winemac: Don't accidentally unminimize a window when trying to change z-order.
[wine/multimedia.git] / dlls / winemac.drv / cocoa_window.m
blob2da8a088740671187cbefabc7c3f0b49cc53b935
1 /*
2  * MACDRV Cocoa window code
3  *
4  * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
21 #import <Carbon/Carbon.h>
23 #import "cocoa_window.h"
25 #include "macdrv_cocoa.h"
26 #import "cocoa_app.h"
27 #import "cocoa_event.h"
28 #import "cocoa_opengl.h"
31 /* Additional Mac virtual keycode, to complement those in Carbon's <HIToolbox/Events.h>. */
32 enum {
33     kVK_RightCommand              = 0x36, /* Invented for Wine; was unused */
37 static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf)
39     NSUInteger style_mask;
41     if (wf->title_bar)
42     {
43         style_mask = NSTitledWindowMask;
44         if (wf->close_button) style_mask |= NSClosableWindowMask;
45         if (wf->minimize_button) style_mask |= NSMiniaturizableWindowMask;
46         if (wf->resizable) style_mask |= NSResizableWindowMask;
47         if (wf->utility) style_mask |= NSUtilityWindowMask;
48     }
49     else style_mask = NSBorderlessWindowMask;
51     return style_mask;
55 static BOOL frame_intersects_screens(NSRect frame, NSArray* screens)
57     NSScreen* screen;
58     for (screen in screens)
59     {
60         if (NSIntersectsRect(frame, [screen frame]))
61             return TRUE;
62     }
63     return FALSE;
67 static NSScreen* screen_covered_by_rect(NSRect rect, NSArray* screens)
69     for (NSScreen* screen in screens)
70     {
71         if (NSContainsRect(rect, [screen frame]))
72             return screen;
73     }
74     return nil;
78 /* We rely on the supposedly device-dependent modifier flags to distinguish the
79    keys on the left side of the keyboard from those on the right.  Some event
80    sources don't set those device-depdendent flags.  If we see a device-independent
81    flag for a modifier without either corresponding device-dependent flag, assume
82    the left one. */
83 static inline void fix_device_modifiers_by_generic(NSUInteger* modifiers)
85     if ((*modifiers & (NX_COMMANDMASK | NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) == NX_COMMANDMASK)
86         *modifiers |= NX_DEVICELCMDKEYMASK;
87     if ((*modifiers & (NX_SHIFTMASK | NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) == NX_SHIFTMASK)
88         *modifiers |= NX_DEVICELSHIFTKEYMASK;
89     if ((*modifiers & (NX_CONTROLMASK | NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) == NX_CONTROLMASK)
90         *modifiers |= NX_DEVICELCTLKEYMASK;
91     if ((*modifiers & (NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) == NX_ALTERNATEMASK)
92         *modifiers |= NX_DEVICELALTKEYMASK;
95 /* As we manipulate individual bits of a modifier mask, we can end up with
96    inconsistent sets of flags.  In particular, we might set or clear one of the
97    left/right-specific bits, but not the corresponding non-side-specific bit.
98    Fix that.  If either side-specific bit is set, set the non-side-specific bit,
99    otherwise clear it. */
100 static inline void fix_generic_modifiers_by_device(NSUInteger* modifiers)
102     if (*modifiers & (NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK))
103         *modifiers |= NX_COMMANDMASK;
104     else
105         *modifiers &= ~NX_COMMANDMASK;
106     if (*modifiers & (NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK))
107         *modifiers |= NX_SHIFTMASK;
108     else
109         *modifiers &= ~NX_SHIFTMASK;
110     if (*modifiers & (NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK))
111         *modifiers |= NX_CONTROLMASK;
112     else
113         *modifiers &= ~NX_CONTROLMASK;
114     if (*modifiers & (NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK))
115         *modifiers |= NX_ALTERNATEMASK;
116     else
117         *modifiers &= ~NX_ALTERNATEMASK;
121 @interface WineContentView : NSView <NSTextInputClient>
123     NSMutableArray* glContexts;
124     NSMutableArray* pendingGlContexts;
126     NSMutableAttributedString* markedText;
127     NSRange markedTextSelection;
130     - (void) addGLContext:(WineOpenGLContext*)context;
131     - (void) removeGLContext:(WineOpenGLContext*)context;
132     - (void) updateGLContexts;
134 @end
137 @interface WineWindow ()
139 @property (nonatomic) BOOL disabled;
140 @property (nonatomic) BOOL noActivate;
141 @property (readwrite, nonatomic) BOOL floating;
142 @property (retain, nonatomic) NSWindow* latentParentWindow;
144 @property (nonatomic) void* hwnd;
145 @property (retain, readwrite, nonatomic) WineEventQueue* queue;
147 @property (nonatomic) void* surface;
148 @property (nonatomic) pthread_mutex_t* surface_mutex;
150 @property (copy, nonatomic) NSBezierPath* shape;
151 @property (nonatomic) BOOL shapeChangedSinceLastDraw;
152 @property (readonly, nonatomic) BOOL needsTransparency;
154 @property (nonatomic) BOOL colorKeyed;
155 @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
156 @property (nonatomic) BOOL usePerPixelAlpha;
158 @property (assign, nonatomic) void* imeData;
159 @property (nonatomic) BOOL commandDone;
161 @end
164 @implementation WineContentView
166     - (void) dealloc
167     {
168         [markedText release];
169         [glContexts release];
170         [pendingGlContexts release];
171         [super dealloc];
172     }
174     - (BOOL) isFlipped
175     {
176         return YES;
177     }
179     - (void) drawRect:(NSRect)rect
180     {
181         WineWindow* window = (WineWindow*)[self window];
183         for (WineOpenGLContext* context in pendingGlContexts)
184             context.needsUpdate = TRUE;
185         [glContexts addObjectsFromArray:pendingGlContexts];
186         [pendingGlContexts removeAllObjects];
188         if ([window contentView] != self)
189             return;
191         if (window.surface && window.surface_mutex &&
192             !pthread_mutex_lock(window.surface_mutex))
193         {
194             const CGRect* rects;
195             int count;
197             if (get_surface_blit_rects(window.surface, &rects, &count) && count)
198             {
199                 CGContextRef context;
200                 int i;
202                 [window.shape addClip];
204                 context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
205                 CGContextSetBlendMode(context, kCGBlendModeCopy);
207                 for (i = 0; i < count; i++)
208                 {
209                     CGRect imageRect;
210                     CGImageRef image;
212                     imageRect = CGRectIntersection(rects[i], NSRectToCGRect(rect));
213                     image = create_surface_image(window.surface, &imageRect, FALSE);
215                     if (image)
216                     {
217                         if (window.colorKeyed)
218                         {
219                             CGImageRef maskedImage;
220                             CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5,
221                                                      window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5,
222                                                      window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 };
223                             maskedImage = CGImageCreateWithMaskingColors(image, components);
224                             if (maskedImage)
225                             {
226                                 CGImageRelease(image);
227                                 image = maskedImage;
228                             }
229                         }
231                         CGContextDrawImage(context, imageRect, image);
233                         CGImageRelease(image);
234                     }
235                 }
236             }
238             pthread_mutex_unlock(window.surface_mutex);
239         }
241         // If the window may be transparent, then we have to invalidate the
242         // shadow every time we draw.  Also, if this is the first time we've
243         // drawn since changing from transparent to opaque.
244         if (![window isOpaque] || window.shapeChangedSinceLastDraw)
245         {
246             window.shapeChangedSinceLastDraw = FALSE;
247             [window invalidateShadow];
248         }
249     }
251     - (void) addGLContext:(WineOpenGLContext*)context
252     {
253         if (!glContexts)
254             glContexts = [[NSMutableArray alloc] init];
255         if (!pendingGlContexts)
256             pendingGlContexts = [[NSMutableArray alloc] init];
257         [pendingGlContexts addObject:context];
258         [self setNeedsDisplay:YES];
259     }
261     - (void) removeGLContext:(WineOpenGLContext*)context
262     {
263         [glContexts removeObjectIdenticalTo:context];
264         [pendingGlContexts removeObjectIdenticalTo:context];
265     }
267     - (void) updateGLContexts
268     {
269         for (WineOpenGLContext* context in glContexts)
270             context.needsUpdate = TRUE;
271     }
273     - (BOOL) acceptsFirstMouse:(NSEvent*)theEvent
274     {
275         return YES;
276     }
278     - (BOOL) preservesContentDuringLiveResize
279     {
280         // Returning YES from this tells Cocoa to keep our view's content during
281         // a Cocoa-driven resize.  In theory, we're also supposed to override
282         // -setFrameSize: to mark exposed sections as needing redisplay, but
283         // user32 will take care of that in a roundabout way.  This way, we don't
284         // redraw until the window surface is flushed.
285         //
286         // This doesn't do anything when we resize the window ourselves.
287         return YES;
288     }
290     - (BOOL)acceptsFirstResponder
291     {
292         return [[self window] contentView] == self;
293     }
295     - (void) completeText:(NSString*)text
296     {
297         macdrv_event* event;
298         WineWindow* window = (WineWindow*)[self window];
300         event = macdrv_create_event(IM_SET_TEXT, window);
301         event->im_set_text.data = [window imeData];
302         event->im_set_text.text = (CFStringRef)[text copy];
303         event->im_set_text.complete = TRUE;
305         [[window queue] postEvent:event];
307         macdrv_release_event(event);
309         [markedText deleteCharactersInRange:NSMakeRange(0, [markedText length])];
310         markedTextSelection = NSMakeRange(0, 0);
311         [[self inputContext] discardMarkedText];
312     }
314     /*
315      * ---------- NSTextInputClient methods ----------
316      */
317     - (NSTextInputContext*) inputContext
318     {
319         if (!markedText)
320             markedText = [[NSMutableAttributedString alloc] init];
321         return [super inputContext];
322     }
324     - (void) insertText:(id)string replacementRange:(NSRange)replacementRange
325     {
326         if ([string isKindOfClass:[NSAttributedString class]])
327             string = [string string];
329         if ([string isKindOfClass:[NSString class]])
330             [self completeText:string];
331     }
333     - (void) doCommandBySelector:(SEL)aSelector
334     {
335         [(WineWindow*)[self window] setCommandDone:TRUE];
336     }
338     - (void) setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
339     {
340         if ([string isKindOfClass:[NSAttributedString class]])
341             string = [string string];
343         if ([string isKindOfClass:[NSString class]])
344         {
345             macdrv_event* event;
346             WineWindow* window = (WineWindow*)[self window];
348             if (replacementRange.location == NSNotFound)
349                 replacementRange = NSMakeRange(0, [markedText length]);
351             [markedText replaceCharactersInRange:replacementRange withString:string];
352             markedTextSelection = selectedRange;
353             markedTextSelection.location += replacementRange.location;
355             event = macdrv_create_event(IM_SET_TEXT, window);
356             event->im_set_text.data = [window imeData];
357             event->im_set_text.text = (CFStringRef)[[markedText string] copy];
358             event->im_set_text.complete = FALSE;
359             event->im_set_text.cursor_pos = markedTextSelection.location;
361             [[window queue] postEvent:event];
363             macdrv_release_event(event);
365             [[self inputContext] invalidateCharacterCoordinates];
366         }
367     }
369     - (void) unmarkText
370     {
371         [self completeText:nil];
372     }
374     - (NSRange) selectedRange
375     {
376         return markedTextSelection;
377     }
379     - (NSRange) markedRange
380     {
381         NSRange range = NSMakeRange(0, [markedText length]);
382         if (!range.length)
383             range.location = NSNotFound;
384         return range;
385     }
387     - (BOOL) hasMarkedText
388     {
389         return [markedText length] > 0;
390     }
392     - (NSAttributedString*) attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
393     {
394         if (aRange.location >= [markedText length])
395             return nil;
397         aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
398         if (actualRange)
399             *actualRange = aRange;
400         return [markedText attributedSubstringFromRange:aRange];
401     }
403     - (NSArray*) validAttributesForMarkedText
404     {
405         return [NSArray array];
406     }
408     - (NSRect) firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
409     {
410         macdrv_query* query;
411         WineWindow* window = (WineWindow*)[self window];
412         NSRect ret;
414         aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
416         query = macdrv_create_query();
417         query->type = QUERY_IME_CHAR_RECT;
418         query->window = (macdrv_window)[window retain];
419         query->ime_char_rect.data = [window imeData];
420         query->ime_char_rect.range = CFRangeMake(aRange.location, aRange.length);
422         if ([window.queue query:query timeout:1])
423         {
424             aRange = NSMakeRange(query->ime_char_rect.range.location, query->ime_char_rect.range.length);
425             ret = NSRectFromCGRect(query->ime_char_rect.rect);
426             [[WineApplicationController sharedController] flipRect:&ret];
427         }
428         else
429             ret = NSMakeRect(100, 100, aRange.length ? 1 : 0, 12);
431         macdrv_release_query(query);
433         if (actualRange)
434             *actualRange = aRange;
435         return ret;
436     }
438     - (NSUInteger) characterIndexForPoint:(NSPoint)aPoint
439     {
440         return NSNotFound;
441     }
443     - (NSInteger) windowLevel
444     {
445         return [[self window] level];
446     }
448 @end
451 @implementation WineWindow
453     @synthesize disabled, noActivate, floating, latentParentWindow, hwnd, queue;
454     @synthesize surface, surface_mutex;
455     @synthesize shape, shapeChangedSinceLastDraw;
456     @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
457     @synthesize usePerPixelAlpha;
458     @synthesize imeData, commandDone;
460     + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
461                                  windowFrame:(NSRect)window_frame
462                                         hwnd:(void*)hwnd
463                                        queue:(WineEventQueue*)queue
464     {
465         WineWindow* window;
466         WineContentView* contentView;
467         NSTrackingArea* trackingArea;
469         [[WineApplicationController sharedController] flipRect:&window_frame];
471         window = [[[self alloc] initWithContentRect:window_frame
472                                           styleMask:style_mask_for_features(wf)
473                                             backing:NSBackingStoreBuffered
474                                               defer:YES] autorelease];
476         if (!window) return nil;
478         /* Standardize windows to eliminate differences between titled and
479            borderless windows and between NSWindow and NSPanel. */
480         [window setHidesOnDeactivate:NO];
481         [window setReleasedWhenClosed:NO];
483         [window disableCursorRects];
484         [window setShowsResizeIndicator:NO];
485         [window setHasShadow:wf->shadow];
486         [window setAcceptsMouseMovedEvents:YES];
487         [window setColorSpace:[NSColorSpace genericRGBColorSpace]];
488         [window setDelegate:window];
489         window.hwnd = hwnd;
490         window.queue = queue;
492         [window registerForDraggedTypes:[NSArray arrayWithObjects:(NSString*)kUTTypeData,
493                                                                   (NSString*)kUTTypeContent,
494                                                                   nil]];
496         contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
497         if (!contentView)
498             return nil;
499         [contentView setAutoresizesSubviews:NO];
501         /* We use tracking areas in addition to setAcceptsMouseMovedEvents:YES
502            because they give us mouse moves in the background. */
503         trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds]
504                                                      options:(NSTrackingMouseMoved |
505                                                               NSTrackingActiveAlways |
506                                                               NSTrackingInVisibleRect)
507                                                        owner:window
508                                                     userInfo:nil] autorelease];
509         if (!trackingArea)
510             return nil;
511         [contentView addTrackingArea:trackingArea];
513         [window setContentView:contentView];
514         [window setInitialFirstResponder:contentView];
516         return window;
517     }
519     - (void) dealloc
520     {
521         [liveResizeDisplayTimer invalidate];
522         [liveResizeDisplayTimer release];
523         [queue release];
524         [latentParentWindow release];
525         [shape release];
526         [super dealloc];
527     }
529     - (void) adjustFeaturesForState
530     {
531         NSUInteger style = [self styleMask];
533         if (style & NSClosableWindowMask)
534             [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
535         if (style & NSMiniaturizableWindowMask)
536             [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
537         if (style & NSResizableWindowMask)
538             [[self standardWindowButton:NSWindowZoomButton] setEnabled:!self.disabled];
539     }
541     - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
542     {
543         NSUInteger currentStyle = [self styleMask];
544         NSUInteger newStyle = style_mask_for_features(wf);
546         if (newStyle != currentStyle)
547         {
548             BOOL showingButtons = (currentStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
549             BOOL shouldShowButtons = (newStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
550             if (shouldShowButtons != showingButtons && !((newStyle ^ currentStyle) & NSClosableWindowMask))
551             {
552                 // -setStyleMask: is buggy on 10.7+ with respect to NSResizableWindowMask.
553                 // If transitioning from NSTitledWindowMask | NSResizableWindowMask to
554                 // just NSTitledWindowMask, the window buttons should disappear rather
555                 // than just being disabled.  But they don't.  Similarly in reverse.
556                 // The workaround is to also toggle NSClosableWindowMask at the same time.
557                 [self setStyleMask:newStyle ^ NSClosableWindowMask];
558             }
559             [self setStyleMask:newStyle];
560         }
562         [self adjustFeaturesForState];
563         [self setHasShadow:wf->shadow];
564     }
566     - (BOOL) isOrderedIn
567     {
568         return [self isVisible] || [self isMiniaturized];
569     }
571     - (NSInteger) minimumLevelForActive:(BOOL)active
572     {
573         NSScreen* screen;
574         BOOL fullscreen;
575         NSInteger level;
577         screen = screen_covered_by_rect([self frame], [NSScreen screens]);
578         fullscreen = (screen != nil);
580         if (self.floating && (active || topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_ALL ||
581                               (topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_NONFULLSCREEN && !fullscreen)))
582             level = NSFloatingWindowLevel;
583         else
584             level = NSNormalWindowLevel;
586         if (active)
587         {
588             BOOL captured;
590             captured = (screen || [self screen]) && [[WineApplicationController sharedController] areDisplaysCaptured];
592             if (captured || fullscreen)
593             {
594                 if (captured)
595                     level = CGShieldingWindowLevel() + 1; /* Need +1 or we don't get mouse moves */
596                 else
597                     level = NSMainMenuWindowLevel + 1;
599                 if (self.floating)
600                     level++;
601             }
602         }
604         return level;
605     }
607     - (void) setMacDrvState:(const struct macdrv_window_state*)state
608     {
609         NSWindowCollectionBehavior behavior;
611         self.disabled = state->disabled;
612         self.noActivate = state->no_activate;
614         if (self.floating != state->floating)
615         {
616             self.floating = state->floating;
617             [[WineApplicationController sharedController] adjustWindowLevels];
618         }
620         behavior = NSWindowCollectionBehaviorDefault;
621         if (state->excluded_by_expose)
622             behavior |= NSWindowCollectionBehaviorTransient;
623         else
624             behavior |= NSWindowCollectionBehaviorManaged;
625         if (state->excluded_by_cycle)
626         {
627             behavior |= NSWindowCollectionBehaviorIgnoresCycle;
628             if ([self isOrderedIn])
629                 [NSApp removeWindowsItem:self];
630         }
631         else
632         {
633             behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
634             if ([self isOrderedIn])
635                 [NSApp addWindowsItem:self title:[self title] filename:NO];
636         }
637         [self setCollectionBehavior:behavior];
639         if (state->minimized && ![self isMiniaturized])
640         {
641             ignore_windowMiniaturize = TRUE;
642             [self miniaturize:nil];
643         }
644         else if (!state->minimized && [self isMiniaturized])
645         {
646             ignore_windowDeminiaturize = TRUE;
647             [self deminiaturize:nil];
648         }
650         /* Whatever events regarding minimization might have been in the queue are now stale. */
651         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
652                                          event_mask_for_type(WINDOW_DID_UNMINIMIZE)
653                                forWindow:self];
654     }
656     // Determine if, among Wine windows, this window is directly above or below
657     // a given other Wine window with no other Wine window intervening.
658     // Intervening non-Wine windows are ignored.
659     - (BOOL) isOrdered:(NSWindowOrderingMode)orderingMode relativeTo:(WineWindow*)otherWindow
660     {
661         NSNumber* windowNumber;
662         NSNumber* otherWindowNumber;
663         NSArray* windowNumbers;
664         NSUInteger windowIndex, otherWindowIndex, lowIndex, highIndex, i;
666         if (![self isVisible] || ![otherWindow isVisible])
667             return FALSE;
669         windowNumber = [NSNumber numberWithInteger:[self windowNumber]];
670         otherWindowNumber = [NSNumber numberWithInteger:[otherWindow windowNumber]];
671         windowNumbers = [[self class] windowNumbersWithOptions:0];
672         windowIndex = [windowNumbers indexOfObject:windowNumber];
673         otherWindowIndex = [windowNumbers indexOfObject:otherWindowNumber];
675         if (windowIndex == NSNotFound || otherWindowIndex == NSNotFound)
676             return FALSE;
678         if (orderingMode == NSWindowAbove)
679         {
680             lowIndex = windowIndex;
681             highIndex = otherWindowIndex;
682         }
683         else if (orderingMode == NSWindowBelow)
684         {
685             lowIndex = otherWindowIndex;
686             highIndex = windowIndex;
687         }
688         else
689             return FALSE;
691         if (highIndex <= lowIndex)
692             return FALSE;
694         for (i = lowIndex + 1; i < highIndex; i++)
695         {
696             NSInteger interveningWindowNumber = [[windowNumbers objectAtIndex:i] integerValue];
697             NSWindow* interveningWindow = [NSApp windowWithWindowNumber:interveningWindowNumber];
698             if ([interveningWindow isKindOfClass:[WineWindow class]])
699                 return FALSE;
700         }
702         return TRUE;
703     }
705     /* Returns whether or not the window was ordered in, which depends on if
706        its frame intersects any screen. */
707     - (BOOL) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next
708     {
709         WineApplicationController* controller = [WineApplicationController sharedController];
710         BOOL on_screen = frame_intersects_screens([self frame], [NSScreen screens]);
711         if (on_screen && ![self isMiniaturized])
712         {
713             BOOL needAdjustWindowLevels = FALSE;
715             [controller transformProcessToForeground];
717             NSDisableScreenUpdates();
719             if (latentParentWindow)
720             {
721                 if ([latentParentWindow level] > [self level])
722                     [self setLevel:[latentParentWindow level]];
723                 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
724                 self.latentParentWindow = nil;
725                 needAdjustWindowLevels = TRUE;
726             }
727             if (prev || next)
728             {
729                 WineWindow* other = [prev isVisible] ? prev : next;
730                 NSWindowOrderingMode orderingMode = [prev isVisible] ? NSWindowBelow : NSWindowAbove;
732                 if (![self isOrdered:orderingMode relativeTo:other])
733                 {
734                     // This window level may not be right for this window based
735                     // on floating-ness, fullscreen-ness, etc.  But we set it
736                     // temporarily to allow us to order the windows properly.
737                     // Then the levels get fixed by -adjustWindowLevels.
738                     if ([self level] != [other level])
739                         [self setLevel:[other level]];
740                     [self orderWindow:orderingMode relativeTo:[other windowNumber]];
741                     needAdjustWindowLevels = TRUE;
742                 }
743             }
744             else
745             {
746                 // Again, temporarily set level to make sure we can order to
747                 // the right place.
748                 next = [controller frontWineWindow];
749                 if (next && [self level] < [next level])
750                     [self setLevel:[next level]];
751                 [self orderFront:nil];
752                 needAdjustWindowLevels = TRUE;
753             }
754             if (needAdjustWindowLevels)
755                 [controller adjustWindowLevels];
757             NSEnableScreenUpdates();
759             /* Cocoa may adjust the frame when the window is ordered onto the screen.
760                Generate a frame-changed event just in case.  The back end will ignore
761                it if nothing actually changed. */
762             [self windowDidResize:nil];
764             if (![self isExcludedFromWindowsMenu])
765                 [NSApp addWindowsItem:self title:[self title] filename:NO];
766         }
768         return on_screen;
769     }
771     - (void) doOrderOut
772     {
773         self.latentParentWindow = [self parentWindow];
774         [latentParentWindow removeChildWindow:self];
775         [self orderOut:nil];
776         [[WineApplicationController sharedController] adjustWindowLevels];
777         [NSApp removeWindowsItem:self];
778     }
780     - (BOOL) setFrameIfOnScreen:(NSRect)contentRect
781     {
782         NSArray* screens = [NSScreen screens];
783         BOOL on_screen = [self isOrderedIn];
785         if (![screens count]) return on_screen;
787         /* Origin is (left, top) in a top-down space.  Need to convert it to
788            (left, bottom) in a bottom-up space. */
789         [[WineApplicationController sharedController] flipRect:&contentRect];
791         if (on_screen)
792         {
793             on_screen = frame_intersects_screens(contentRect, screens);
794             if (!on_screen)
795                 [self doOrderOut];
796         }
798         /* The back end is establishing a new window size and position.  It's
799            not interested in any stale events regarding those that may be sitting
800            in the queue. */
801         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
802                                forWindow:self];
804         if (!NSIsEmptyRect(contentRect))
805         {
806             NSRect frame, oldFrame;
808             oldFrame = [self frame];
809             frame = [self frameRectForContentRect:contentRect];
810             if (!NSEqualRects(frame, oldFrame))
811             {
812                 if (NSEqualSizes(frame.size, oldFrame.size))
813                     [self setFrameOrigin:frame.origin];
814                 else
815                     [self setFrame:frame display:YES];
817                 if (on_screen)
818                 {
819                     BOOL fullscreen = (screen_covered_by_rect(frame, screens) != nil);
820                     BOOL oldFullscreen = (screen_covered_by_rect(oldFrame, screens) != nil);
822                     if (fullscreen != oldFullscreen)
823                         [[WineApplicationController sharedController] adjustWindowLevels];
825                     /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
826                        event.  The back end will ignore it if nothing actually changed. */
827                     [self windowDidResize:nil];
828                 }
829             }
830         }
832         return on_screen;
833     }
835     - (void) setMacDrvParentWindow:(WineWindow*)parent
836     {
837         if ([self parentWindow] != parent)
838         {
839             [[self parentWindow] removeChildWindow:self];
840             self.latentParentWindow = nil;
841             if ([self isVisible] && parent)
842             {
843                 if ([parent level] > [self level])
844                     [self setLevel:[parent level]];
845                 [parent addChildWindow:self ordered:NSWindowAbove];
846                 [[WineApplicationController sharedController] adjustWindowLevels];
847             }
848             else
849                 self.latentParentWindow = parent;
850         }
851     }
853     - (void) setDisabled:(BOOL)newValue
854     {
855         if (disabled != newValue)
856         {
857             disabled = newValue;
858             [self adjustFeaturesForState];
860             if (disabled)
861             {
862                 NSSize size = [self frame].size;
863                 [self setMinSize:size];
864                 [self setMaxSize:size];
865             }
866             else
867             {
868                 [self setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
869                 [self setMinSize:NSZeroSize];
870             }
871         }
872     }
874     - (BOOL) needsTransparency
875     {
876         return self.shape || self.colorKeyed || self.usePerPixelAlpha;
877     }
879     - (void) checkTransparency
880     {
881         if (![self isOpaque] && !self.needsTransparency)
882         {
883             [self setBackgroundColor:[NSColor windowBackgroundColor]];
884             [self setOpaque:YES];
885         }
886         else if ([self isOpaque] && self.needsTransparency)
887         {
888             [self setBackgroundColor:[NSColor clearColor]];
889             [self setOpaque:NO];
890         }
891     }
893     - (void) setShape:(NSBezierPath*)newShape
894     {
895         if (shape == newShape) return;
896         if (shape && newShape && [shape isEqual:newShape]) return;
898         if (shape)
899         {
900             [[self contentView] setNeedsDisplayInRect:[shape bounds]];
901             [shape release];
902         }
903         if (newShape)
904             [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
906         shape = [newShape copy];
907         self.shapeChangedSinceLastDraw = TRUE;
909         [self checkTransparency];
910     }
912     - (void) makeFocused:(BOOL)activate
913     {
914         WineApplicationController* controller = [WineApplicationController sharedController];
915         NSArray* screens;
916         WineWindow* front;
918         [controller transformProcessToForeground];
920         /* If a borderless window is offscreen, orderFront: won't move
921            it onscreen like it would for a titled window.  Do that ourselves. */
922         screens = [NSScreen screens];
923         if (!([self styleMask] & NSTitledWindowMask) && ![self isOrderedIn] &&
924             !frame_intersects_screens([self frame], screens))
925         {
926             NSScreen* primaryScreen = [screens objectAtIndex:0];
927             NSRect frame = [primaryScreen frame];
928             [self setFrameTopLeftPoint:NSMakePoint(NSMinX(frame), NSMaxY(frame))];
929             frame = [self constrainFrameRect:[self frame] toScreen:primaryScreen];
930             [self setFrame:frame display:YES];
931         }
933         if (activate)
934             [NSApp activateIgnoringOtherApps:YES];
936         NSDisableScreenUpdates();
938         if (latentParentWindow)
939         {
940             if ([latentParentWindow level] > [self level])
941                 [self setLevel:[latentParentWindow level]];
942             [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
943             self.latentParentWindow = nil;
944         }
945         front = [controller frontWineWindow];
946         if (front && [self level] < [front level])
947             [self setLevel:[front level]];
948         [self orderFront:nil];
949         [controller adjustWindowLevels];
951         NSEnableScreenUpdates();
953         causing_becomeKeyWindow = TRUE;
954         [self makeKeyWindow];
955         causing_becomeKeyWindow = FALSE;
957         if (![self isExcludedFromWindowsMenu])
958             [NSApp addWindowsItem:self title:[self title] filename:NO];
960         /* Cocoa may adjust the frame when the window is ordered onto the screen.
961            Generate a frame-changed event just in case.  The back end will ignore
962            it if nothing actually changed. */
963         [self windowDidResize:nil];
964     }
966     - (void) postKey:(uint16_t)keyCode
967              pressed:(BOOL)pressed
968            modifiers:(NSUInteger)modifiers
969                event:(NSEvent*)theEvent
970     {
971         macdrv_event* event;
972         CGEventRef cgevent;
973         WineApplicationController* controller = [WineApplicationController sharedController];
975         event = macdrv_create_event(pressed ? KEY_PRESS : KEY_RELEASE, self);
976         event->key.keycode   = keyCode;
977         event->key.modifiers = modifiers;
978         event->key.time_ms   = [controller ticksForEventTime:[theEvent timestamp]];
980         if ((cgevent = [theEvent CGEvent]))
981         {
982             CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent,
983                                                         kCGKeyboardEventKeyboardType);
984             if (keyboardType != controller.keyboardType)
985             {
986                 controller.keyboardType = keyboardType;
987                 [controller keyboardSelectionDidChange];
988             }
989         }
991         [queue postEvent:event];
993         macdrv_release_event(event);
994     }
996     - (void) postKeyEvent:(NSEvent *)theEvent
997     {
998         [self flagsChanged:theEvent];
999         [self postKey:[theEvent keyCode]
1000               pressed:[theEvent type] == NSKeyDown
1001             modifiers:[theEvent modifierFlags]
1002                 event:theEvent];
1003     }
1006     /*
1007      * ---------- NSWindow method overrides ----------
1008      */
1009     - (BOOL) canBecomeKeyWindow
1010     {
1011         if (causing_becomeKeyWindow) return YES;
1012         if (self.disabled || self.noActivate) return NO;
1013         return [self isKeyWindow];
1014     }
1016     - (BOOL) canBecomeMainWindow
1017     {
1018         return [self canBecomeKeyWindow];
1019     }
1021     - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
1022     {
1023         // If a window is sized to completely cover a screen, then it's in
1024         // full-screen mode.  In that case, we don't allow NSWindow to constrain
1025         // it.
1026         NSRect contentRect = [self contentRectForFrameRect:frameRect];
1027         if (!screen_covered_by_rect(contentRect, [NSScreen screens]))
1028             frameRect = [super constrainFrameRect:frameRect toScreen:screen];
1029         return frameRect;
1030     }
1032     - (BOOL) isExcludedFromWindowsMenu
1033     {
1034         return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
1035     }
1037     - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
1038     {
1039         BOOL ret = [super validateMenuItem:menuItem];
1041         if ([menuItem action] == @selector(makeKeyAndOrderFront:))
1042             ret = [self isKeyWindow] || (!self.disabled && !self.noActivate);
1044         return ret;
1045     }
1047     /* We don't call this.  It's the action method of the items in the Window menu. */
1048     - (void) makeKeyAndOrderFront:(id)sender
1049     {
1050         WineApplicationController* controller = [WineApplicationController sharedController];
1051         WineWindow* front = [controller frontWineWindow];
1053         if (![self isKeyWindow] && !self.disabled && !self.noActivate)
1054             [controller windowGotFocus:self];
1056         if (front && [self level] < [front level])
1057             [self setLevel:[front level]];
1058         [self orderFront:nil];
1059         [controller adjustWindowLevels];
1060     }
1062     - (void) sendEvent:(NSEvent*)event
1063     {
1064         WineApplicationController* controller = [WineApplicationController sharedController];
1066         /* NSWindow consumes certain key-down events as part of Cocoa's keyboard
1067            interface control.  For example, Control-Tab switches focus among
1068            views.  We want to bypass that feature, so directly route key-down
1069            events to -keyDown:. */
1070         if ([event type] == NSKeyDown)
1071             [[self firstResponder] keyDown:event];
1072         else
1073         {
1074             if ([event type] == NSLeftMouseDown)
1075             {
1076                 /* Since our windows generally claim they can't be made key, clicks
1077                    in their title bars are swallowed by the theme frame stuff.  So,
1078                    we hook directly into the event stream and assume that any click
1079                    in the window will activate it, if Wine and the Win32 program
1080                    accept. */
1081                 if (![self isKeyWindow] && !self.disabled && !self.noActivate)
1082                     [controller windowGotFocus:self];
1083             }
1085             [super sendEvent:event];
1086         }
1087     }
1090     /*
1091      * ---------- NSResponder method overrides ----------
1092      */
1093     - (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
1094     - (void) keyUp:(NSEvent *)theEvent   { [self postKeyEvent:theEvent]; }
1096     - (void) flagsChanged:(NSEvent *)theEvent
1097     {
1098         static const struct {
1099             NSUInteger  mask;
1100             uint16_t    keycode;
1101         } modifiers[] = {
1102             { NX_ALPHASHIFTMASK,        kVK_CapsLock },
1103             { NX_DEVICELSHIFTKEYMASK,   kVK_Shift },
1104             { NX_DEVICERSHIFTKEYMASK,   kVK_RightShift },
1105             { NX_DEVICELCTLKEYMASK,     kVK_Control },
1106             { NX_DEVICERCTLKEYMASK,     kVK_RightControl },
1107             { NX_DEVICELALTKEYMASK,     kVK_Option },
1108             { NX_DEVICERALTKEYMASK,     kVK_RightOption },
1109             { NX_DEVICELCMDKEYMASK,     kVK_Command },
1110             { NX_DEVICERCMDKEYMASK,     kVK_RightCommand },
1111         };
1113         NSUInteger modifierFlags = [theEvent modifierFlags];
1114         NSUInteger changed;
1115         int i, last_changed;
1117         fix_device_modifiers_by_generic(&modifierFlags);
1118         changed = modifierFlags ^ lastModifierFlags;
1120         last_changed = -1;
1121         for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++)
1122             if (changed & modifiers[i].mask)
1123                 last_changed = i;
1125         for (i = 0; i <= last_changed; i++)
1126         {
1127             if (changed & modifiers[i].mask)
1128             {
1129                 BOOL pressed = (modifierFlags & modifiers[i].mask) != 0;
1131                 if (i == last_changed)
1132                     lastModifierFlags = modifierFlags;
1133                 else
1134                 {
1135                     lastModifierFlags ^= modifiers[i].mask;
1136                     fix_generic_modifiers_by_device(&lastModifierFlags);
1137                 }
1139                 // Caps lock generates one event for each press-release action.
1140                 // We need to simulate a pair of events for each actual event.
1141                 if (modifiers[i].mask == NX_ALPHASHIFTMASK)
1142                 {
1143                     [self postKey:modifiers[i].keycode
1144                           pressed:TRUE
1145                         modifiers:lastModifierFlags
1146                             event:(NSEvent*)theEvent];
1147                     pressed = FALSE;
1148                 }
1150                 [self postKey:modifiers[i].keycode
1151                       pressed:pressed
1152                     modifiers:lastModifierFlags
1153                         event:(NSEvent*)theEvent];
1154             }
1155         }
1156     }
1159     /*
1160      * ---------- NSWindowDelegate methods ----------
1161      */
1162     - (void)windowDidBecomeKey:(NSNotification *)notification
1163     {
1164         WineApplicationController* controller = [WineApplicationController sharedController];
1165         NSEvent* event = [controller lastFlagsChanged];
1166         if (event)
1167             [self flagsChanged:event];
1169         if (causing_becomeKeyWindow) return;
1171         [controller windowGotFocus:self];
1172     }
1174     - (void)windowDidDeminiaturize:(NSNotification *)notification
1175     {
1176         if (!ignore_windowDeminiaturize)
1177         {
1178             macdrv_event* event;
1180             /* Coalesce events by discarding any previous ones still in the queue. */
1181             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
1182                                              event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1183                                    forWindow:self];
1185             event = macdrv_create_event(WINDOW_DID_UNMINIMIZE, self);
1186             [queue postEvent:event];
1187             macdrv_release_event(event);
1188         }
1190         ignore_windowDeminiaturize = FALSE;
1192         [[WineApplicationController sharedController] adjustWindowLevels];
1193     }
1195     - (void) windowDidEndLiveResize:(NSNotification *)notification
1196     {
1197         [liveResizeDisplayTimer invalidate];
1198         [liveResizeDisplayTimer release];
1199         liveResizeDisplayTimer = nil;
1200     }
1202     - (void)windowDidMove:(NSNotification *)notification
1203     {
1204         [self windowDidResize:notification];
1205     }
1207     - (void)windowDidResignKey:(NSNotification *)notification
1208     {
1209         macdrv_event* event;
1211         if (causing_becomeKeyWindow) return;
1213         event = macdrv_create_event(WINDOW_LOST_FOCUS, self);
1214         [queue postEvent:event];
1215         macdrv_release_event(event);
1216     }
1218     - (void)windowDidResize:(NSNotification *)notification
1219     {
1220         macdrv_event* event;
1221         NSRect frame = [self frame];
1223         if (self.disabled)
1224         {
1225             NSSize size = frame.size;
1226             [self setMinSize:size];
1227             [self setMaxSize:size];
1228         }
1230         frame = [self contentRectForFrameRect:frame];
1231         [[WineApplicationController sharedController] flipRect:&frame];
1233         /* Coalesce events by discarding any previous ones still in the queue. */
1234         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
1235                                forWindow:self];
1237         event = macdrv_create_event(WINDOW_FRAME_CHANGED, self);
1238         event->window_frame_changed.frame = NSRectToCGRect(frame);
1239         [queue postEvent:event];
1240         macdrv_release_event(event);
1242         [[[self contentView] inputContext] invalidateCharacterCoordinates];
1243     }
1245     - (BOOL)windowShouldClose:(id)sender
1246     {
1247         macdrv_event* event = macdrv_create_event(WINDOW_CLOSE_REQUESTED, self);
1248         [queue postEvent:event];
1249         macdrv_release_event(event);
1250         return NO;
1251     }
1253     - (void)windowWillMiniaturize:(NSNotification *)notification
1254     {
1255         if (!ignore_windowMiniaturize)
1256         {
1257             macdrv_event* event;
1259             /* Coalesce events by discarding any previous ones still in the queue. */
1260             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_MINIMIZE) |
1261                                              event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1262                                    forWindow:self];
1264             event = macdrv_create_event(WINDOW_DID_MINIMIZE, self);
1265             [queue postEvent:event];
1266             macdrv_release_event(event);
1267         }
1269         ignore_windowMiniaturize = FALSE;
1270     }
1272     - (void) windowWillStartLiveResize:(NSNotification *)notification
1273     {
1274         // There's a strange restriction in window redrawing during Cocoa-
1275         // managed window resizing.  Only calls to -[NSView setNeedsDisplay...]
1276         // that happen synchronously when Cocoa tells us that our window size
1277         // has changed or asynchronously in a short interval thereafter provoke
1278         // the window to redraw.  Calls to those methods that happen asynchronously
1279         // a half second or more after the last change of the window size aren't
1280         // heeded until the next resize-related user event (e.g. mouse movement).
1281         //
1282         // Wine often has a significant delay between when it's been told that
1283         // the window has changed size and when it can flush completed drawing.
1284         // So, our windows would get stuck with incomplete drawing for as long
1285         // as the user holds the mouse button down and doesn't move it.
1286         //
1287         // We address this by "manually" asking our windows to check if they need
1288         // redrawing every so often (during live resize only).
1289         [self windowDidEndLiveResize:nil];
1290         liveResizeDisplayTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0
1291                                                                   target:self
1292                                                                 selector:@selector(displayIfNeeded)
1293                                                                 userInfo:nil
1294                                                                  repeats:YES];
1295         [liveResizeDisplayTimer retain];
1296         [[NSRunLoop currentRunLoop] addTimer:liveResizeDisplayTimer
1297                                      forMode:NSRunLoopCommonModes];
1298     }
1301     /*
1302      * ---------- NSPasteboardOwner methods ----------
1303      */
1304     - (void) pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
1305     {
1306         macdrv_query* query = macdrv_create_query();
1307         query->type = QUERY_PASTEBOARD_DATA;
1308         query->window = (macdrv_window)[self retain];
1309         query->pasteboard_data.type = (CFStringRef)[type copy];
1311         [self.queue query:query timeout:3];
1312         macdrv_release_query(query);
1313     }
1316     /*
1317      * ---------- NSDraggingDestination methods ----------
1318      */
1319     - (NSDragOperation) draggingEntered:(id <NSDraggingInfo>)sender
1320     {
1321         return [self draggingUpdated:sender];
1322     }
1324     - (void) draggingExited:(id <NSDraggingInfo>)sender
1325     {
1326         // This isn't really a query.  We don't need any response.  However, it
1327         // has to be processed in a similar manner as the other drag-and-drop
1328         // queries in order to maintain the proper order of operations.
1329         macdrv_query* query = macdrv_create_query();
1330         query->type = QUERY_DRAG_EXITED;
1331         query->window = (macdrv_window)[self retain];
1333         [self.queue query:query timeout:0.1];
1334         macdrv_release_query(query);
1335     }
1337     - (NSDragOperation) draggingUpdated:(id <NSDraggingInfo>)sender
1338     {
1339         NSDragOperation ret;
1340         NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
1341         NSPasteboard* pb = [sender draggingPasteboard];
1343         macdrv_query* query = macdrv_create_query();
1344         query->type = QUERY_DRAG_OPERATION;
1345         query->window = (macdrv_window)[self retain];
1346         query->drag_operation.x = pt.x;
1347         query->drag_operation.y = pt.y;
1348         query->drag_operation.offered_ops = [sender draggingSourceOperationMask];
1349         query->drag_operation.accepted_op = NSDragOperationNone;
1350         query->drag_operation.pasteboard = (CFTypeRef)[pb retain];
1352         [self.queue query:query timeout:3];
1353         ret = query->status ? query->drag_operation.accepted_op : NSDragOperationNone;
1354         macdrv_release_query(query);
1356         return ret;
1357     }
1359     - (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
1360     {
1361         BOOL ret;
1362         NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
1363         NSPasteboard* pb = [sender draggingPasteboard];
1365         macdrv_query* query = macdrv_create_query();
1366         query->type = QUERY_DRAG_DROP;
1367         query->window = (macdrv_window)[self retain];
1368         query->drag_drop.x = pt.x;
1369         query->drag_drop.y = pt.y;
1370         query->drag_drop.op = [sender draggingSourceOperationMask];
1371         query->drag_drop.pasteboard = (CFTypeRef)[pb retain];
1373         [self.queue query:query timeout:3 * 60 processEvents:YES];
1374         ret = query->status;
1375         macdrv_release_query(query);
1377         return ret;
1378     }
1380     - (BOOL) wantsPeriodicDraggingUpdates
1381     {
1382         return NO;
1383     }
1385 @end
1388 /***********************************************************************
1389  *              macdrv_create_cocoa_window
1391  * Create a Cocoa window with the given content frame and features (e.g.
1392  * title bar, close box, etc.).
1393  */
1394 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
1395         CGRect frame, void* hwnd, macdrv_event_queue queue)
1397     __block WineWindow* window;
1399     OnMainThread(^{
1400         window = [[WineWindow createWindowWithFeatures:wf
1401                                            windowFrame:NSRectFromCGRect(frame)
1402                                                   hwnd:hwnd
1403                                                  queue:(WineEventQueue*)queue] retain];
1404     });
1406     return (macdrv_window)window;
1409 /***********************************************************************
1410  *              macdrv_destroy_cocoa_window
1412  * Destroy a Cocoa window.
1413  */
1414 void macdrv_destroy_cocoa_window(macdrv_window w)
1416     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1417     WineWindow* window = (WineWindow*)w;
1419     [window.queue discardEventsMatchingMask:-1 forWindow:window];
1420     [window close];
1421     [window release];
1423     [pool release];
1426 /***********************************************************************
1427  *              macdrv_get_window_hwnd
1429  * Get the hwnd that was set for the window at creation.
1430  */
1431 void* macdrv_get_window_hwnd(macdrv_window w)
1433     WineWindow* window = (WineWindow*)w;
1434     return window.hwnd;
1437 /***********************************************************************
1438  *              macdrv_set_cocoa_window_features
1440  * Update a Cocoa window's features.
1441  */
1442 void macdrv_set_cocoa_window_features(macdrv_window w,
1443         const struct macdrv_window_features* wf)
1445     WineWindow* window = (WineWindow*)w;
1447     OnMainThread(^{
1448         [window setWindowFeatures:wf];
1449     });
1452 /***********************************************************************
1453  *              macdrv_set_cocoa_window_state
1455  * Update a Cocoa window's state.
1456  */
1457 void macdrv_set_cocoa_window_state(macdrv_window w,
1458         const struct macdrv_window_state* state)
1460     WineWindow* window = (WineWindow*)w;
1462     OnMainThread(^{
1463         [window setMacDrvState:state];
1464     });
1467 /***********************************************************************
1468  *              macdrv_set_cocoa_window_title
1470  * Set a Cocoa window's title.
1471  */
1472 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
1473         size_t length)
1475     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1476     WineWindow* window = (WineWindow*)w;
1477     NSString* titleString;
1479     if (title)
1480         titleString = [NSString stringWithCharacters:title length:length];
1481     else
1482         titleString = @"";
1483     OnMainThreadAsync(^{
1484         [window setTitle:titleString];
1485         if ([window isOrderedIn] && ![window isExcludedFromWindowsMenu])
1486             [NSApp changeWindowsItem:window title:titleString filename:NO];
1487     });
1489     [pool release];
1492 /***********************************************************************
1493  *              macdrv_order_cocoa_window
1495  * Reorder a Cocoa window relative to other windows.  If prev is
1496  * non-NULL, it is ordered below that window.  Else, if next is non-NULL,
1497  * it is ordered above that window.  Otherwise, it is ordered to the
1498  * front.
1500  * Returns true if the window has actually been ordered onto the screen
1501  * (i.e. if its frame intersects with a screen).  Otherwise, false.
1502  */
1503 int macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev,
1504         macdrv_window next)
1506     WineWindow* window = (WineWindow*)w;
1507     __block BOOL on_screen;
1509     OnMainThread(^{
1510         on_screen = [window orderBelow:(WineWindow*)prev
1511                                orAbove:(WineWindow*)next];
1512     });
1514     return on_screen;
1517 /***********************************************************************
1518  *              macdrv_hide_cocoa_window
1520  * Hides a Cocoa window.
1521  */
1522 void macdrv_hide_cocoa_window(macdrv_window w)
1524     WineWindow* window = (WineWindow*)w;
1526     OnMainThread(^{
1527         [window doOrderOut];
1528     });
1531 /***********************************************************************
1532  *              macdrv_set_cocoa_window_frame
1534  * Move a Cocoa window.  If the window has been moved out of the bounds
1535  * of the desktop, it is ordered out.  (This routine won't ever order a
1536  * window in, though.)
1538  * Returns true if the window is on screen; false otherwise.
1539  */
1540 int macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
1542     WineWindow* window = (WineWindow*)w;
1543     __block BOOL on_screen;
1545     OnMainThread(^{
1546         on_screen = [window setFrameIfOnScreen:NSRectFromCGRect(*new_frame)];
1547     });
1549     return on_screen;
1552 /***********************************************************************
1553  *              macdrv_get_cocoa_window_frame
1555  * Gets the frame of a Cocoa window.
1556  */
1557 void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
1559     WineWindow* window = (WineWindow*)w;
1561     OnMainThread(^{
1562         NSRect frame;
1564         frame = [window contentRectForFrameRect:[window frame]];
1565         [[WineApplicationController sharedController] flipRect:&frame];
1566         *out_frame = NSRectToCGRect(frame);
1567     });
1570 /***********************************************************************
1571  *              macdrv_set_cocoa_parent_window
1573  * Sets the parent window for a Cocoa window.  If parent is NULL, clears
1574  * the parent window.
1575  */
1576 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
1578     WineWindow* window = (WineWindow*)w;
1580     OnMainThread(^{
1581         [window setMacDrvParentWindow:(WineWindow*)parent];
1582     });
1585 /***********************************************************************
1586  *              macdrv_set_window_surface
1587  */
1588 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
1590     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1591     WineWindow* window = (WineWindow*)w;
1593     OnMainThread(^{
1594         window.surface = surface;
1595         window.surface_mutex = mutex;
1596     });
1598     [pool release];
1601 /***********************************************************************
1602  *              macdrv_window_needs_display
1604  * Mark a window as needing display in a specified rect (in non-client
1605  * area coordinates).
1606  */
1607 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
1609     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1610     WineWindow* window = (WineWindow*)w;
1612     OnMainThreadAsync(^{
1613         [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
1614     });
1616     [pool release];
1619 /***********************************************************************
1620  *              macdrv_set_window_shape
1622  * Sets the shape of a Cocoa window from an array of rectangles.  If
1623  * rects is NULL, resets the window's shape to its frame.
1624  */
1625 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
1627     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1628     WineWindow* window = (WineWindow*)w;
1630     OnMainThread(^{
1631         if (!rects || !count)
1632             window.shape = nil;
1633         else
1634         {
1635             NSBezierPath* path;
1636             unsigned int i;
1638             path = [NSBezierPath bezierPath];
1639             for (i = 0; i < count; i++)
1640                 [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
1641             window.shape = path;
1642         }
1643     });
1645     [pool release];
1648 /***********************************************************************
1649  *              macdrv_set_window_alpha
1650  */
1651 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
1653     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1654     WineWindow* window = (WineWindow*)w;
1656     [window setAlphaValue:alpha];
1658     [pool release];
1661 /***********************************************************************
1662  *              macdrv_set_window_color_key
1663  */
1664 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
1665                                  CGFloat keyBlue)
1667     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1668     WineWindow* window = (WineWindow*)w;
1670     OnMainThread(^{
1671         window.colorKeyed       = TRUE;
1672         window.colorKeyRed      = keyRed;
1673         window.colorKeyGreen    = keyGreen;
1674         window.colorKeyBlue     = keyBlue;
1675         [window checkTransparency];
1676     });
1678     [pool release];
1681 /***********************************************************************
1682  *              macdrv_clear_window_color_key
1683  */
1684 void macdrv_clear_window_color_key(macdrv_window w)
1686     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1687     WineWindow* window = (WineWindow*)w;
1689     OnMainThread(^{
1690         window.colorKeyed = FALSE;
1691         [window checkTransparency];
1692     });
1694     [pool release];
1697 /***********************************************************************
1698  *              macdrv_window_use_per_pixel_alpha
1699  */
1700 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
1702     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1703     WineWindow* window = (WineWindow*)w;
1705     OnMainThread(^{
1706         window.usePerPixelAlpha = use_per_pixel_alpha;
1707         [window checkTransparency];
1708     });
1710     [pool release];
1713 /***********************************************************************
1714  *              macdrv_give_cocoa_window_focus
1716  * Makes the Cocoa window "key" (gives it keyboard focus).  This also
1717  * orders it front and, if its frame was not within the desktop bounds,
1718  * Cocoa will typically move it on-screen.
1719  */
1720 void macdrv_give_cocoa_window_focus(macdrv_window w, int activate)
1722     WineWindow* window = (WineWindow*)w;
1724     OnMainThread(^{
1725         [window makeFocused:activate];
1726     });
1729 /***********************************************************************
1730  *              macdrv_create_view
1732  * Creates and returns a view in the specified rect of the window.  The
1733  * caller is responsible for calling macdrv_dispose_view() on the view
1734  * when it is done with it.
1735  */
1736 macdrv_view macdrv_create_view(macdrv_window w, CGRect rect)
1738     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1739     WineWindow* window = (WineWindow*)w;
1740     __block WineContentView* view;
1742     if (CGRectIsNull(rect)) rect = CGRectZero;
1744     OnMainThread(^{
1745         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1747         view = [[WineContentView alloc] initWithFrame:NSRectFromCGRect(rect)];
1748         [view setAutoresizesSubviews:NO];
1749         [nc addObserver:view
1750                selector:@selector(updateGLContexts)
1751                    name:NSViewGlobalFrameDidChangeNotification
1752                  object:view];
1753         [nc addObserver:view
1754                selector:@selector(updateGLContexts)
1755                    name:NSApplicationDidChangeScreenParametersNotification
1756                  object:NSApp];
1757         [[window contentView] addSubview:view];
1758     });
1760     [pool release];
1761     return (macdrv_view)view;
1764 /***********************************************************************
1765  *              macdrv_dispose_view
1767  * Destroys a view previously returned by macdrv_create_view.
1768  */
1769 void macdrv_dispose_view(macdrv_view v)
1771     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1772     WineContentView* view = (WineContentView*)v;
1774     OnMainThread(^{
1775         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1777         [nc removeObserver:view
1778                       name:NSViewGlobalFrameDidChangeNotification
1779                     object:view];
1780         [nc removeObserver:view
1781                       name:NSApplicationDidChangeScreenParametersNotification
1782                     object:NSApp];
1783         [view removeFromSuperview];
1784         [view release];
1785     });
1787     [pool release];
1790 /***********************************************************************
1791  *              macdrv_set_view_window_and_frame
1793  * Move a view to a new window and/or position within its window.  If w
1794  * is NULL, leave the view in its current window and just change its
1795  * frame.
1796  */
1797 void macdrv_set_view_window_and_frame(macdrv_view v, macdrv_window w, CGRect rect)
1799     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1800     WineContentView* view = (WineContentView*)v;
1801     WineWindow* window = (WineWindow*)w;
1803     if (CGRectIsNull(rect)) rect = CGRectZero;
1805     OnMainThread(^{
1806         BOOL changedWindow = (window && window != [view window]);
1807         NSRect newFrame = NSRectFromCGRect(rect);
1808         NSRect oldFrame = [view frame];
1810         if (changedWindow)
1811         {
1812             [view removeFromSuperview];
1813             [[window contentView] addSubview:view];
1814         }
1816         if (!NSEqualRects(oldFrame, newFrame))
1817         {
1818             if (!changedWindow)
1819                 [[view superview] setNeedsDisplayInRect:oldFrame];
1820             if (NSEqualPoints(oldFrame.origin, newFrame.origin))
1821                 [view setFrameSize:newFrame.size];
1822             else if (NSEqualSizes(oldFrame.size, newFrame.size))
1823                 [view setFrameOrigin:newFrame.origin];
1824             else
1825                 [view setFrame:newFrame];
1826             [view setNeedsDisplay:YES];
1827         }
1828     });
1830     [pool release];
1833 /***********************************************************************
1834  *              macdrv_add_view_opengl_context
1836  * Add an OpenGL context to the list being tracked for each view.
1837  */
1838 void macdrv_add_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
1840     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1841     WineContentView* view = (WineContentView*)v;
1842     WineOpenGLContext *context = (WineOpenGLContext*)c;
1844     OnMainThreadAsync(^{
1845         [view addGLContext:context];
1846     });
1848     [pool release];
1851 /***********************************************************************
1852  *              macdrv_remove_view_opengl_context
1854  * Add an OpenGL context to the list being tracked for each view.
1855  */
1856 void macdrv_remove_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
1858     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1859     WineContentView* view = (WineContentView*)v;
1860     WineOpenGLContext *context = (WineOpenGLContext*)c;
1862     OnMainThreadAsync(^{
1863         [view removeGLContext:context];
1864     });
1866     [pool release];
1869 /***********************************************************************
1870  *              macdrv_window_background_color
1872  * Returns the standard Mac window background color as a 32-bit value of
1873  * the form 0x00rrggbb.
1874  */
1875 uint32_t macdrv_window_background_color(void)
1877     static uint32_t result;
1878     static dispatch_once_t once;
1880     // Annoyingly, [NSColor windowBackgroundColor] refuses to convert to other
1881     // color spaces (RGB or grayscale).  So, the only way to get RGB values out
1882     // of it is to draw with it.
1883     dispatch_once(&once, ^{
1884         OnMainThread(^{
1885             unsigned char rgbx[4];
1886             unsigned char *planes = rgbx;
1887             NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&planes
1888                                                                                pixelsWide:1
1889                                                                                pixelsHigh:1
1890                                                                             bitsPerSample:8
1891                                                                           samplesPerPixel:3
1892                                                                                  hasAlpha:NO
1893                                                                                  isPlanar:NO
1894                                                                            colorSpaceName:NSCalibratedRGBColorSpace
1895                                                                              bitmapFormat:0
1896                                                                               bytesPerRow:4
1897                                                                              bitsPerPixel:32];
1898             [NSGraphicsContext saveGraphicsState];
1899             [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
1900             [[NSColor windowBackgroundColor] set];
1901             NSRectFill(NSMakeRect(0, 0, 1, 1));
1902             [NSGraphicsContext restoreGraphicsState];
1903             [bitmap release];
1904             result = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
1905         });
1906     });
1908     return result;
1911 /***********************************************************************
1912  *              macdrv_send_text_input_event
1913  */
1914 int macdrv_send_text_input_event(int pressed, unsigned int flags, int repeat, int keyc, void* data)
1916     __block BOOL ret;
1918     OnMainThread(^{
1919         WineWindow* window = (WineWindow*)[NSApp keyWindow];
1920         if (![window isKindOfClass:[WineWindow class]])
1921         {
1922             window = (WineWindow*)[NSApp mainWindow];
1923             if (![window isKindOfClass:[WineWindow class]])
1924                 window = [[WineApplicationController sharedController] frontWineWindow];
1925         }
1927         if (window)
1928         {
1929             NSUInteger localFlags = flags;
1930             CGEventRef c;
1931             NSEvent* event;
1933             window.imeData = data;
1934             fix_device_modifiers_by_generic(&localFlags);
1936             // An NSEvent created with +keyEventWithType:... is internally marked
1937             // as synthetic and doesn't get sent through input methods.  But one
1938             // created from a CGEvent doesn't have that problem.
1939             c = CGEventCreateKeyboardEvent(NULL, keyc, pressed);
1940             CGEventSetFlags(c, localFlags);
1941             CGEventSetIntegerValueField(c, kCGKeyboardEventAutorepeat, repeat);
1942             event = [NSEvent eventWithCGEvent:c];
1943             CFRelease(c);
1945             window.commandDone = FALSE;
1946             ret = [[[window contentView] inputContext] handleEvent:event] && !window.commandDone;
1947         }
1948         else
1949             ret = FALSE;
1950     });
1952     return ret;