TESTING -- override pthreads to fix gstreamer v5
[wine/multimedia.git] / dlls / winemac.drv / cocoa_window.m
blobbcc1cf527faae1f96838978bd1af181202fd9303
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>
22 #import <CoreVideo/CoreVideo.h>
24 #import "cocoa_window.h"
26 #include "macdrv_cocoa.h"
27 #import "cocoa_app.h"
28 #import "cocoa_event.h"
29 #import "cocoa_opengl.h"
32 #if !defined(MAC_OS_X_VERSION_10_7) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
33 enum {
34     NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7,
35     NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8,
36     NSWindowFullScreenButton = 7,
37     NSFullScreenWindowMask = 1 << 14,
40 @interface NSWindow (WineFullScreenExtensions)
41     - (void) toggleFullScreen:(id)sender;
42 @end
43 #endif
46 /* Additional Mac virtual keycode, to complement those in Carbon's <HIToolbox/Events.h>. */
47 enum {
48     kVK_RightCommand              = 0x36, /* Invented for Wine; was unused */
52 static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf)
54     NSUInteger style_mask;
56     if (wf->title_bar)
57     {
58         style_mask = NSTitledWindowMask;
59         if (wf->close_button) style_mask |= NSClosableWindowMask;
60         if (wf->minimize_button) style_mask |= NSMiniaturizableWindowMask;
61         if (wf->resizable || wf->maximize_button) style_mask |= NSResizableWindowMask;
62         if (wf->utility) style_mask |= NSUtilityWindowMask;
63     }
64     else style_mask = NSBorderlessWindowMask;
66     return style_mask;
70 static BOOL frame_intersects_screens(NSRect frame, NSArray* screens)
72     NSScreen* screen;
73     for (screen in screens)
74     {
75         if (NSIntersectsRect(frame, [screen frame]))
76             return TRUE;
77     }
78     return FALSE;
82 static NSScreen* screen_covered_by_rect(NSRect rect, NSArray* screens)
84     for (NSScreen* screen in screens)
85     {
86         if (NSContainsRect(rect, [screen frame]))
87             return screen;
88     }
89     return nil;
93 /* We rely on the supposedly device-dependent modifier flags to distinguish the
94    keys on the left side of the keyboard from those on the right.  Some event
95    sources don't set those device-depdendent flags.  If we see a device-independent
96    flag for a modifier without either corresponding device-dependent flag, assume
97    the left one. */
98 static inline void fix_device_modifiers_by_generic(NSUInteger* modifiers)
100     if ((*modifiers & (NX_COMMANDMASK | NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) == NX_COMMANDMASK)
101         *modifiers |= NX_DEVICELCMDKEYMASK;
102     if ((*modifiers & (NX_SHIFTMASK | NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) == NX_SHIFTMASK)
103         *modifiers |= NX_DEVICELSHIFTKEYMASK;
104     if ((*modifiers & (NX_CONTROLMASK | NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) == NX_CONTROLMASK)
105         *modifiers |= NX_DEVICELCTLKEYMASK;
106     if ((*modifiers & (NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) == NX_ALTERNATEMASK)
107         *modifiers |= NX_DEVICELALTKEYMASK;
110 /* As we manipulate individual bits of a modifier mask, we can end up with
111    inconsistent sets of flags.  In particular, we might set or clear one of the
112    left/right-specific bits, but not the corresponding non-side-specific bit.
113    Fix that.  If either side-specific bit is set, set the non-side-specific bit,
114    otherwise clear it. */
115 static inline void fix_generic_modifiers_by_device(NSUInteger* modifiers)
117     if (*modifiers & (NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK))
118         *modifiers |= NX_COMMANDMASK;
119     else
120         *modifiers &= ~NX_COMMANDMASK;
121     if (*modifiers & (NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK))
122         *modifiers |= NX_SHIFTMASK;
123     else
124         *modifiers &= ~NX_SHIFTMASK;
125     if (*modifiers & (NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK))
126         *modifiers |= NX_CONTROLMASK;
127     else
128         *modifiers &= ~NX_CONTROLMASK;
129     if (*modifiers & (NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK))
130         *modifiers |= NX_ALTERNATEMASK;
131     else
132         *modifiers &= ~NX_ALTERNATEMASK;
135 static inline NSUInteger adjusted_modifiers_for_option_behavior(NSUInteger modifiers)
137     fix_device_modifiers_by_generic(&modifiers);
138     if (left_option_is_alt && (modifiers & NX_DEVICELALTKEYMASK))
139     {
140         modifiers |= NX_DEVICELCMDKEYMASK;
141         modifiers &= ~NX_DEVICELALTKEYMASK;
142     }
143     if (right_option_is_alt && (modifiers & NX_DEVICERALTKEYMASK))
144     {
145         modifiers |= NX_DEVICERCMDKEYMASK;
146         modifiers &= ~NX_DEVICERALTKEYMASK;
147     }
148     fix_generic_modifiers_by_device(&modifiers);
150     return modifiers;
154 @interface NSWindow (WineAccessPrivateMethods)
155     - (id) _displayChanged;
156 @end
159 @interface WineDisplayLink : NSObject
161     CGDirectDisplayID _displayID;
162     CVDisplayLinkRef _link;
163     NSMutableSet* _windows;
165     NSTimeInterval _actualRefreshPeriod;
166     NSTimeInterval _nominalRefreshPeriod;
169     - (id) initWithDisplayID:(CGDirectDisplayID)displayID;
171     - (void) addWindow:(WineWindow*)window;
172     - (void) removeWindow:(WineWindow*)window;
174     - (NSTimeInterval) refreshPeriod;
176     - (void) start;
178 @end
180 @implementation WineDisplayLink
182 static CVReturn WineDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* inNow, const CVTimeStamp* inOutputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext);
184     - (id) initWithDisplayID:(CGDirectDisplayID)displayID
185     {
186         self = [super init];
187         if (self)
188         {
189             CVReturn status = CVDisplayLinkCreateWithCGDisplay(displayID, &_link);
190             if (status == kCVReturnSuccess && !_link)
191                 status = kCVReturnError;
192             if (status == kCVReturnSuccess)
193                 status = CVDisplayLinkSetOutputCallback(_link, WineDisplayLinkCallback, self);
194             if (status != kCVReturnSuccess)
195             {
196                 [self release];
197                 return nil;
198             }
200             _displayID = displayID;
201             _windows = [[NSMutableSet alloc] init];
202         }
203         return self;
204     }
206     - (void) dealloc
207     {
208         if (_link)
209         {
210             CVDisplayLinkStop(_link);
211             CVDisplayLinkRelease(_link);
212         }
213         [_windows release];
214         [super dealloc];
215     }
217     - (void) addWindow:(WineWindow*)window
218     {
219         @synchronized(self) {
220             BOOL needsStart = !_windows.count;
221             [_windows addObject:window];
222             if (needsStart)
223                 CVDisplayLinkStart(_link);
224         }
225     }
227     - (void) removeWindow:(WineWindow*)window
228     {
229         @synchronized(self) {
230             BOOL wasRunning = _windows.count > 0;
231             [_windows removeObject:window];
232             if (wasRunning && !_windows.count)
233                 CVDisplayLinkStop(_link);
234         }
235     }
237     - (void) fire
238     {
239         NSSet* windows;
240         @synchronized(self) {
241             windows = [_windows copy];
242         }
243         dispatch_async(dispatch_get_main_queue(), ^{
244             BOOL anyDisplayed = FALSE;
245             for (WineWindow* window in windows)
246             {
247                 if ([window viewsNeedDisplay])
248                 {
249                     [window displayIfNeeded];
250                     anyDisplayed = YES;
251                 }
252             }
253             if (!anyDisplayed)
254                 CVDisplayLinkStop(_link);
255         });
256         [windows release];
257     }
259     - (NSTimeInterval) refreshPeriod
260     {
261         if (_actualRefreshPeriod || (_actualRefreshPeriod = CVDisplayLinkGetActualOutputVideoRefreshPeriod(_link)))
262             return _actualRefreshPeriod;
264         if (_nominalRefreshPeriod)
265             return _nominalRefreshPeriod;
267         CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(_link);
268         if (time.flags & kCVTimeIsIndefinite)
269             return 1.0 / 60.0;
270         _nominalRefreshPeriod = time.timeValue / (double)time.timeScale;
271         return _nominalRefreshPeriod;
272     }
274     - (void) start
275     {
276         CVDisplayLinkStart(_link);
277     }
279 static CVReturn WineDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* inNow, const CVTimeStamp* inOutputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
281     WineDisplayLink* link = displayLinkContext;
282     [link fire];
283     return kCVReturnSuccess;
286 @end
289 @interface WineContentView : NSView <NSTextInputClient>
291     NSMutableArray* glContexts;
292     NSMutableArray* pendingGlContexts;
293     BOOL clearedGlSurface;
295     NSMutableAttributedString* markedText;
296     NSRange markedTextSelection;
299     - (void) addGLContext:(WineOpenGLContext*)context;
300     - (void) removeGLContext:(WineOpenGLContext*)context;
301     - (void) updateGLContexts;
303 @end
306 @interface WineWindow ()
308 @property (readwrite, nonatomic) BOOL disabled;
309 @property (readwrite, nonatomic) BOOL noActivate;
310 @property (readwrite, nonatomic) BOOL floating;
311 @property (readwrite, getter=isFakingClose, nonatomic) BOOL fakingClose;
312 @property (retain, nonatomic) NSWindow* latentParentWindow;
314 @property (nonatomic) void* hwnd;
315 @property (retain, readwrite, nonatomic) WineEventQueue* queue;
317 @property (nonatomic) void* surface;
318 @property (nonatomic) pthread_mutex_t* surface_mutex;
320 @property (copy, nonatomic) NSBezierPath* shape;
321 @property (copy, nonatomic) NSData* shapeData;
322 @property (nonatomic) BOOL shapeChangedSinceLastDraw;
323 @property (readonly, nonatomic) BOOL needsTransparency;
325 @property (nonatomic) BOOL colorKeyed;
326 @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
327 @property (nonatomic) BOOL usePerPixelAlpha;
329 @property (assign, nonatomic) void* imeData;
330 @property (nonatomic) BOOL commandDone;
332 @property (readonly, copy, nonatomic) NSArray* childWineWindows;
334     - (void) updateColorSpace;
335     - (void) updateForGLSubviews;
337     - (BOOL) becameEligibleParentOrChild;
338     - (void) becameIneligibleChild;
340 @end
343 @implementation WineContentView
345     - (void) dealloc
346     {
347         [markedText release];
348         [glContexts release];
349         [pendingGlContexts release];
350         [super dealloc];
351     }
353     - (BOOL) isFlipped
354     {
355         return YES;
356     }
358     - (void) drawRect:(NSRect)rect
359     {
360         WineWindow* window = (WineWindow*)[self window];
362         for (WineOpenGLContext* context in pendingGlContexts)
363         {
364             if (!clearedGlSurface)
365             {
366                 context.shouldClearToBlack = TRUE;
367                 clearedGlSurface = TRUE;
368             }
369             context.needsUpdate = TRUE;
370         }
371         [glContexts addObjectsFromArray:pendingGlContexts];
372         [pendingGlContexts removeAllObjects];
374         if ([window contentView] != self)
375             return;
377         if (window.shapeChangedSinceLastDraw && window.shape && !window.colorKeyed && !window.usePerPixelAlpha)
378         {
379             [[NSColor clearColor] setFill];
380             NSRectFill(rect);
382             [window.shape addClip];
384             [[NSColor windowBackgroundColor] setFill];
385             NSRectFill(rect);
386         }
388         if (window.surface && window.surface_mutex &&
389             !pthread_mutex_lock(window.surface_mutex))
390         {
391             const CGRect* rects;
392             int count;
394             if (get_surface_blit_rects(window.surface, &rects, &count) && count)
395             {
396                 CGContextRef context;
397                 int i;
399                 [window.shape addClip];
401                 context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
402                 CGContextSetBlendMode(context, kCGBlendModeCopy);
403                 CGContextSetInterpolationQuality(context, kCGInterpolationNone);
405                 for (i = 0; i < count; i++)
406                 {
407                     CGRect imageRect;
408                     CGImageRef image;
410                     imageRect = CGRectIntersection(rects[i], NSRectToCGRect(rect));
411                     image = create_surface_image(window.surface, &imageRect, FALSE);
413                     if (image)
414                     {
415                         if (window.colorKeyed)
416                         {
417                             CGImageRef maskedImage;
418                             CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5,
419                                                      window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5,
420                                                      window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 };
421                             maskedImage = CGImageCreateWithMaskingColors(image, components);
422                             if (maskedImage)
423                             {
424                                 CGImageRelease(image);
425                                 image = maskedImage;
426                             }
427                         }
429                         CGContextDrawImage(context, imageRect, image);
431                         CGImageRelease(image);
432                     }
433                 }
434             }
436             pthread_mutex_unlock(window.surface_mutex);
437         }
439         // If the window may be transparent, then we have to invalidate the
440         // shadow every time we draw.  Also, if this is the first time we've
441         // drawn since changing from transparent to opaque.
442         if (window.colorKeyed || window.usePerPixelAlpha || window.shapeChangedSinceLastDraw)
443         {
444             window.shapeChangedSinceLastDraw = FALSE;
445             [window invalidateShadow];
446         }
447     }
449     - (void) addGLContext:(WineOpenGLContext*)context
450     {
451         if (!glContexts)
452             glContexts = [[NSMutableArray alloc] init];
453         if (!pendingGlContexts)
454             pendingGlContexts = [[NSMutableArray alloc] init];
456         if ([[self window] windowNumber] > 0 && !NSIsEmptyRect([self visibleRect]))
457         {
458             [glContexts addObject:context];
459             if (!clearedGlSurface)
460             {
461                 context.shouldClearToBlack = TRUE;
462                 clearedGlSurface = TRUE;
463             }
464             context.needsUpdate = TRUE;
465         }
466         else
467         {
468             [pendingGlContexts addObject:context];
469             [self setNeedsDisplay:YES];
470         }
472         [(WineWindow*)[self window] updateForGLSubviews];
473     }
475     - (void) removeGLContext:(WineOpenGLContext*)context
476     {
477         [glContexts removeObjectIdenticalTo:context];
478         [pendingGlContexts removeObjectIdenticalTo:context];
479         [(WineWindow*)[self window] updateForGLSubviews];
480     }
482     - (void) updateGLContexts
483     {
484         for (WineOpenGLContext* context in glContexts)
485             context.needsUpdate = TRUE;
486     }
488     - (BOOL) hasGLContext
489     {
490         return [glContexts count] || [pendingGlContexts count];
491     }
493     - (BOOL) acceptsFirstMouse:(NSEvent*)theEvent
494     {
495         return YES;
496     }
498     - (BOOL) preservesContentDuringLiveResize
499     {
500         // Returning YES from this tells Cocoa to keep our view's content during
501         // a Cocoa-driven resize.  In theory, we're also supposed to override
502         // -setFrameSize: to mark exposed sections as needing redisplay, but
503         // user32 will take care of that in a roundabout way.  This way, we don't
504         // redraw until the window surface is flushed.
505         //
506         // This doesn't do anything when we resize the window ourselves.
507         return YES;
508     }
510     - (BOOL)acceptsFirstResponder
511     {
512         return [[self window] contentView] == self;
513     }
515     - (BOOL) mouseDownCanMoveWindow
516     {
517         return NO;
518     }
520     - (void) completeText:(NSString*)text
521     {
522         macdrv_event* event;
523         WineWindow* window = (WineWindow*)[self window];
525         event = macdrv_create_event(IM_SET_TEXT, window);
526         event->im_set_text.data = [window imeData];
527         event->im_set_text.text = (CFStringRef)[text copy];
528         event->im_set_text.complete = TRUE;
530         [[window queue] postEvent:event];
532         macdrv_release_event(event);
534         [markedText deleteCharactersInRange:NSMakeRange(0, [markedText length])];
535         markedTextSelection = NSMakeRange(0, 0);
536         [[self inputContext] discardMarkedText];
537     }
539     - (NSFocusRingType) focusRingType
540     {
541         return NSFocusRingTypeNone;
542     }
544     /*
545      * ---------- NSTextInputClient methods ----------
546      */
547     - (NSTextInputContext*) inputContext
548     {
549         if (!markedText)
550             markedText = [[NSMutableAttributedString alloc] init];
551         return [super inputContext];
552     }
554     - (void) insertText:(id)string replacementRange:(NSRange)replacementRange
555     {
556         if ([string isKindOfClass:[NSAttributedString class]])
557             string = [string string];
559         if ([string isKindOfClass:[NSString class]])
560             [self completeText:string];
561     }
563     - (void) doCommandBySelector:(SEL)aSelector
564     {
565         [(WineWindow*)[self window] setCommandDone:TRUE];
566     }
568     - (void) setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
569     {
570         if ([string isKindOfClass:[NSAttributedString class]])
571             string = [string string];
573         if ([string isKindOfClass:[NSString class]])
574         {
575             macdrv_event* event;
576             WineWindow* window = (WineWindow*)[self window];
578             if (replacementRange.location == NSNotFound)
579                 replacementRange = NSMakeRange(0, [markedText length]);
581             [markedText replaceCharactersInRange:replacementRange withString:string];
582             markedTextSelection = selectedRange;
583             markedTextSelection.location += replacementRange.location;
585             event = macdrv_create_event(IM_SET_TEXT, window);
586             event->im_set_text.data = [window imeData];
587             event->im_set_text.text = (CFStringRef)[[markedText string] copy];
588             event->im_set_text.complete = FALSE;
589             event->im_set_text.cursor_pos = markedTextSelection.location + markedTextSelection.length;
591             [[window queue] postEvent:event];
593             macdrv_release_event(event);
595             [[self inputContext] invalidateCharacterCoordinates];
596         }
597     }
599     - (void) unmarkText
600     {
601         [self completeText:nil];
602     }
604     - (NSRange) selectedRange
605     {
606         return markedTextSelection;
607     }
609     - (NSRange) markedRange
610     {
611         NSRange range = NSMakeRange(0, [markedText length]);
612         if (!range.length)
613             range.location = NSNotFound;
614         return range;
615     }
617     - (BOOL) hasMarkedText
618     {
619         return [markedText length] > 0;
620     }
622     - (NSAttributedString*) attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
623     {
624         if (aRange.location >= [markedText length])
625             return nil;
627         aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
628         if (actualRange)
629             *actualRange = aRange;
630         return [markedText attributedSubstringFromRange:aRange];
631     }
633     - (NSArray*) validAttributesForMarkedText
634     {
635         return [NSArray array];
636     }
638     - (NSRect) firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
639     {
640         macdrv_query* query;
641         WineWindow* window = (WineWindow*)[self window];
642         NSRect ret;
644         aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
646         query = macdrv_create_query();
647         query->type = QUERY_IME_CHAR_RECT;
648         query->window = (macdrv_window)[window retain];
649         query->ime_char_rect.data = [window imeData];
650         query->ime_char_rect.range = CFRangeMake(aRange.location, aRange.length);
652         if ([window.queue query:query timeout:1])
653         {
654             aRange = NSMakeRange(query->ime_char_rect.range.location, query->ime_char_rect.range.length);
655             ret = NSRectFromCGRect(query->ime_char_rect.rect);
656             [[WineApplicationController sharedController] flipRect:&ret];
657         }
658         else
659             ret = NSMakeRect(100, 100, aRange.length ? 1 : 0, 12);
661         macdrv_release_query(query);
663         if (actualRange)
664             *actualRange = aRange;
665         return ret;
666     }
668     - (NSUInteger) characterIndexForPoint:(NSPoint)aPoint
669     {
670         return NSNotFound;
671     }
673     - (NSInteger) windowLevel
674     {
675         return [[self window] level];
676     }
678 @end
681 @implementation WineWindow
683     static WineWindow* causing_becomeKeyWindow;
685     @synthesize disabled, noActivate, floating, fullscreen, fakingClose, latentParentWindow, hwnd, queue;
686     @synthesize surface, surface_mutex;
687     @synthesize shape, shapeData, shapeChangedSinceLastDraw;
688     @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
689     @synthesize usePerPixelAlpha;
690     @synthesize imeData, commandDone;
692     + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
693                                  windowFrame:(NSRect)window_frame
694                                         hwnd:(void*)hwnd
695                                        queue:(WineEventQueue*)queue
696     {
697         WineWindow* window;
698         WineContentView* contentView;
699         NSTrackingArea* trackingArea;
700         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
702         [[WineApplicationController sharedController] flipRect:&window_frame];
704         window = [[[self alloc] initWithContentRect:window_frame
705                                           styleMask:style_mask_for_features(wf)
706                                             backing:NSBackingStoreBuffered
707                                               defer:YES] autorelease];
709         if (!window) return nil;
711         /* Standardize windows to eliminate differences between titled and
712            borderless windows and between NSWindow and NSPanel. */
713         [window setHidesOnDeactivate:NO];
714         [window setReleasedWhenClosed:NO];
716         [window setOneShot:YES];
717         [window disableCursorRects];
718         [window setShowsResizeIndicator:NO];
719         [window setHasShadow:wf->shadow];
720         [window setAcceptsMouseMovedEvents:YES];
721         [window setColorSpace:[NSColorSpace genericRGBColorSpace]];
722         [window setDelegate:window];
723         [window setAutodisplay:NO];
724         window.hwnd = hwnd;
725         window.queue = queue;
726         window->savedContentMinSize = NSZeroSize;
727         window->savedContentMaxSize = NSMakeSize(FLT_MAX, FLT_MAX);
728         window->resizable = wf->resizable;
729         window->_lastDisplayTime = [[NSDate distantPast] timeIntervalSinceReferenceDate];
731         [window registerForDraggedTypes:[NSArray arrayWithObjects:(NSString*)kUTTypeData,
732                                                                   (NSString*)kUTTypeContent,
733                                                                   nil]];
735         contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
736         if (!contentView)
737             return nil;
738         [contentView setAutoresizesSubviews:NO];
740         /* We use tracking areas in addition to setAcceptsMouseMovedEvents:YES
741            because they give us mouse moves in the background. */
742         trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds]
743                                                      options:(NSTrackingMouseMoved |
744                                                               NSTrackingActiveAlways |
745                                                               NSTrackingInVisibleRect)
746                                                        owner:window
747                                                     userInfo:nil] autorelease];
748         if (!trackingArea)
749             return nil;
750         [contentView addTrackingArea:trackingArea];
752         [window setContentView:contentView];
753         [window setInitialFirstResponder:contentView];
755         [nc addObserver:window
756                selector:@selector(updateFullscreen)
757                    name:NSApplicationDidChangeScreenParametersNotification
758                  object:NSApp];
759         [window updateFullscreen];
761         [nc addObserver:window
762                selector:@selector(applicationWillHide)
763                    name:NSApplicationWillHideNotification
764                  object:NSApp];
765         [nc addObserver:window
766                selector:@selector(applicationDidUnhide)
767                    name:NSApplicationDidUnhideNotification
768                  object:NSApp];
770         [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:window
771                                                               selector:@selector(checkWineDisplayLink)
772                                                                   name:NSWorkspaceActiveSpaceDidChangeNotification
773                                                                 object:[NSWorkspace sharedWorkspace]];
775         return window;
776     }
778     - (void) dealloc
779     {
780         [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
781         [[NSNotificationCenter defaultCenter] removeObserver:self];
782         [queue release];
783         [latentChildWindows release];
784         [latentParentWindow release];
785         [shape release];
786         [shapeData release];
787         [super dealloc];
788     }
790     - (BOOL) preventResizing
791     {
792         BOOL preventForClipping = cursor_clipping_locks_windows && [[WineApplicationController sharedController] clippingCursor];
793         return ([self styleMask] & NSResizableWindowMask) && (disabled || !resizable || preventForClipping);
794     }
796     - (BOOL) allowsMovingWithMaximized:(BOOL)inMaximized
797     {
798         if (allow_immovable_windows && (disabled || inMaximized))
799             return NO;
800         else if (cursor_clipping_locks_windows && [[WineApplicationController sharedController] clippingCursor])
801             return NO;
802         else
803             return YES;
804     }
806     - (void) adjustFeaturesForState
807     {
808         NSUInteger style = [self styleMask];
810         if (style & NSClosableWindowMask)
811             [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
812         if (style & NSMiniaturizableWindowMask)
813             [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
814         if (style & NSResizableWindowMask)
815             [[self standardWindowButton:NSWindowZoomButton] setEnabled:!self.disabled];
816         if ([self respondsToSelector:@selector(toggleFullScreen:)])
817         {
818             if ([self collectionBehavior] & NSWindowCollectionBehaviorFullScreenPrimary)
819                 [[self standardWindowButton:NSWindowFullScreenButton] setEnabled:!self.disabled];
820         }
822         if ([self preventResizing])
823         {
824             NSSize size = [self contentRectForFrameRect:[self frame]].size;
825             [self setContentMinSize:size];
826             [self setContentMaxSize:size];
827         }
828         else
829         {
830             [self setContentMaxSize:savedContentMaxSize];
831             [self setContentMinSize:savedContentMinSize];
832         }
834         if (allow_immovable_windows || cursor_clipping_locks_windows)
835             [self setMovable:[self allowsMovingWithMaximized:maximized]];
836     }
838     - (void) adjustFullScreenBehavior:(NSWindowCollectionBehavior)behavior
839     {
840         if ([self respondsToSelector:@selector(toggleFullScreen:)])
841         {
842             NSUInteger style = [self styleMask];
844             if (behavior & NSWindowCollectionBehaviorParticipatesInCycle &&
845                 style & NSResizableWindowMask && !(style & NSUtilityWindowMask) && !maximized)
846             {
847                 behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
848                 behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary;
849             }
850             else
851             {
852                 behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
853                 behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary;
854                 if (style & NSFullScreenWindowMask)
855                     [super toggleFullScreen:nil];
856             }
857         }
859         if (behavior != [self collectionBehavior])
860         {
861             [self setCollectionBehavior:behavior];
862             [self adjustFeaturesForState];
863         }
864     }
866     - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
867     {
868         static const NSUInteger usedStyles = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask |
869                                              NSResizableWindowMask | NSUtilityWindowMask | NSBorderlessWindowMask;
870         NSUInteger currentStyle = [self styleMask];
871         NSUInteger newStyle = style_mask_for_features(wf) | (currentStyle & ~usedStyles);
873         if (newStyle != currentStyle)
874         {
875             NSString* title = [[[self title] copy] autorelease];
876             BOOL showingButtons = (currentStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
877             BOOL shouldShowButtons = (newStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
878             if (shouldShowButtons != showingButtons && !((newStyle ^ currentStyle) & NSClosableWindowMask))
879             {
880                 // -setStyleMask: is buggy on 10.7+ with respect to NSResizableWindowMask.
881                 // If transitioning from NSTitledWindowMask | NSResizableWindowMask to
882                 // just NSTitledWindowMask, the window buttons should disappear rather
883                 // than just being disabled.  But they don't.  Similarly in reverse.
884                 // The workaround is to also toggle NSClosableWindowMask at the same time.
885                 [self setStyleMask:newStyle ^ NSClosableWindowMask];
886             }
887             [self setStyleMask:newStyle];
889             // -setStyleMask: resets the firstResponder to the window.  Set it
890             // back to the content view.
891             if ([[self contentView] acceptsFirstResponder])
892                 [self makeFirstResponder:[self contentView]];
894             [self adjustFullScreenBehavior:[self collectionBehavior]];
896             if ([[self title] length] == 0 && [title length] > 0)
897                 [self setTitle:title];
898         }
900         resizable = wf->resizable;
901         [self adjustFeaturesForState];
902         [self setHasShadow:wf->shadow];
903     }
905     // Indicates if the window would be visible if the app were not hidden.
906     - (BOOL) wouldBeVisible
907     {
908         return [NSApp isHidden] ? savedVisibleState : [self isVisible];
909     }
911     - (BOOL) isOrderedIn
912     {
913         return [self wouldBeVisible] || [self isMiniaturized];
914     }
916     - (NSInteger) minimumLevelForActive:(BOOL)active
917     {
918         NSInteger level;
920         if (self.floating && (active || topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_ALL ||
921                               (topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_NONFULLSCREEN && !fullscreen)))
922             level = NSFloatingWindowLevel;
923         else
924             level = NSNormalWindowLevel;
926         if (active)
927         {
928             BOOL captured;
930             captured = (fullscreen || [self screen]) && [[WineApplicationController sharedController] areDisplaysCaptured];
932             if (captured || fullscreen)
933             {
934                 if (captured)
935                     level = CGShieldingWindowLevel() + 1; /* Need +1 or we don't get mouse moves */
936                 else
937                     level = NSStatusWindowLevel + 1;
939                 if (self.floating)
940                     level++;
941             }
942         }
944         return level;
945     }
947     - (void) postDidUnminimizeEvent
948     {
949         macdrv_event* event;
951         /* Coalesce events by discarding any previous ones still in the queue. */
952         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_UNMINIMIZE)
953                                forWindow:self];
955         event = macdrv_create_event(WINDOW_DID_UNMINIMIZE, self);
956         [queue postEvent:event];
957         macdrv_release_event(event);
958     }
960     - (void) sendResizeStartQuery
961     {
962         macdrv_query* query = macdrv_create_query();
963         query->type = QUERY_RESIZE_START;
964         query->window = (macdrv_window)[self retain];
966         [self.queue query:query timeout:0.3];
967         macdrv_release_query(query);
968     }
970     - (void) setMacDrvState:(const struct macdrv_window_state*)state
971     {
972         NSWindowCollectionBehavior behavior;
974         self.disabled = state->disabled;
975         self.noActivate = state->no_activate;
977         if (self.floating != state->floating)
978         {
979             self.floating = state->floating;
980             if (state->floating)
981             {
982                 // Became floating.  If child of non-floating window, make that
983                 // relationship latent.
984                 WineWindow* parent = (WineWindow*)[self parentWindow];
985                 if (parent && !parent.floating)
986                     [self becameIneligibleChild];
987             }
988             else
989             {
990                 // Became non-floating.  If parent of floating children, make that
991                 // relationship latent.
992                 WineWindow* child;
993                 for (child in [self childWineWindows])
994                 {
995                     if (child.floating)
996                         [child becameIneligibleChild];
997                 }
998             }
1000             // Check our latent relationships.  If floating status was the only
1001             // reason they were latent, then make them active.
1002             if ([self isVisible])
1003                 [self becameEligibleParentOrChild];
1005             [[WineApplicationController sharedController] adjustWindowLevels];
1006         }
1008         if (state->minimized_valid)
1009         {
1010             macdrv_event_mask discard = event_mask_for_type(WINDOW_DID_UNMINIMIZE);
1012             pendingMinimize = FALSE;
1013             if (state->minimized && ![self isMiniaturized])
1014             {
1015                 if ([self wouldBeVisible])
1016                 {
1017                     if ([self styleMask] & NSFullScreenWindowMask)
1018                     {
1019                         [self postDidUnminimizeEvent];
1020                         discard &= ~event_mask_for_type(WINDOW_DID_UNMINIMIZE);
1021                     }
1022                     else
1023                     {
1024                         [super miniaturize:nil];
1025                         discard |= event_mask_for_type(WINDOW_BROUGHT_FORWARD) |
1026                                    event_mask_for_type(WINDOW_GOT_FOCUS) |
1027                                    event_mask_for_type(WINDOW_LOST_FOCUS);
1028                     }
1029                 }
1030                 else
1031                     pendingMinimize = TRUE;
1032             }
1033             else if (!state->minimized && [self isMiniaturized])
1034             {
1035                 ignore_windowDeminiaturize = TRUE;
1036                 [self deminiaturize:nil];
1037                 discard |= event_mask_for_type(WINDOW_LOST_FOCUS);
1038             }
1040             if (discard)
1041                 [queue discardEventsMatchingMask:discard forWindow:self];
1042         }
1044         if (state->maximized != maximized)
1045         {
1046             maximized = state->maximized;
1047             [self adjustFeaturesForState];
1049             if (!maximized && [self inLiveResize])
1050                 [self sendResizeStartQuery];
1051         }
1053         behavior = NSWindowCollectionBehaviorDefault;
1054         if (state->excluded_by_expose)
1055             behavior |= NSWindowCollectionBehaviorTransient;
1056         else
1057             behavior |= NSWindowCollectionBehaviorManaged;
1058         if (state->excluded_by_cycle)
1059         {
1060             behavior |= NSWindowCollectionBehaviorIgnoresCycle;
1061             if ([self isOrderedIn])
1062                 [NSApp removeWindowsItem:self];
1063         }
1064         else
1065         {
1066             behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
1067             if ([self isOrderedIn])
1068                 [NSApp addWindowsItem:self title:[self title] filename:NO];
1069         }
1070         [self adjustFullScreenBehavior:behavior];
1071     }
1073     - (BOOL) addChildWineWindow:(WineWindow*)child assumeVisible:(BOOL)assumeVisible
1074     {
1075         BOOL reordered = FALSE;
1077         if ([self isVisible] && (assumeVisible || [child isVisible]) && (self.floating || !child.floating))
1078         {
1079             if ([self level] > [child level])
1080                 [child setLevel:[self level]];
1081             [self addChildWindow:child ordered:NSWindowAbove];
1082             [child checkWineDisplayLink];
1083             [latentChildWindows removeObjectIdenticalTo:child];
1084             child.latentParentWindow = nil;
1085             reordered = TRUE;
1086         }
1087         else
1088         {
1089             if (!latentChildWindows)
1090                 latentChildWindows = [[NSMutableArray alloc] init];
1091             if (![latentChildWindows containsObject:child])
1092                 [latentChildWindows addObject:child];
1093             child.latentParentWindow = self;
1094         }
1096         return reordered;
1097     }
1099     - (BOOL) addChildWineWindow:(WineWindow*)child
1100     {
1101         return [self addChildWineWindow:child assumeVisible:FALSE];
1102     }
1104     - (void) removeChildWineWindow:(WineWindow*)child
1105     {
1106         [self removeChildWindow:child];
1107         if (child.latentParentWindow == self)
1108             child.latentParentWindow = nil;
1109         [latentChildWindows removeObjectIdenticalTo:child];
1110     }
1112     - (BOOL) becameEligibleParentOrChild
1113     {
1114         BOOL reordered = FALSE;
1115         NSUInteger count;
1117         if (latentParentWindow.floating || !self.floating)
1118         {
1119             // If we aren't visible currently, we assume that we should be and soon
1120             // will be.  So, if the latent parent is visible that's enough to assume
1121             // we can establish the parent-child relationship in Cocoa.  That will
1122             // actually make us visible, which is fine.
1123             if ([latentParentWindow addChildWineWindow:self assumeVisible:TRUE])
1124                 reordered = TRUE;
1125         }
1127         // Here, though, we may not actually be visible yet and adding a child
1128         // won't make us visible.  The caller will have to call this method
1129         // again after actually making us visible.
1130         if ([self isVisible] && (count = [latentChildWindows count]))
1131         {
1132             NSMutableIndexSet* indexesToRemove = [NSMutableIndexSet indexSet];
1133             NSUInteger i;
1135             for (i = 0; i < count; i++)
1136             {
1137                 WineWindow* child = [latentChildWindows objectAtIndex:i];
1138                 if ([child isVisible] && (self.floating || !child.floating))
1139                 {
1140                     if (child.latentParentWindow == self)
1141                     {
1142                         if ([self level] > [child level])
1143                             [child setLevel:[self level]];
1144                         [self addChildWindow:child ordered:NSWindowAbove];
1145                         child.latentParentWindow = nil;
1146                         reordered = TRUE;
1147                     }
1148                     else
1149                         ERR(@"shouldn't happen: %@ thinks %@ is a latent child, but it doesn't agree\n", self, child);
1150                     [indexesToRemove addIndex:i];
1151                 }
1152             }
1154             [latentChildWindows removeObjectsAtIndexes:indexesToRemove];
1155         }
1157         return reordered;
1158     }
1160     - (void) becameIneligibleChild
1161     {
1162         WineWindow* parent = (WineWindow*)[self parentWindow];
1163         if (parent)
1164         {
1165             if (!parent->latentChildWindows)
1166                 parent->latentChildWindows = [[NSMutableArray alloc] init];
1167             [parent->latentChildWindows insertObject:self atIndex:0];
1168             self.latentParentWindow = parent;
1169             [parent removeChildWindow:self];
1170         }
1171     }
1173     - (void) becameIneligibleParentOrChild
1174     {
1175         NSArray* childWindows = [self childWineWindows];
1177         [self becameIneligibleChild];
1179         if ([childWindows count])
1180         {
1181             WineWindow* child;
1183             for (child in childWindows)
1184             {
1185                 child.latentParentWindow = self;
1186                 [self removeChildWindow:child];
1187             }
1189             if (latentChildWindows)
1190                 [latentChildWindows replaceObjectsInRange:NSMakeRange(0, 0) withObjectsFromArray:childWindows];
1191             else
1192                 latentChildWindows = [childWindows mutableCopy];
1193         }
1194     }
1196     // Determine if, among Wine windows, this window is directly above or below
1197     // a given other Wine window with no other Wine window intervening.
1198     // Intervening non-Wine windows are ignored.
1199     - (BOOL) isOrdered:(NSWindowOrderingMode)orderingMode relativeTo:(WineWindow*)otherWindow
1200     {
1201         NSNumber* windowNumber;
1202         NSNumber* otherWindowNumber;
1203         NSArray* windowNumbers;
1204         NSUInteger windowIndex, otherWindowIndex, lowIndex, highIndex, i;
1206         if (![self isVisible] || ![otherWindow isVisible])
1207             return FALSE;
1209         windowNumber = [NSNumber numberWithInteger:[self windowNumber]];
1210         otherWindowNumber = [NSNumber numberWithInteger:[otherWindow windowNumber]];
1211         windowNumbers = [[self class] windowNumbersWithOptions:0];
1212         windowIndex = [windowNumbers indexOfObject:windowNumber];
1213         otherWindowIndex = [windowNumbers indexOfObject:otherWindowNumber];
1215         if (windowIndex == NSNotFound || otherWindowIndex == NSNotFound)
1216             return FALSE;
1218         if (orderingMode == NSWindowAbove)
1219         {
1220             lowIndex = windowIndex;
1221             highIndex = otherWindowIndex;
1222         }
1223         else if (orderingMode == NSWindowBelow)
1224         {
1225             lowIndex = otherWindowIndex;
1226             highIndex = windowIndex;
1227         }
1228         else
1229             return FALSE;
1231         if (highIndex <= lowIndex)
1232             return FALSE;
1234         for (i = lowIndex + 1; i < highIndex; i++)
1235         {
1236             NSInteger interveningWindowNumber = [[windowNumbers objectAtIndex:i] integerValue];
1237             NSWindow* interveningWindow = [NSApp windowWithWindowNumber:interveningWindowNumber];
1238             if ([interveningWindow isKindOfClass:[WineWindow class]])
1239                 return FALSE;
1240         }
1242         return TRUE;
1243     }
1245     - (void) order:(NSWindowOrderingMode)mode childWindow:(WineWindow*)child relativeTo:(WineWindow*)other
1246     {
1247         NSMutableArray* windowNumbers;
1248         NSNumber* childWindowNumber;
1249         NSUInteger otherIndex, limit;
1250         NSArray* origChildren;
1251         NSMutableArray* children;
1253         // Get the z-order from the window server and modify it to reflect the
1254         // requested window ordering.
1255         windowNumbers = [[[[self class] windowNumbersWithOptions:NSWindowNumberListAllSpaces] mutableCopy] autorelease];
1256         childWindowNumber = [NSNumber numberWithInteger:[child windowNumber]];
1257         [windowNumbers removeObject:childWindowNumber];
1258         otherIndex = [windowNumbers indexOfObject:[NSNumber numberWithInteger:[other windowNumber]]];
1259         [windowNumbers insertObject:childWindowNumber atIndex:otherIndex + (mode == NSWindowAbove ? 0 : 1)];
1261         // Get our child windows and sort them in the reverse of the desired
1262         // z-order (back-to-front).
1263         origChildren = [self childWineWindows];
1264         children = [[origChildren mutableCopy] autorelease];
1265         [children sortWithOptions:NSSortStable
1266                   usingComparator:^NSComparisonResult(id obj1, id obj2){
1267             NSNumber* window1Number = [NSNumber numberWithInteger:[obj1 windowNumber]];
1268             NSNumber* window2Number = [NSNumber numberWithInteger:[obj2 windowNumber]];
1269             NSUInteger index1 = [windowNumbers indexOfObject:window1Number];
1270             NSUInteger index2 = [windowNumbers indexOfObject:window2Number];
1271             if (index1 == NSNotFound)
1272             {
1273                 if (index2 == NSNotFound)
1274                     return NSOrderedSame;
1275                 else
1276                     return NSOrderedAscending;
1277             }
1278             else if (index2 == NSNotFound)
1279                 return NSOrderedDescending;
1280             else if (index1 < index2)
1281                 return NSOrderedDescending;
1282             else if (index2 < index1)
1283                 return NSOrderedAscending;
1285             return NSOrderedSame;
1286         }];
1288         // If the current and desired children arrays match up to a point, leave
1289         // those matching children alone.
1290         limit = MIN([origChildren count], [children count]);
1291         for (otherIndex = 0; otherIndex < limit; otherIndex++)
1292         {
1293             if ([origChildren objectAtIndex:otherIndex] != [children objectAtIndex:otherIndex])
1294                 break;
1295         }
1296         [children removeObjectsInRange:NSMakeRange(0, otherIndex)];
1298         // Remove all of the child windows and re-add them back-to-front so they
1299         // are in the desired order.
1300         for (other in children)
1301             [self removeChildWindow:other];
1302         for (other in children)
1303             [self addChildWindow:other ordered:NSWindowAbove];
1304     }
1306     /* Returns whether or not the window was ordered in, which depends on if
1307        its frame intersects any screen. */
1308     - (void) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next activate:(BOOL)activate
1309     {
1310         WineApplicationController* controller = [WineApplicationController sharedController];
1311         if (![self isMiniaturized])
1312         {
1313             BOOL needAdjustWindowLevels = FALSE;
1314             BOOL wasVisible;
1316             [controller transformProcessToForeground];
1317             [NSApp unhide:nil];
1318             wasVisible = [self isVisible];
1320             if (activate)
1321                 [NSApp activateIgnoringOtherApps:YES];
1323             NSDisableScreenUpdates();
1325             if ([self becameEligibleParentOrChild])
1326                 needAdjustWindowLevels = TRUE;
1328             if (prev || next)
1329             {
1330                 WineWindow* other = [prev isVisible] ? prev : next;
1331                 NSWindowOrderingMode orderingMode = [prev isVisible] ? NSWindowBelow : NSWindowAbove;
1333                 if (![self isOrdered:orderingMode relativeTo:other])
1334                 {
1335                     WineWindow* parent = (WineWindow*)[self parentWindow];
1336                     WineWindow* otherParent = (WineWindow*)[other parentWindow];
1338                     // This window level may not be right for this window based
1339                     // on floating-ness, fullscreen-ness, etc.  But we set it
1340                     // temporarily to allow us to order the windows properly.
1341                     // Then the levels get fixed by -adjustWindowLevels.
1342                     if ([self level] != [other level])
1343                         [self setLevel:[other level]];
1344                     [self orderWindow:orderingMode relativeTo:[other windowNumber]];
1345                     [self checkWineDisplayLink];
1347                     // The above call to -[NSWindow orderWindow:relativeTo:] won't
1348                     // reorder windows which are both children of the same parent
1349                     // relative to each other, so do that separately.
1350                     if (parent && parent == otherParent)
1351                         [parent order:orderingMode childWindow:self relativeTo:other];
1353                     needAdjustWindowLevels = TRUE;
1354                 }
1355             }
1356             else
1357             {
1358                 // Again, temporarily set level to make sure we can order to
1359                 // the right place.
1360                 next = [controller frontWineWindow];
1361                 if (next && [self level] < [next level])
1362                     [self setLevel:[next level]];
1363                 [self orderFront:nil];
1364                 [self checkWineDisplayLink];
1365                 needAdjustWindowLevels = TRUE;
1366             }
1368             if ([self becameEligibleParentOrChild])
1369                 needAdjustWindowLevels = TRUE;
1371             if (needAdjustWindowLevels)
1372             {
1373                 if (!wasVisible && fullscreen && [self isOnActiveSpace])
1374                     [controller updateFullscreenWindows];
1375                 [controller adjustWindowLevels];
1376             }
1378             if (pendingMinimize)
1379             {
1380                 [super miniaturize:nil];
1381                 pendingMinimize = FALSE;
1382             }
1384             NSEnableScreenUpdates();
1386             /* Cocoa may adjust the frame when the window is ordered onto the screen.
1387                Generate a frame-changed event just in case.  The back end will ignore
1388                it if nothing actually changed. */
1389             [self windowDidResize:nil];
1391             if (![self isExcludedFromWindowsMenu])
1392                 [NSApp addWindowsItem:self title:[self title] filename:NO];
1393         }
1394     }
1396     - (void) doOrderOut
1397     {
1398         WineApplicationController* controller = [WineApplicationController sharedController];
1399         BOOL wasVisible = [self isVisible];
1400         BOOL wasOnActiveSpace = [self isOnActiveSpace];
1402         if ([self isMiniaturized])
1403             pendingMinimize = TRUE;
1405         WineWindow* parent = (WineWindow*)self.parentWindow;
1406         if ([parent isKindOfClass:[WineWindow class]])
1407             [parent grabDockIconSnapshotFromWindow:self force:NO];
1409         [self becameIneligibleParentOrChild];
1410         if ([self isMiniaturized])
1411         {
1412             fakingClose = TRUE;
1413             [self close];
1414             fakingClose = FALSE;
1415         }
1416         else
1417             [self orderOut:nil];
1418         [self checkWineDisplayLink];
1419         savedVisibleState = FALSE;
1420         if (wasVisible && wasOnActiveSpace && fullscreen)
1421             [controller updateFullscreenWindows];
1422         [controller adjustWindowLevels];
1423         [NSApp removeWindowsItem:self];
1425         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD) |
1426                                          event_mask_for_type(WINDOW_GOT_FOCUS) |
1427                                          event_mask_for_type(WINDOW_LOST_FOCUS) |
1428                                          event_mask_for_type(WINDOW_MAXIMIZE_REQUESTED) |
1429                                          event_mask_for_type(WINDOW_MINIMIZE_REQUESTED) |
1430                                          event_mask_for_type(WINDOW_RESTORE_REQUESTED)
1431                                forWindow:self];
1432     }
1434     - (void) updateFullscreen
1435     {
1436         NSRect contentRect = [self contentRectForFrameRect:[self frame]];
1437         BOOL nowFullscreen = !([self styleMask] & NSFullScreenWindowMask) && screen_covered_by_rect(contentRect, [NSScreen screens]);
1439         if (nowFullscreen != fullscreen)
1440         {
1441             WineApplicationController* controller = [WineApplicationController sharedController];
1443             fullscreen = nowFullscreen;
1444             if ([self isVisible] && [self isOnActiveSpace])
1445                 [controller updateFullscreenWindows];
1447             [controller adjustWindowLevels];
1448         }
1449     }
1451     - (void) setFrameFromWine:(NSRect)contentRect
1452     {
1453         /* Origin is (left, top) in a top-down space.  Need to convert it to
1454            (left, bottom) in a bottom-up space. */
1455         [[WineApplicationController sharedController] flipRect:&contentRect];
1457         /* The back end is establishing a new window size and position.  It's
1458            not interested in any stale events regarding those that may be sitting
1459            in the queue. */
1460         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
1461                                forWindow:self];
1463         if (!NSIsEmptyRect(contentRect))
1464         {
1465             NSRect frame, oldFrame;
1467             oldFrame = [self frame];
1468             frame = [self frameRectForContentRect:contentRect];
1469             if (!NSEqualRects(frame, oldFrame))
1470             {
1471                 BOOL equalSizes = NSEqualSizes(frame.size, oldFrame.size);
1472                 BOOL needEnableScreenUpdates = FALSE;
1474                 if ([self preventResizing])
1475                 {
1476                     // Allow the following calls to -setFrame:display: to work even
1477                     // if they would violate the content size constraints. This
1478                     // shouldn't be necessary since the content size constraints are
1479                     // documented to not constrain that method, but it seems to be.
1480                     [self setContentMinSize:NSZeroSize];
1481                     [self setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
1482                 }
1484                 if (equalSizes && [[self childWineWindows] count])
1485                 {
1486                     // If we change the window frame such that the origin moves
1487                     // but the size doesn't change, then Cocoa moves child
1488                     // windows with the parent.  We don't want that so we fake
1489                     // a change of the size and then change it back.
1490                     NSRect bogusFrame = frame;
1491                     bogusFrame.size.width++;
1493                     NSDisableScreenUpdates();
1494                     needEnableScreenUpdates = TRUE;
1496                     ignore_windowResize = TRUE;
1497                     [self setFrame:bogusFrame display:NO];
1498                     ignore_windowResize = FALSE;
1499                 }
1501                 [self setFrame:frame display:YES];
1502                 if ([self preventResizing])
1503                 {
1504                     [self setContentMinSize:contentRect.size];
1505                     [self setContentMaxSize:contentRect.size];
1506                 }
1508                 if (needEnableScreenUpdates)
1509                     NSEnableScreenUpdates();
1511                 if (!equalSizes)
1512                     [self updateColorSpace];
1514                 if (!enteringFullScreen &&
1515                     [[NSProcessInfo processInfo] systemUptime] - enteredFullScreenTime > 1.0)
1516                     nonFullscreenFrame = frame;
1518                 [self updateFullscreen];
1520                 if ([self isOrderedIn])
1521                 {
1522                     /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
1523                        event.  The back end will ignore it if nothing actually changed. */
1524                     [self windowDidResize:nil];
1525                 }
1526             }
1527         }
1528     }
1530     - (void) setMacDrvParentWindow:(WineWindow*)parent
1531     {
1532         WineWindow* oldParent = (WineWindow*)[self parentWindow];
1533         if ((oldParent && oldParent != parent) || (!oldParent && latentParentWindow != parent))
1534         {
1535             [oldParent removeChildWineWindow:self];
1536             [latentParentWindow removeChildWineWindow:self];
1537             if ([parent addChildWineWindow:self])
1538                 [[WineApplicationController sharedController] adjustWindowLevels];
1539         }
1540     }
1542     - (void) setDisabled:(BOOL)newValue
1543     {
1544         if (disabled != newValue)
1545         {
1546             disabled = newValue;
1547             [self adjustFeaturesForState];
1548         }
1549     }
1551     - (BOOL) needsTransparency
1552     {
1553         return self.shape || self.colorKeyed || self.usePerPixelAlpha ||
1554                 (gl_surface_mode == GL_SURFACE_BEHIND && [[self.contentView valueForKeyPath:@"subviews.@max.hasGLContext"] boolValue]);
1555     }
1557     - (void) checkTransparency
1558     {
1559         if (![self isOpaque] && !self.needsTransparency)
1560         {
1561             self.shapeChangedSinceLastDraw = TRUE;
1562             [[self contentView] setNeedsDisplay:YES];
1563             [self setBackgroundColor:[NSColor windowBackgroundColor]];
1564             [self setOpaque:YES];
1565         }
1566         else if ([self isOpaque] && self.needsTransparency)
1567         {
1568             self.shapeChangedSinceLastDraw = TRUE;
1569             [[self contentView] setNeedsDisplay:YES];
1570             [self setBackgroundColor:[NSColor clearColor]];
1571             [self setOpaque:NO];
1572         }
1573     }
1575     - (void) setShape:(NSBezierPath*)newShape
1576     {
1577         if (shape == newShape) return;
1579         if (shape)
1580         {
1581             [[self contentView] setNeedsDisplayInRect:[shape bounds]];
1582             [shape release];
1583         }
1584         if (newShape)
1585             [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
1587         shape = [newShape copy];
1588         self.shapeChangedSinceLastDraw = TRUE;
1590         [self checkTransparency];
1591     }
1593     - (void) makeFocused:(BOOL)activate
1594     {
1595         if (activate)
1596         {
1597             [[WineApplicationController sharedController] transformProcessToForeground];
1598             [NSApp activateIgnoringOtherApps:YES];
1599         }
1601         causing_becomeKeyWindow = self;
1602         [self makeKeyWindow];
1603         causing_becomeKeyWindow = nil;
1605         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS) |
1606                                          event_mask_for_type(WINDOW_LOST_FOCUS)
1607                                forWindow:self];
1608     }
1610     - (void) postKey:(uint16_t)keyCode
1611              pressed:(BOOL)pressed
1612            modifiers:(NSUInteger)modifiers
1613                event:(NSEvent*)theEvent
1614     {
1615         macdrv_event* event;
1616         CGEventRef cgevent;
1617         WineApplicationController* controller = [WineApplicationController sharedController];
1619         event = macdrv_create_event(pressed ? KEY_PRESS : KEY_RELEASE, self);
1620         event->key.keycode   = keyCode;
1621         event->key.modifiers = modifiers;
1622         event->key.time_ms   = [controller ticksForEventTime:[theEvent timestamp]];
1624         if ((cgevent = [theEvent CGEvent]))
1625         {
1626             CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent,
1627                                                         kCGKeyboardEventKeyboardType);
1628             if (keyboardType != controller.keyboardType)
1629             {
1630                 controller.keyboardType = keyboardType;
1631                 [controller keyboardSelectionDidChange];
1632             }
1633         }
1635         [queue postEvent:event];
1637         macdrv_release_event(event);
1639         [controller noteKey:keyCode pressed:pressed];
1640     }
1642     - (void) postKeyEvent:(NSEvent *)theEvent
1643     {
1644         [self flagsChanged:theEvent];
1645         [self postKey:[theEvent keyCode]
1646               pressed:[theEvent type] == NSKeyDown
1647             modifiers:adjusted_modifiers_for_option_behavior([theEvent modifierFlags])
1648                 event:theEvent];
1649     }
1651     - (void) setWineMinSize:(NSSize)minSize maxSize:(NSSize)maxSize
1652     {
1653         savedContentMinSize = minSize;
1654         savedContentMaxSize = maxSize;
1655         if (![self preventResizing])
1656         {
1657             [self setContentMinSize:minSize];
1658             [self setContentMaxSize:maxSize];
1659         }
1660     }
1662     - (WineWindow*) ancestorWineWindow
1663     {
1664         WineWindow* ancestor = self;
1665         for (;;)
1666         {
1667             WineWindow* parent = (WineWindow*)[ancestor parentWindow];
1668             if ([parent isKindOfClass:[WineWindow class]])
1669                 ancestor = parent;
1670             else
1671                 break;
1672         }
1673         return ancestor;
1674     }
1676     - (void) postBroughtForwardEvent
1677     {
1678         macdrv_event* event = macdrv_create_event(WINDOW_BROUGHT_FORWARD, self);
1679         [queue postEvent:event];
1680         macdrv_release_event(event);
1681     }
1683     - (void) updateForCursorClipping
1684     {
1685         [self adjustFeaturesForState];
1686     }
1688     - (void) endWindowDragging
1689     {
1690         if (draggingPhase)
1691         {
1692             if (draggingPhase == 3)
1693             {
1694                 macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, self);
1695                 [queue postEvent:event];
1696                 macdrv_release_event(event);
1697             }
1699             draggingPhase = 0;
1700             [[WineApplicationController sharedController] window:self isBeingDragged:NO];
1701         }
1702     }
1704     - (NSMutableDictionary*) displayIDToDisplayLinkMap
1705     {
1706         static NSMutableDictionary* displayIDToDisplayLinkMap;
1707         if (!displayIDToDisplayLinkMap)
1708         {
1709             displayIDToDisplayLinkMap = [[NSMutableDictionary alloc] init];
1711             [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationDidChangeScreenParametersNotification
1712                                                               object:NSApp
1713                                                                queue:nil
1714                                                           usingBlock:^(NSNotification *note){
1715                 NSMutableSet* badDisplayIDs = [NSMutableSet setWithArray:displayIDToDisplayLinkMap.allKeys];
1716                 NSSet* validDisplayIDs = [NSSet setWithArray:[[NSScreen screens] valueForKeyPath:@"deviceDescription.NSScreenNumber"]];
1717                 [badDisplayIDs minusSet:validDisplayIDs];
1718                 [displayIDToDisplayLinkMap removeObjectsForKeys:[badDisplayIDs allObjects]];
1719             }];
1720         }
1721         return displayIDToDisplayLinkMap;
1722     }
1724     - (WineDisplayLink*) wineDisplayLink
1725     {
1726         if (!_lastDisplayID)
1727             return nil;
1729         NSMutableDictionary* displayIDToDisplayLinkMap = [self displayIDToDisplayLinkMap];
1730         return [displayIDToDisplayLinkMap objectForKey:[NSNumber numberWithUnsignedInt:_lastDisplayID]];
1731     }
1733     - (void) checkWineDisplayLink
1734     {
1735         NSScreen* screen = self.screen;
1736         if (![self isVisible] || ![self isOnActiveSpace] || [self isMiniaturized] || [self isEmptyShaped])
1737             screen = nil;
1738 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
1739         if ([self respondsToSelector:@selector(occlusionState)] && !(self.occlusionState & NSWindowOcclusionStateVisible))
1740             screen = nil;
1741 #endif
1743         NSNumber* displayIDNumber = [screen.deviceDescription objectForKey:@"NSScreenNumber"];
1744         CGDirectDisplayID displayID = [displayIDNumber unsignedIntValue];
1745         if (displayID == _lastDisplayID)
1746             return;
1748         NSMutableDictionary* displayIDToDisplayLinkMap = [self displayIDToDisplayLinkMap];
1750         if (_lastDisplayID)
1751         {
1752             WineDisplayLink* link = [displayIDToDisplayLinkMap objectForKey:[NSNumber numberWithUnsignedInt:_lastDisplayID]];
1753             [link removeWindow:self];
1754         }
1755         if (displayID)
1756         {
1757             WineDisplayLink* link = [displayIDToDisplayLinkMap objectForKey:displayIDNumber];
1758             if (!link)
1759             {
1760                 link = [[[WineDisplayLink alloc] initWithDisplayID:displayID] autorelease];
1761                 [displayIDToDisplayLinkMap setObject:link forKey:displayIDNumber];
1762             }
1763             [link addWindow:self];
1764             [self displayIfNeeded];
1765         }
1766         _lastDisplayID = displayID;
1767     }
1769     - (BOOL) isEmptyShaped
1770     {
1771         return (self.shapeData.length == sizeof(CGRectZero) && !memcmp(self.shapeData.bytes, &CGRectZero, sizeof(CGRectZero)));
1772     }
1774     - (BOOL) canProvideSnapshot
1775     {
1776         return (self.windowNumber > 0 && ![self isEmptyShaped]);
1777     }
1779     - (void) grabDockIconSnapshotFromWindow:(WineWindow*)window force:(BOOL)force
1780     {
1781         if (![self isEmptyShaped])
1782             return;
1784         NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
1785         if (!force && now < lastDockIconSnapshot + 1)
1786             return;
1788         if (window)
1789         {
1790             if (![window canProvideSnapshot])
1791                 return;
1792         }
1793         else
1794         {
1795             CGFloat bestArea;
1796             for (WineWindow* childWindow in self.childWindows)
1797             {
1798                 if (![childWindow isKindOfClass:[WineWindow class]] || ![childWindow canProvideSnapshot])
1799                     continue;
1801                 NSSize size = childWindow.frame.size;
1802                 CGFloat area = size.width * size.height;
1803                 if (!window || area > bestArea)
1804                 {
1805                     window = childWindow;
1806                     bestArea = area;
1807                 }
1808             }
1810             if (!window)
1811                 return;
1812         }
1814         const void* windowID = (const void*)(CGWindowID)window.windowNumber;
1815         CFArrayRef windowIDs = CFArrayCreate(NULL, &windowID, 1, NULL);
1816         CGImageRef windowImage = CGWindowListCreateImageFromArray(CGRectNull, windowIDs, kCGWindowImageBoundsIgnoreFraming);
1817         CFRelease(windowIDs);
1818         if (!windowImage)
1819             return;
1821         NSImage* appImage = [NSApp applicationIconImage];
1822         if (!appImage)
1823             appImage = [NSImage imageNamed:NSImageNameApplicationIcon];
1825         NSImage* dockIcon = [[[NSImage alloc] initWithSize:NSMakeSize(256, 256)] autorelease];
1826         [dockIcon lockFocus];
1828         CGContextRef cgcontext = [[NSGraphicsContext currentContext] graphicsPort];
1830         CGRect rect = CGRectMake(8, 8, 240, 240);
1831         size_t width = CGImageGetWidth(windowImage);
1832         size_t height = CGImageGetHeight(windowImage);
1833         if (width > height)
1834         {
1835             rect.size.height *= height / (double)width;
1836             rect.origin.y += (CGRectGetWidth(rect) - CGRectGetHeight(rect)) / 2;
1837         }
1838         else if (width != height)
1839         {
1840             rect.size.width *= width / (double)height;
1841             rect.origin.x += (CGRectGetHeight(rect) - CGRectGetWidth(rect)) / 2;
1842         }
1844         CGContextDrawImage(cgcontext, rect, windowImage);
1845         [appImage drawInRect:NSMakeRect(156, 4, 96, 96)
1846                     fromRect:NSZeroRect
1847                    operation:NSCompositeSourceOver
1848                     fraction:1
1849               respectFlipped:YES
1850                        hints:nil];
1852         [dockIcon unlockFocus];
1854         CGImageRelease(windowImage);
1856         NSImageView* imageView = (NSImageView*)self.dockTile.contentView;
1857         if (![imageView isKindOfClass:[NSImageView class]])
1858         {
1859             imageView = [[[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 256, 256)] autorelease];
1860             imageView.imageScaling = NSImageScaleProportionallyUpOrDown;
1861             self.dockTile.contentView = imageView;
1862         }
1863         imageView.image = dockIcon;
1864         [self.dockTile display];
1865         lastDockIconSnapshot = now;
1866     }
1868     - (void) checkEmptyShaped
1869     {
1870         if (self.dockTile.contentView && ![self isEmptyShaped])
1871         {
1872             self.dockTile.contentView = nil;
1873             lastDockIconSnapshot = 0;
1874         }
1875         [self checkWineDisplayLink];
1876     }
1879     /*
1880      * ---------- NSWindow method overrides ----------
1881      */
1882     - (BOOL) canBecomeKeyWindow
1883     {
1884         if (causing_becomeKeyWindow == self) return YES;
1885         if (self.disabled || self.noActivate) return NO;
1886         return [self isKeyWindow];
1887     }
1889     - (BOOL) canBecomeMainWindow
1890     {
1891         return [self canBecomeKeyWindow];
1892     }
1894     - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
1895     {
1896         // If a window is sized to completely cover a screen, then it's in
1897         // full-screen mode.  In that case, we don't allow NSWindow to constrain
1898         // it.
1899         NSArray* screens = [NSScreen screens];
1900         NSRect contentRect = [self contentRectForFrameRect:frameRect];
1901         if (!screen_covered_by_rect(contentRect, screens) &&
1902             frame_intersects_screens(frameRect, screens))
1903             frameRect = [super constrainFrameRect:frameRect toScreen:screen];
1904         return frameRect;
1905     }
1907     // This private method of NSWindow is called as Cocoa reacts to the display
1908     // configuration changing.  Among other things, it adjusts the window's
1909     // frame based on how the screen(s) changed size.  That tells Wine that the
1910     // window has been moved.  We don't want that.  Rather, we want to make
1911     // sure that the WinAPI notion of the window position is maintained/
1912     // restored, possibly undoing or overriding Cocoa's adjustment.
1913     //
1914     // So, we queue a REASSERT_WINDOW_POSITION event to the back end before
1915     // Cocoa has a chance to adjust the frame, thus preceding any resulting
1916     // WINDOW_FRAME_CHANGED event that may get queued.  The back end will
1917     // reassert its notion of the position.  That call won't get processed
1918     // until after this method returns, so it will override whatever this
1919     // method does to the window position.  It will also discard any pending
1920     // WINDOW_FRAME_CHANGED events.
1921     //
1922     // Unfortunately, the only way I've found to know when Cocoa is _about to_
1923     // adjust the window's position due to a display change is to hook into
1924     // this private method.  This private method has remained stable from 10.6
1925     // through 10.11.  If it does change, the most likely thing is that it
1926     // will be removed and no longer called and this fix will simply stop
1927     // working.  The only real danger would be if Apple changed the return type
1928     // to a struct or floating-point type, which would change the calling
1929     // convention.
1930     - (id) _displayChanged
1931     {
1932         macdrv_event* event = macdrv_create_event(REASSERT_WINDOW_POSITION, self);
1933         [queue postEvent:event];
1934         macdrv_release_event(event);
1936         return [super _displayChanged];
1937     }
1939     - (BOOL) isExcludedFromWindowsMenu
1940     {
1941         return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
1942     }
1944     - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
1945     {
1946         BOOL ret = [super validateMenuItem:menuItem];
1948         if ([menuItem action] == @selector(makeKeyAndOrderFront:))
1949             ret = [self isKeyWindow] || (!self.disabled && !self.noActivate);
1950         if ([menuItem action] == @selector(toggleFullScreen:) && (self.disabled || maximized))
1951             ret = NO;
1953         return ret;
1954     }
1956     /* We don't call this.  It's the action method of the items in the Window menu. */
1957     - (void) makeKeyAndOrderFront:(id)sender
1958     {
1959         if ([self isMiniaturized])
1960             [self deminiaturize:nil];
1961         [self orderBelow:nil orAbove:nil activate:NO];
1962         [[self ancestorWineWindow] postBroughtForwardEvent];
1964         if (![self isKeyWindow] && !self.disabled && !self.noActivate)
1965             [[WineApplicationController sharedController] windowGotFocus:self];
1966     }
1968     - (void) sendEvent:(NSEvent*)event
1969     {
1970         NSEventType type = event.type;
1972         /* NSWindow consumes certain key-down events as part of Cocoa's keyboard
1973            interface control.  For example, Control-Tab switches focus among
1974            views.  We want to bypass that feature, so directly route key-down
1975            events to -keyDown:. */
1976         if (type == NSKeyDown)
1977             [[self firstResponder] keyDown:event];
1978         else
1979         {
1980             if (!draggingPhase && maximized && ![self isMovable] &&
1981                 ![self allowsMovingWithMaximized:YES] && [self allowsMovingWithMaximized:NO] &&
1982                 type == NSLeftMouseDown && (self.styleMask & NSTitledWindowMask))
1983             {
1984                 NSRect titleBar = self.frame;
1985                 NSRect contentRect = [self contentRectForFrameRect:titleBar];
1986                 titleBar.size.height = NSMaxY(titleBar) - NSMaxY(contentRect);
1987                 titleBar.origin.y = NSMaxY(contentRect);
1989                 dragStartPosition = [self convertBaseToScreen:event.locationInWindow];
1991                 if (NSMouseInRect(dragStartPosition, titleBar, NO))
1992                 {
1993                     static const NSWindowButton buttons[] = {
1994                         NSWindowCloseButton,
1995                         NSWindowMiniaturizeButton,
1996                         NSWindowZoomButton,
1997                         NSWindowFullScreenButton,
1998                     };
1999                     BOOL hitButton = NO;
2000                     int i;
2002                     for (i = 0; i < sizeof(buttons) / sizeof(buttons[0]); i++)
2003                     {
2004                         NSButton* button;
2006                         if (buttons[i] == NSWindowFullScreenButton && ![self respondsToSelector:@selector(toggleFullScreen:)])
2007                             continue;
2009                         button = [self standardWindowButton:buttons[i]];
2010                         if ([button hitTest:[button.superview convertPoint:event.locationInWindow fromView:nil]])
2011                         {
2012                             hitButton = YES;
2013                             break;
2014                         }
2015                     }
2017                     if (!hitButton)
2018                     {
2019                         draggingPhase = 1;
2020                         dragWindowStartPosition = NSMakePoint(NSMinX(titleBar), NSMaxY(titleBar));
2021                         [[WineApplicationController sharedController] window:self isBeingDragged:YES];
2022                     }
2023                 }
2024             }
2025             else if (draggingPhase && (type == NSLeftMouseDragged || type == NSLeftMouseUp))
2026             {
2027                 if ([self isMovable])
2028                 {
2029                     NSPoint point = [self convertBaseToScreen:event.locationInWindow];
2030                     NSPoint newTopLeft = dragWindowStartPosition;
2032                     newTopLeft.x += point.x - dragStartPosition.x;
2033                     newTopLeft.y += point.y - dragStartPosition.y;
2035                     if (draggingPhase == 2)
2036                     {
2037                         macdrv_event* event = macdrv_create_event(WINDOW_DRAG_BEGIN, self);
2038                         [queue postEvent:event];
2039                         macdrv_release_event(event);
2041                         draggingPhase = 3;
2042                     }
2044                     [self setFrameTopLeftPoint:newTopLeft];
2045                 }
2046                 else if (draggingPhase == 1 && type == NSLeftMouseDragged)
2047                 {
2048                     macdrv_event* event;
2049                     NSRect frame = [self contentRectForFrameRect:self.frame];
2051                     [[WineApplicationController sharedController] flipRect:&frame];
2053                     event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self);
2054                     event->window_restore_requested.keep_frame = TRUE;
2055                     event->window_restore_requested.frame = NSRectToCGRect(frame);
2056                     [queue postEvent:event];
2057                     macdrv_release_event(event);
2059                     draggingPhase = 2;
2060                 }
2062                 if (type == NSLeftMouseUp)
2063                     [self endWindowDragging];
2064             }
2066             [super sendEvent:event];
2067         }
2068     }
2070     - (void) miniaturize:(id)sender
2071     {
2072         macdrv_event* event = macdrv_create_event(WINDOW_MINIMIZE_REQUESTED, self);
2073         [queue postEvent:event];
2074         macdrv_release_event(event);
2076         WineWindow* parent = (WineWindow*)self.parentWindow;
2077         if ([parent isKindOfClass:[WineWindow class]])
2078             [parent grabDockIconSnapshotFromWindow:self force:YES];
2079     }
2081     - (void) toggleFullScreen:(id)sender
2082     {
2083         if (!self.disabled && !maximized)
2084             [super toggleFullScreen:sender];
2085     }
2087     - (void) setViewsNeedDisplay:(BOOL)value
2088     {
2089         if (value && ![self viewsNeedDisplay])
2090         {
2091             WineDisplayLink* link = [self wineDisplayLink];
2092             if (link)
2093             {
2094                 NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
2095                 if (_lastDisplayTime + [link refreshPeriod] < now)
2096                     [self setAutodisplay:YES];
2097                 else
2098                 {
2099                     [link start];
2100                     _lastDisplayTime = now;
2101                 }
2102             }
2103         }
2104         [super setViewsNeedDisplay:value];
2105     }
2107     - (void) display
2108     {
2109         _lastDisplayTime = [[NSProcessInfo processInfo] systemUptime];
2110         [super display];
2111         [self setAutodisplay:NO];
2112     }
2114     - (void) displayIfNeeded
2115     {
2116         _lastDisplayTime = [[NSProcessInfo processInfo] systemUptime];
2117         [super displayIfNeeded];
2118         [self setAutodisplay:NO];
2119     }
2121     - (NSArray*) childWineWindows
2122     {
2123         NSArray* childWindows = self.childWindows;
2124         NSIndexSet* indexes = [childWindows indexesOfObjectsPassingTest:^BOOL(id child, NSUInteger idx, BOOL *stop){
2125             return [child isKindOfClass:[WineWindow class]];
2126         }];
2127         return [childWindows objectsAtIndexes:indexes];
2128     }
2130     // We normally use the generic/calibrated RGB color space for the window,
2131     // rather than the device color space, to avoid expensive color conversion
2132     // which slows down drawing.  However, for windows displaying OpenGL, having
2133     // a different color space than the screen greatly reduces frame rates, often
2134     // limiting it to the display refresh rate.
2135     //
2136     // To avoid this, we switch back to the screen color space whenever the
2137     // window is covered by a view with an attached OpenGL context.
2138     - (void) updateColorSpace
2139     {
2140         NSRect contentRect = [[self contentView] frame];
2141         BOOL coveredByGLView = FALSE;
2142         for (WineContentView* view in [[self contentView] subviews])
2143         {
2144             if ([view hasGLContext])
2145             {
2146                 NSRect frame = [view convertRect:[view bounds] toView:nil];
2147                 if (NSContainsRect(frame, contentRect))
2148                 {
2149                     coveredByGLView = TRUE;
2150                     break;
2151                 }
2152             }
2153         }
2155         if (coveredByGLView)
2156             [self setColorSpace:nil];
2157         else
2158             [self setColorSpace:[NSColorSpace genericRGBColorSpace]];
2159     }
2161     - (void) updateForGLSubviews
2162     {
2163         [self updateColorSpace];
2164         if (gl_surface_mode == GL_SURFACE_BEHIND)
2165             [self checkTransparency];
2166     }
2169     /*
2170      * ---------- NSResponder method overrides ----------
2171      */
2172     - (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
2174     - (void) flagsChanged:(NSEvent *)theEvent
2175     {
2176         static const struct {
2177             NSUInteger  mask;
2178             uint16_t    keycode;
2179         } modifiers[] = {
2180             { NX_ALPHASHIFTMASK,        kVK_CapsLock },
2181             { NX_DEVICELSHIFTKEYMASK,   kVK_Shift },
2182             { NX_DEVICERSHIFTKEYMASK,   kVK_RightShift },
2183             { NX_DEVICELCTLKEYMASK,     kVK_Control },
2184             { NX_DEVICERCTLKEYMASK,     kVK_RightControl },
2185             { NX_DEVICELALTKEYMASK,     kVK_Option },
2186             { NX_DEVICERALTKEYMASK,     kVK_RightOption },
2187             { NX_DEVICELCMDKEYMASK,     kVK_Command },
2188             { NX_DEVICERCMDKEYMASK,     kVK_RightCommand },
2189         };
2191         NSUInteger modifierFlags = adjusted_modifiers_for_option_behavior([theEvent modifierFlags]);
2192         NSUInteger changed;
2193         int i, last_changed;
2195         fix_device_modifiers_by_generic(&modifierFlags);
2196         changed = modifierFlags ^ lastModifierFlags;
2198         last_changed = -1;
2199         for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++)
2200             if (changed & modifiers[i].mask)
2201                 last_changed = i;
2203         for (i = 0; i <= last_changed; i++)
2204         {
2205             if (changed & modifiers[i].mask)
2206             {
2207                 BOOL pressed = (modifierFlags & modifiers[i].mask) != 0;
2209                 if (i == last_changed)
2210                     lastModifierFlags = modifierFlags;
2211                 else
2212                 {
2213                     lastModifierFlags ^= modifiers[i].mask;
2214                     fix_generic_modifiers_by_device(&lastModifierFlags);
2215                 }
2217                 // Caps lock generates one event for each press-release action.
2218                 // We need to simulate a pair of events for each actual event.
2219                 if (modifiers[i].mask == NX_ALPHASHIFTMASK)
2220                 {
2221                     [self postKey:modifiers[i].keycode
2222                           pressed:TRUE
2223                         modifiers:lastModifierFlags
2224                             event:(NSEvent*)theEvent];
2225                     pressed = FALSE;
2226                 }
2228                 [self postKey:modifiers[i].keycode
2229                       pressed:pressed
2230                     modifiers:lastModifierFlags
2231                         event:(NSEvent*)theEvent];
2232             }
2233         }
2234     }
2236     - (void) applicationWillHide
2237     {
2238         savedVisibleState = [self isVisible];
2239     }
2241     - (void) applicationDidUnhide
2242     {
2243         if ([self isVisible])
2244             [self becameEligibleParentOrChild];
2245     }
2248     /*
2249      * ---------- NSWindowDelegate methods ----------
2250      */
2251     - (NSSize) window:(NSWindow*)window willUseFullScreenContentSize:(NSSize)proposedSize
2252     {
2253         macdrv_query* query;
2254         NSSize size;
2256         query = macdrv_create_query();
2257         query->type = QUERY_MIN_MAX_INFO;
2258         query->window = (macdrv_window)[self retain];
2259         [self.queue query:query timeout:0.5];
2260         macdrv_release_query(query);
2262         size = [self contentMaxSize];
2263         if (proposedSize.width < size.width)
2264             size.width = proposedSize.width;
2265         if (proposedSize.height < size.height)
2266             size.height = proposedSize.height;
2267         return size;
2268     }
2270     - (void)windowDidBecomeKey:(NSNotification *)notification
2271     {
2272         WineApplicationController* controller = [WineApplicationController sharedController];
2273         NSEvent* event = [controller lastFlagsChanged];
2274         if (event)
2275             [self flagsChanged:event];
2277         if (causing_becomeKeyWindow == self) return;
2279         [controller windowGotFocus:self];
2280     }
2282     - (void) windowDidChangeOcclusionState:(NSNotification*)notification
2283     {
2284         [self checkWineDisplayLink];
2285     }
2287     - (void) windowDidChangeScreen:(NSNotification*)notification
2288     {
2289         [self checkWineDisplayLink];
2290     }
2292     - (void)windowDidDeminiaturize:(NSNotification *)notification
2293     {
2294         WineApplicationController* controller = [WineApplicationController sharedController];
2296         if (!ignore_windowDeminiaturize)
2297             [self postDidUnminimizeEvent];
2298         ignore_windowDeminiaturize = FALSE;
2300         [self becameEligibleParentOrChild];
2302         if (fullscreen && [self isOnActiveSpace])
2303             [controller updateFullscreenWindows];
2304         [controller adjustWindowLevels];
2306         if (![self parentWindow])
2307             [self postBroughtForwardEvent];
2309         if (!self.disabled && !self.noActivate)
2310         {
2311             causing_becomeKeyWindow = self;
2312             [self makeKeyWindow];
2313             causing_becomeKeyWindow = nil;
2314             [controller windowGotFocus:self];
2315         }
2317         [self windowDidResize:notification];
2318         [self checkWineDisplayLink];
2319     }
2321     - (void) windowDidEndLiveResize:(NSNotification *)notification
2322     {
2323         if (!maximized)
2324         {
2325             macdrv_event* event = macdrv_create_event(WINDOW_RESIZE_ENDED, self);
2326             [queue postEvent:event];
2327             macdrv_release_event(event);
2328         }
2329     }
2331     - (void) windowDidEnterFullScreen:(NSNotification*)notification
2332     {
2333         enteringFullScreen = FALSE;
2334         enteredFullScreenTime = [[NSProcessInfo processInfo] systemUptime];
2335     }
2337     - (void) windowDidExitFullScreen:(NSNotification*)notification
2338     {
2339         exitingFullScreen = FALSE;
2340         [self setFrame:nonFullscreenFrame display:YES animate:NO];
2341         [self windowDidResize:nil];
2342     }
2344     - (void) windowDidFailToEnterFullScreen:(NSWindow*)window
2345     {
2346         enteringFullScreen = FALSE;
2347         enteredFullScreenTime = 0;
2348     }
2350     - (void) windowDidFailToExitFullScreen:(NSWindow*)window
2351     {
2352         exitingFullScreen = FALSE;
2353         [self windowDidResize:nil];
2354     }
2356     - (void)windowDidMiniaturize:(NSNotification *)notification
2357     {
2358         if (fullscreen && [self isOnActiveSpace])
2359             [[WineApplicationController sharedController] updateFullscreenWindows];
2360         [self checkWineDisplayLink];
2361     }
2363     - (void)windowDidMove:(NSNotification *)notification
2364     {
2365         [self windowDidResize:notification];
2366     }
2368     - (void)windowDidResignKey:(NSNotification *)notification
2369     {
2370         macdrv_event* event;
2372         if (causing_becomeKeyWindow) return;
2374         event = macdrv_create_event(WINDOW_LOST_FOCUS, self);
2375         [queue postEvent:event];
2376         macdrv_release_event(event);
2377     }
2379     - (void)windowDidResize:(NSNotification *)notification
2380     {
2381         macdrv_event* event;
2382         NSRect frame = [self frame];
2384         if ([self inLiveResize])
2385         {
2386             if (NSMinX(frame) != NSMinX(frameAtResizeStart))
2387                 resizingFromLeft = TRUE;
2388             if (NSMaxY(frame) != NSMaxY(frameAtResizeStart))
2389                 resizingFromTop = TRUE;
2390         }
2392         frame = [self contentRectForFrameRect:frame];
2394         if (ignore_windowResize || exitingFullScreen) return;
2396         if ([self preventResizing])
2397         {
2398             [self setContentMinSize:frame.size];
2399             [self setContentMaxSize:frame.size];
2400         }
2402         [[WineApplicationController sharedController] flipRect:&frame];
2404         /* Coalesce events by discarding any previous ones still in the queue. */
2405         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
2406                                forWindow:self];
2408         event = macdrv_create_event(WINDOW_FRAME_CHANGED, self);
2409         event->window_frame_changed.frame = NSRectToCGRect(frame);
2410         event->window_frame_changed.fullscreen = ([self styleMask] & NSFullScreenWindowMask) != 0;
2411         event->window_frame_changed.in_resize = [self inLiveResize];
2412         [queue postEvent:event];
2413         macdrv_release_event(event);
2415         [[[self contentView] inputContext] invalidateCharacterCoordinates];
2416         [self updateFullscreen];
2417     }
2419     - (BOOL)windowShouldClose:(id)sender
2420     {
2421         macdrv_event* event = macdrv_create_event(WINDOW_CLOSE_REQUESTED, self);
2422         [queue postEvent:event];
2423         macdrv_release_event(event);
2424         return NO;
2425     }
2427     - (BOOL) windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame
2428     {
2429         if (maximized)
2430         {
2431             macdrv_event* event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self);
2432             [queue postEvent:event];
2433             macdrv_release_event(event);
2434             return NO;
2435         }
2436         else if (!resizable)
2437         {
2438             macdrv_event* event = macdrv_create_event(WINDOW_MAXIMIZE_REQUESTED, self);
2439             [queue postEvent:event];
2440             macdrv_release_event(event);
2441             return NO;
2442         }
2444         return YES;
2445     }
2447     - (void) windowWillClose:(NSNotification*)notification
2448     {
2449         WineWindow* child;
2451         if (fakingClose) return;
2452         if (latentParentWindow)
2453         {
2454             [latentParentWindow->latentChildWindows removeObjectIdenticalTo:self];
2455             self.latentParentWindow = nil;
2456         }
2458         for (child in latentChildWindows)
2459         {
2460             if (child.latentParentWindow == self)
2461                 child.latentParentWindow = nil;
2462         }
2463         [latentChildWindows removeAllObjects];
2464     }
2466     - (void) windowWillEnterFullScreen:(NSNotification*)notification
2467     {
2468         enteringFullScreen = TRUE;
2469         nonFullscreenFrame = [self frame];
2470     }
2472     - (void) windowWillExitFullScreen:(NSNotification*)notification
2473     {
2474         exitingFullScreen = TRUE;
2475     }
2477     - (void)windowWillMiniaturize:(NSNotification *)notification
2478     {
2479         [self becameIneligibleParentOrChild];
2480         [self grabDockIconSnapshotFromWindow:nil force:NO];
2481     }
2483     - (NSSize) windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize
2484     {
2485         if ([self inLiveResize])
2486         {
2487             if (maximized)
2488                 return self.frame.size;
2490             NSRect rect;
2491             macdrv_query* query;
2493             rect = [self frame];
2494             if (resizingFromLeft)
2495                 rect.origin.x = NSMaxX(rect) - frameSize.width;
2496             if (!resizingFromTop)
2497                 rect.origin.y = NSMaxY(rect) - frameSize.height;
2498             rect.size = frameSize;
2499             rect = [self contentRectForFrameRect:rect];
2500             [[WineApplicationController sharedController] flipRect:&rect];
2502             query = macdrv_create_query();
2503             query->type = QUERY_RESIZE_SIZE;
2504             query->window = (macdrv_window)[self retain];
2505             query->resize_size.rect = NSRectToCGRect(rect);
2506             query->resize_size.from_left = resizingFromLeft;
2507             query->resize_size.from_top = resizingFromTop;
2509             if ([self.queue query:query timeout:0.1])
2510             {
2511                 rect = NSRectFromCGRect(query->resize_size.rect);
2512                 rect = [self frameRectForContentRect:rect];
2513                 frameSize = rect.size;
2514             }
2516             macdrv_release_query(query);
2517         }
2519         return frameSize;
2520     }
2522     - (void) windowWillStartLiveResize:(NSNotification *)notification
2523     {
2524         [self endWindowDragging];
2526         if (maximized)
2527         {
2528             macdrv_event* event;
2529             NSRect frame = [self contentRectForFrameRect:self.frame];
2531             [[WineApplicationController sharedController] flipRect:&frame];
2533             event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self);
2534             event->window_restore_requested.keep_frame = TRUE;
2535             event->window_restore_requested.frame = NSRectToCGRect(frame);
2536             [queue postEvent:event];
2537             macdrv_release_event(event);
2538         }
2539         else
2540             [self sendResizeStartQuery];
2542         frameAtResizeStart = [self frame];
2543         resizingFromLeft = resizingFromTop = FALSE;
2544     }
2546     - (NSRect) windowWillUseStandardFrame:(NSWindow*)window defaultFrame:(NSRect)proposedFrame
2547     {
2548         macdrv_query* query;
2549         NSRect currentContentRect, proposedContentRect, newContentRect, screenRect;
2550         NSSize maxSize;
2552         query = macdrv_create_query();
2553         query->type = QUERY_MIN_MAX_INFO;
2554         query->window = (macdrv_window)[self retain];
2555         [self.queue query:query timeout:0.5];
2556         macdrv_release_query(query);
2558         currentContentRect = [self contentRectForFrameRect:[self frame]];
2559         proposedContentRect = [self contentRectForFrameRect:proposedFrame];
2561         maxSize = [self contentMaxSize];
2562         newContentRect.size.width = MIN(NSWidth(proposedContentRect), maxSize.width);
2563         newContentRect.size.height = MIN(NSHeight(proposedContentRect), maxSize.height);
2565         // Try to keep the top-left corner where it is.
2566         newContentRect.origin.x = NSMinX(currentContentRect);
2567         newContentRect.origin.y = NSMaxY(currentContentRect) - NSHeight(newContentRect);
2569         // If that pushes the bottom or right off the screen, pull it up and to the left.
2570         screenRect = [self contentRectForFrameRect:[[self screen] visibleFrame]];
2571         if (NSMaxX(newContentRect) > NSMaxX(screenRect))
2572             newContentRect.origin.x = NSMaxX(screenRect) - NSWidth(newContentRect);
2573         if (NSMinY(newContentRect) < NSMinY(screenRect))
2574             newContentRect.origin.y = NSMinY(screenRect);
2576         // If that pushes the top or left off the screen, push it down and the right
2577         // again.  Do this last because the top-left corner is more important than the
2578         // bottom-right.
2579         if (NSMinX(newContentRect) < NSMinX(screenRect))
2580             newContentRect.origin.x = NSMinX(screenRect);
2581         if (NSMaxY(newContentRect) > NSMaxY(screenRect))
2582             newContentRect.origin.y = NSMaxY(screenRect) - NSHeight(newContentRect);
2584         return [self frameRectForContentRect:newContentRect];
2585     }
2588     /*
2589      * ---------- NSPasteboardOwner methods ----------
2590      */
2591     - (void) pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
2592     {
2593         macdrv_query* query = macdrv_create_query();
2594         query->type = QUERY_PASTEBOARD_DATA;
2595         query->window = (macdrv_window)[self retain];
2596         query->pasteboard_data.type = (CFStringRef)[type copy];
2598         [self.queue query:query timeout:3];
2599         macdrv_release_query(query);
2600     }
2603     /*
2604      * ---------- NSDraggingDestination methods ----------
2605      */
2606     - (NSDragOperation) draggingEntered:(id <NSDraggingInfo>)sender
2607     {
2608         return [self draggingUpdated:sender];
2609     }
2611     - (void) draggingExited:(id <NSDraggingInfo>)sender
2612     {
2613         // This isn't really a query.  We don't need any response.  However, it
2614         // has to be processed in a similar manner as the other drag-and-drop
2615         // queries in order to maintain the proper order of operations.
2616         macdrv_query* query = macdrv_create_query();
2617         query->type = QUERY_DRAG_EXITED;
2618         query->window = (macdrv_window)[self retain];
2620         [self.queue query:query timeout:0.1];
2621         macdrv_release_query(query);
2622     }
2624     - (NSDragOperation) draggingUpdated:(id <NSDraggingInfo>)sender
2625     {
2626         NSDragOperation ret;
2627         NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
2628         NSPasteboard* pb = [sender draggingPasteboard];
2630         macdrv_query* query = macdrv_create_query();
2631         query->type = QUERY_DRAG_OPERATION;
2632         query->window = (macdrv_window)[self retain];
2633         query->drag_operation.x = pt.x;
2634         query->drag_operation.y = pt.y;
2635         query->drag_operation.offered_ops = [sender draggingSourceOperationMask];
2636         query->drag_operation.accepted_op = NSDragOperationNone;
2637         query->drag_operation.pasteboard = (CFTypeRef)[pb retain];
2639         [self.queue query:query timeout:3];
2640         ret = query->status ? query->drag_operation.accepted_op : NSDragOperationNone;
2641         macdrv_release_query(query);
2643         return ret;
2644     }
2646     - (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
2647     {
2648         BOOL ret;
2649         NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
2650         NSPasteboard* pb = [sender draggingPasteboard];
2652         macdrv_query* query = macdrv_create_query();
2653         query->type = QUERY_DRAG_DROP;
2654         query->window = (macdrv_window)[self retain];
2655         query->drag_drop.x = pt.x;
2656         query->drag_drop.y = pt.y;
2657         query->drag_drop.op = [sender draggingSourceOperationMask];
2658         query->drag_drop.pasteboard = (CFTypeRef)[pb retain];
2660         [self.queue query:query timeout:3 * 60 processEvents:YES];
2661         ret = query->status;
2662         macdrv_release_query(query);
2664         return ret;
2665     }
2667     - (BOOL) wantsPeriodicDraggingUpdates
2668     {
2669         return NO;
2670     }
2672 @end
2675 /***********************************************************************
2676  *              macdrv_create_cocoa_window
2678  * Create a Cocoa window with the given content frame and features (e.g.
2679  * title bar, close box, etc.).
2680  */
2681 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
2682         CGRect frame, void* hwnd, macdrv_event_queue queue)
2684     __block WineWindow* window;
2686     OnMainThread(^{
2687         window = [[WineWindow createWindowWithFeatures:wf
2688                                            windowFrame:NSRectFromCGRect(frame)
2689                                                   hwnd:hwnd
2690                                                  queue:(WineEventQueue*)queue] retain];
2691     });
2693     return (macdrv_window)window;
2696 /***********************************************************************
2697  *              macdrv_destroy_cocoa_window
2699  * Destroy a Cocoa window.
2700  */
2701 void macdrv_destroy_cocoa_window(macdrv_window w)
2703     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2704     WineWindow* window = (WineWindow*)w;
2706     OnMainThread(^{
2707         [window doOrderOut];
2708         [window close];
2709     });
2710     [window.queue discardEventsMatchingMask:-1 forWindow:window];
2711     [window release];
2713     [pool release];
2716 /***********************************************************************
2717  *              macdrv_get_window_hwnd
2719  * Get the hwnd that was set for the window at creation.
2720  */
2721 void* macdrv_get_window_hwnd(macdrv_window w)
2723     WineWindow* window = (WineWindow*)w;
2724     return window.hwnd;
2727 /***********************************************************************
2728  *              macdrv_set_cocoa_window_features
2730  * Update a Cocoa window's features.
2731  */
2732 void macdrv_set_cocoa_window_features(macdrv_window w,
2733         const struct macdrv_window_features* wf)
2735     WineWindow* window = (WineWindow*)w;
2737     OnMainThread(^{
2738         [window setWindowFeatures:wf];
2739     });
2742 /***********************************************************************
2743  *              macdrv_set_cocoa_window_state
2745  * Update a Cocoa window's state.
2746  */
2747 void macdrv_set_cocoa_window_state(macdrv_window w,
2748         const struct macdrv_window_state* state)
2750     WineWindow* window = (WineWindow*)w;
2752     OnMainThread(^{
2753         [window setMacDrvState:state];
2754     });
2757 /***********************************************************************
2758  *              macdrv_set_cocoa_window_title
2760  * Set a Cocoa window's title.
2761  */
2762 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
2763         size_t length)
2765     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2766     WineWindow* window = (WineWindow*)w;
2767     NSString* titleString;
2769     if (title)
2770         titleString = [NSString stringWithCharacters:title length:length];
2771     else
2772         titleString = @"";
2773     OnMainThreadAsync(^{
2774         [window setTitle:titleString];
2775         if ([window isOrderedIn] && ![window isExcludedFromWindowsMenu])
2776             [NSApp changeWindowsItem:window title:titleString filename:NO];
2777     });
2779     [pool release];
2782 /***********************************************************************
2783  *              macdrv_order_cocoa_window
2785  * Reorder a Cocoa window relative to other windows.  If prev is
2786  * non-NULL, it is ordered below that window.  Else, if next is non-NULL,
2787  * it is ordered above that window.  Otherwise, it is ordered to the
2788  * front.
2789  */
2790 void macdrv_order_cocoa_window(macdrv_window w, macdrv_window p,
2791         macdrv_window n, int activate)
2793     WineWindow* window = (WineWindow*)w;
2794     WineWindow* prev = (WineWindow*)p;
2795     WineWindow* next = (WineWindow*)n;
2797     OnMainThreadAsync(^{
2798         [window orderBelow:prev
2799                    orAbove:next
2800                   activate:activate];
2801     });
2802     [window.queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD)
2803                                   forWindow:window];
2804     [next.queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD)
2805                                 forWindow:next];
2808 /***********************************************************************
2809  *              macdrv_hide_cocoa_window
2811  * Hides a Cocoa window.
2812  */
2813 void macdrv_hide_cocoa_window(macdrv_window w)
2815     WineWindow* window = (WineWindow*)w;
2817     OnMainThread(^{
2818         [window doOrderOut];
2819     });
2822 /***********************************************************************
2823  *              macdrv_set_cocoa_window_frame
2825  * Move a Cocoa window.
2826  */
2827 void macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
2829     WineWindow* window = (WineWindow*)w;
2831     OnMainThread(^{
2832         [window setFrameFromWine:NSRectFromCGRect(*new_frame)];
2833     });
2836 /***********************************************************************
2837  *              macdrv_get_cocoa_window_frame
2839  * Gets the frame of a Cocoa window.
2840  */
2841 void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
2843     WineWindow* window = (WineWindow*)w;
2845     OnMainThread(^{
2846         NSRect frame;
2848         frame = [window contentRectForFrameRect:[window frame]];
2849         [[WineApplicationController sharedController] flipRect:&frame];
2850         *out_frame = NSRectToCGRect(frame);
2851     });
2854 /***********************************************************************
2855  *              macdrv_set_cocoa_parent_window
2857  * Sets the parent window for a Cocoa window.  If parent is NULL, clears
2858  * the parent window.
2859  */
2860 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
2862     WineWindow* window = (WineWindow*)w;
2864     OnMainThread(^{
2865         [window setMacDrvParentWindow:(WineWindow*)parent];
2866     });
2869 /***********************************************************************
2870  *              macdrv_set_window_surface
2871  */
2872 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
2874     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2875     WineWindow* window = (WineWindow*)w;
2877     OnMainThread(^{
2878         window.surface = surface;
2879         window.surface_mutex = mutex;
2880     });
2882     [pool release];
2885 /***********************************************************************
2886  *              macdrv_window_needs_display
2888  * Mark a window as needing display in a specified rect (in non-client
2889  * area coordinates).
2890  */
2891 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
2893     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2894     WineWindow* window = (WineWindow*)w;
2896     OnMainThreadAsync(^{
2897         [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
2898     });
2900     [pool release];
2903 /***********************************************************************
2904  *              macdrv_set_window_shape
2906  * Sets the shape of a Cocoa window from an array of rectangles.  If
2907  * rects is NULL, resets the window's shape to its frame.
2908  */
2909 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
2911     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2912     WineWindow* window = (WineWindow*)w;
2914     OnMainThread(^{
2915         if (!rects || !count)
2916         {
2917             window.shape = nil;
2918             window.shapeData = nil;
2919             [window checkEmptyShaped];
2920         }
2921         else
2922         {
2923             size_t length = sizeof(*rects) * count;
2924             if (window.shapeData.length != length || memcmp(window.shapeData.bytes, rects, length))
2925             {
2926                 NSBezierPath* path;
2927                 unsigned int i;
2929                 path = [NSBezierPath bezierPath];
2930                 for (i = 0; i < count; i++)
2931                     [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
2932                 window.shape = path;
2933                 window.shapeData = [NSData dataWithBytes:rects length:length];
2934                 [window checkEmptyShaped];
2935             }
2936         }
2937     });
2939     [pool release];
2942 /***********************************************************************
2943  *              macdrv_set_window_alpha
2944  */
2945 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
2947     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2948     WineWindow* window = (WineWindow*)w;
2950     [window setAlphaValue:alpha];
2952     [pool release];
2955 /***********************************************************************
2956  *              macdrv_set_window_color_key
2957  */
2958 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
2959                                  CGFloat keyBlue)
2961     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2962     WineWindow* window = (WineWindow*)w;
2964     OnMainThread(^{
2965         window.colorKeyed       = TRUE;
2966         window.colorKeyRed      = keyRed;
2967         window.colorKeyGreen    = keyGreen;
2968         window.colorKeyBlue     = keyBlue;
2969         [window checkTransparency];
2970     });
2972     [pool release];
2975 /***********************************************************************
2976  *              macdrv_clear_window_color_key
2977  */
2978 void macdrv_clear_window_color_key(macdrv_window w)
2980     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2981     WineWindow* window = (WineWindow*)w;
2983     OnMainThread(^{
2984         window.colorKeyed = FALSE;
2985         [window checkTransparency];
2986     });
2988     [pool release];
2991 /***********************************************************************
2992  *              macdrv_window_use_per_pixel_alpha
2993  */
2994 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
2996     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2997     WineWindow* window = (WineWindow*)w;
2999     OnMainThread(^{
3000         window.usePerPixelAlpha = use_per_pixel_alpha;
3001         [window checkTransparency];
3002     });
3004     [pool release];
3007 /***********************************************************************
3008  *              macdrv_give_cocoa_window_focus
3010  * Makes the Cocoa window "key" (gives it keyboard focus).  This also
3011  * orders it front and, if its frame was not within the desktop bounds,
3012  * Cocoa will typically move it on-screen.
3013  */
3014 void macdrv_give_cocoa_window_focus(macdrv_window w, int activate)
3016     WineWindow* window = (WineWindow*)w;
3018     OnMainThread(^{
3019         [window makeFocused:activate];
3020     });
3023 /***********************************************************************
3024  *              macdrv_set_window_min_max_sizes
3026  * Sets the window's minimum and maximum content sizes.
3027  */
3028 void macdrv_set_window_min_max_sizes(macdrv_window w, CGSize min_size, CGSize max_size)
3030     WineWindow* window = (WineWindow*)w;
3032     OnMainThread(^{
3033         [window setWineMinSize:NSSizeFromCGSize(min_size) maxSize:NSSizeFromCGSize(max_size)];
3034     });
3037 /***********************************************************************
3038  *              macdrv_create_view
3040  * Creates and returns a view in the specified rect of the window.  The
3041  * caller is responsible for calling macdrv_dispose_view() on the view
3042  * when it is done with it.
3043  */
3044 macdrv_view macdrv_create_view(macdrv_window w, CGRect rect)
3046     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3047     WineWindow* window = (WineWindow*)w;
3048     __block WineContentView* view;
3050     if (CGRectIsNull(rect)) rect = CGRectZero;
3052     OnMainThread(^{
3053         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
3055         view = [[WineContentView alloc] initWithFrame:NSRectFromCGRect(rect)];
3056         [view setAutoresizesSubviews:NO];
3057         [nc addObserver:view
3058                selector:@selector(updateGLContexts)
3059                    name:NSViewGlobalFrameDidChangeNotification
3060                  object:view];
3061         [nc addObserver:view
3062                selector:@selector(updateGLContexts)
3063                    name:NSApplicationDidChangeScreenParametersNotification
3064                  object:NSApp];
3065         [[window contentView] addSubview:view];
3066         [window updateForGLSubviews];
3067     });
3069     [pool release];
3070     return (macdrv_view)view;
3073 /***********************************************************************
3074  *              macdrv_dispose_view
3076  * Destroys a view previously returned by macdrv_create_view.
3077  */
3078 void macdrv_dispose_view(macdrv_view v)
3080     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3081     WineContentView* view = (WineContentView*)v;
3083     OnMainThread(^{
3084         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
3085         WineWindow* window = (WineWindow*)[view window];
3087         [nc removeObserver:view
3088                       name:NSViewGlobalFrameDidChangeNotification
3089                     object:view];
3090         [nc removeObserver:view
3091                       name:NSApplicationDidChangeScreenParametersNotification
3092                     object:NSApp];
3093         [view removeFromSuperview];
3094         [view release];
3095         [window updateForGLSubviews];
3096     });
3098     [pool release];
3101 /***********************************************************************
3102  *              macdrv_set_view_window_and_frame
3104  * Move a view to a new window and/or position within its window.  If w
3105  * is NULL, leave the view in its current window and just change its
3106  * frame.
3107  */
3108 void macdrv_set_view_window_and_frame(macdrv_view v, macdrv_window w, CGRect rect)
3110     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3111     WineContentView* view = (WineContentView*)v;
3112     WineWindow* window = (WineWindow*)w;
3114     if (CGRectIsNull(rect)) rect = CGRectZero;
3116     OnMainThread(^{
3117         BOOL changedWindow = (window && window != [view window]);
3118         NSRect newFrame = NSRectFromCGRect(rect);
3119         NSRect oldFrame = [view frame];
3120         BOOL needUpdateWindowForGLSubviews = FALSE;
3122         if (changedWindow)
3123         {
3124             WineWindow* oldWindow = (WineWindow*)[view window];
3125             [view removeFromSuperview];
3126             [oldWindow updateForGLSubviews];
3127             [[window contentView] addSubview:view];
3128             needUpdateWindowForGLSubviews = TRUE;
3129         }
3131         if (!NSEqualRects(oldFrame, newFrame))
3132         {
3133             if (!changedWindow)
3134                 [[view superview] setNeedsDisplayInRect:oldFrame];
3135             if (NSEqualPoints(oldFrame.origin, newFrame.origin))
3136                 [view setFrameSize:newFrame.size];
3137             else if (NSEqualSizes(oldFrame.size, newFrame.size))
3138                 [view setFrameOrigin:newFrame.origin];
3139             else
3140                 [view setFrame:newFrame];
3141             [view setNeedsDisplay:YES];
3142             needUpdateWindowForGLSubviews = TRUE;
3143         }
3145         if (needUpdateWindowForGLSubviews)
3146             [(WineWindow*)[view window] updateForGLSubviews];
3147     });
3149     [pool release];
3152 /***********************************************************************
3153  *              macdrv_add_view_opengl_context
3155  * Add an OpenGL context to the list being tracked for each view.
3156  */
3157 void macdrv_add_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
3159     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3160     WineContentView* view = (WineContentView*)v;
3161     WineOpenGLContext *context = (WineOpenGLContext*)c;
3163     OnMainThread(^{
3164         [view addGLContext:context];
3165     });
3167     [pool release];
3170 /***********************************************************************
3171  *              macdrv_remove_view_opengl_context
3173  * Add an OpenGL context to the list being tracked for each view.
3174  */
3175 void macdrv_remove_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
3177     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3178     WineContentView* view = (WineContentView*)v;
3179     WineOpenGLContext *context = (WineOpenGLContext*)c;
3181     OnMainThreadAsync(^{
3182         [view removeGLContext:context];
3183     });
3185     [pool release];
3188 /***********************************************************************
3189  *              macdrv_window_background_color
3191  * Returns the standard Mac window background color as a 32-bit value of
3192  * the form 0x00rrggbb.
3193  */
3194 uint32_t macdrv_window_background_color(void)
3196     static uint32_t result;
3197     static dispatch_once_t once;
3199     // Annoyingly, [NSColor windowBackgroundColor] refuses to convert to other
3200     // color spaces (RGB or grayscale).  So, the only way to get RGB values out
3201     // of it is to draw with it.
3202     dispatch_once(&once, ^{
3203         OnMainThread(^{
3204             unsigned char rgbx[4];
3205             unsigned char *planes = rgbx;
3206             NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&planes
3207                                                                                pixelsWide:1
3208                                                                                pixelsHigh:1
3209                                                                             bitsPerSample:8
3210                                                                           samplesPerPixel:3
3211                                                                                  hasAlpha:NO
3212                                                                                  isPlanar:NO
3213                                                                            colorSpaceName:NSCalibratedRGBColorSpace
3214                                                                              bitmapFormat:0
3215                                                                               bytesPerRow:4
3216                                                                              bitsPerPixel:32];
3217             [NSGraphicsContext saveGraphicsState];
3218             [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
3219             [[NSColor windowBackgroundColor] set];
3220             NSRectFill(NSMakeRect(0, 0, 1, 1));
3221             [NSGraphicsContext restoreGraphicsState];
3222             [bitmap release];
3223             result = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
3224         });
3225     });
3227     return result;
3230 /***********************************************************************
3231  *              macdrv_send_text_input_event
3232  */
3233 int macdrv_send_text_input_event(int pressed, unsigned int flags, int repeat, int keyc, void* data)
3235     __block BOOL ret;
3237     OnMainThread(^{
3238         WineWindow* window = (WineWindow*)[NSApp keyWindow];
3239         if (![window isKindOfClass:[WineWindow class]])
3240         {
3241             window = (WineWindow*)[NSApp mainWindow];
3242             if (![window isKindOfClass:[WineWindow class]])
3243                 window = [[WineApplicationController sharedController] frontWineWindow];
3244         }
3246         if (window)
3247         {
3248             NSUInteger localFlags = flags;
3249             CGEventRef c;
3250             NSEvent* event;
3252             window.imeData = data;
3253             fix_device_modifiers_by_generic(&localFlags);
3255             // An NSEvent created with +keyEventWithType:... is internally marked
3256             // as synthetic and doesn't get sent through input methods.  But one
3257             // created from a CGEvent doesn't have that problem.
3258             c = CGEventCreateKeyboardEvent(NULL, keyc, pressed);
3259             CGEventSetFlags(c, localFlags);
3260             CGEventSetIntegerValueField(c, kCGKeyboardEventAutorepeat, repeat);
3261             event = [NSEvent eventWithCGEvent:c];
3262             CFRelease(c);
3264             window.commandDone = FALSE;
3265             ret = [[[window contentView] inputContext] handleEvent:event] && !window.commandDone;
3266         }
3267         else
3268             ret = FALSE;
3269     });
3271     return ret;