winemac: Stop the CVDisplayLink when there are no more changes to flush.
[wine.git] / dlls / winemac.drv / cocoa_window.m
blob15fa16f0153f5e8dba304f505ddfd8ce10f0c6a6
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         [[NSNotificationCenter defaultCenter] removeObserver:self];
781         [queue release];
782         [latentChildWindows release];
783         [latentParentWindow release];
784         [shape release];
785         [shapeData release];
786         [super dealloc];
787     }
789     - (BOOL) preventResizing
790     {
791         BOOL preventForClipping = cursor_clipping_locks_windows && [[WineApplicationController sharedController] clippingCursor];
792         return ([self styleMask] & NSResizableWindowMask) && (disabled || !resizable || preventForClipping);
793     }
795     - (BOOL) allowsMovingWithMaximized:(BOOL)inMaximized
796     {
797         if (allow_immovable_windows && (disabled || inMaximized))
798             return NO;
799         else if (cursor_clipping_locks_windows && [[WineApplicationController sharedController] clippingCursor])
800             return NO;
801         else
802             return YES;
803     }
805     - (void) adjustFeaturesForState
806     {
807         NSUInteger style = [self styleMask];
809         if (style & NSClosableWindowMask)
810             [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
811         if (style & NSMiniaturizableWindowMask)
812             [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
813         if (style & NSResizableWindowMask)
814             [[self standardWindowButton:NSWindowZoomButton] setEnabled:!self.disabled];
815         if ([self respondsToSelector:@selector(toggleFullScreen:)])
816         {
817             if ([self collectionBehavior] & NSWindowCollectionBehaviorFullScreenPrimary)
818                 [[self standardWindowButton:NSWindowFullScreenButton] setEnabled:!self.disabled];
819         }
821         if ([self preventResizing])
822         {
823             NSSize size = [self contentRectForFrameRect:[self frame]].size;
824             [self setContentMinSize:size];
825             [self setContentMaxSize:size];
826         }
827         else
828         {
829             [self setContentMaxSize:savedContentMaxSize];
830             [self setContentMinSize:savedContentMinSize];
831         }
833         if (allow_immovable_windows || cursor_clipping_locks_windows)
834             [self setMovable:[self allowsMovingWithMaximized:maximized]];
835     }
837     - (void) adjustFullScreenBehavior:(NSWindowCollectionBehavior)behavior
838     {
839         if ([self respondsToSelector:@selector(toggleFullScreen:)])
840         {
841             NSUInteger style = [self styleMask];
843             if (behavior & NSWindowCollectionBehaviorParticipatesInCycle &&
844                 style & NSResizableWindowMask && !(style & NSUtilityWindowMask) && !maximized)
845             {
846                 behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
847                 behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary;
848             }
849             else
850             {
851                 behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
852                 behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary;
853                 if (style & NSFullScreenWindowMask)
854                     [super toggleFullScreen:nil];
855             }
856         }
858         if (behavior != [self collectionBehavior])
859         {
860             [self setCollectionBehavior:behavior];
861             [self adjustFeaturesForState];
862         }
863     }
865     - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
866     {
867         static const NSUInteger usedStyles = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask |
868                                              NSResizableWindowMask | NSUtilityWindowMask | NSBorderlessWindowMask;
869         NSUInteger currentStyle = [self styleMask];
870         NSUInteger newStyle = style_mask_for_features(wf) | (currentStyle & ~usedStyles);
872         if (newStyle != currentStyle)
873         {
874             NSString* title = [[[self title] copy] autorelease];
875             BOOL showingButtons = (currentStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
876             BOOL shouldShowButtons = (newStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
877             if (shouldShowButtons != showingButtons && !((newStyle ^ currentStyle) & NSClosableWindowMask))
878             {
879                 // -setStyleMask: is buggy on 10.7+ with respect to NSResizableWindowMask.
880                 // If transitioning from NSTitledWindowMask | NSResizableWindowMask to
881                 // just NSTitledWindowMask, the window buttons should disappear rather
882                 // than just being disabled.  But they don't.  Similarly in reverse.
883                 // The workaround is to also toggle NSClosableWindowMask at the same time.
884                 [self setStyleMask:newStyle ^ NSClosableWindowMask];
885             }
886             [self setStyleMask:newStyle];
888             // -setStyleMask: resets the firstResponder to the window.  Set it
889             // back to the content view.
890             if ([[self contentView] acceptsFirstResponder])
891                 [self makeFirstResponder:[self contentView]];
893             [self adjustFullScreenBehavior:[self collectionBehavior]];
895             if ([[self title] length] == 0 && [title length] > 0)
896                 [self setTitle:title];
897         }
899         resizable = wf->resizable;
900         [self adjustFeaturesForState];
901         [self setHasShadow:wf->shadow];
902     }
904     // Indicates if the window would be visible if the app were not hidden.
905     - (BOOL) wouldBeVisible
906     {
907         return [NSApp isHidden] ? savedVisibleState : [self isVisible];
908     }
910     - (BOOL) isOrderedIn
911     {
912         return [self wouldBeVisible] || [self isMiniaturized];
913     }
915     - (NSInteger) minimumLevelForActive:(BOOL)active
916     {
917         NSInteger level;
919         if (self.floating && (active || topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_ALL ||
920                               (topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_NONFULLSCREEN && !fullscreen)))
921             level = NSFloatingWindowLevel;
922         else
923             level = NSNormalWindowLevel;
925         if (active)
926         {
927             BOOL captured;
929             captured = (fullscreen || [self screen]) && [[WineApplicationController sharedController] areDisplaysCaptured];
931             if (captured || fullscreen)
932             {
933                 if (captured)
934                     level = CGShieldingWindowLevel() + 1; /* Need +1 or we don't get mouse moves */
935                 else
936                     level = NSStatusWindowLevel + 1;
938                 if (self.floating)
939                     level++;
940             }
941         }
943         return level;
944     }
946     - (void) postDidUnminimizeEvent
947     {
948         macdrv_event* event;
950         /* Coalesce events by discarding any previous ones still in the queue. */
951         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_UNMINIMIZE)
952                                forWindow:self];
954         event = macdrv_create_event(WINDOW_DID_UNMINIMIZE, self);
955         [queue postEvent:event];
956         macdrv_release_event(event);
957     }
959     - (void) sendResizeStartQuery
960     {
961         macdrv_query* query = macdrv_create_query();
962         query->type = QUERY_RESIZE_START;
963         query->window = (macdrv_window)[self retain];
965         [self.queue query:query timeout:0.3];
966         macdrv_release_query(query);
967     }
969     - (void) setMacDrvState:(const struct macdrv_window_state*)state
970     {
971         NSWindowCollectionBehavior behavior;
973         self.disabled = state->disabled;
974         self.noActivate = state->no_activate;
976         if (self.floating != state->floating)
977         {
978             self.floating = state->floating;
979             if (state->floating)
980             {
981                 // Became floating.  If child of non-floating window, make that
982                 // relationship latent.
983                 WineWindow* parent = (WineWindow*)[self parentWindow];
984                 if (parent && !parent.floating)
985                     [self becameIneligibleChild];
986             }
987             else
988             {
989                 // Became non-floating.  If parent of floating children, make that
990                 // relationship latent.
991                 WineWindow* child;
992                 for (child in [self childWineWindows])
993                 {
994                     if (child.floating)
995                         [child becameIneligibleChild];
996                 }
997             }
999             // Check our latent relationships.  If floating status was the only
1000             // reason they were latent, then make them active.
1001             if ([self isVisible])
1002                 [self becameEligibleParentOrChild];
1004             [[WineApplicationController sharedController] adjustWindowLevels];
1005         }
1007         if (state->minimized_valid)
1008         {
1009             macdrv_event_mask discard = event_mask_for_type(WINDOW_DID_UNMINIMIZE);
1011             pendingMinimize = FALSE;
1012             if (state->minimized && ![self isMiniaturized])
1013             {
1014                 if ([self wouldBeVisible])
1015                 {
1016                     if ([self styleMask] & NSFullScreenWindowMask)
1017                     {
1018                         [self postDidUnminimizeEvent];
1019                         discard &= ~event_mask_for_type(WINDOW_DID_UNMINIMIZE);
1020                     }
1021                     else
1022                     {
1023                         [super miniaturize:nil];
1024                         discard |= event_mask_for_type(WINDOW_BROUGHT_FORWARD) |
1025                                    event_mask_for_type(WINDOW_GOT_FOCUS) |
1026                                    event_mask_for_type(WINDOW_LOST_FOCUS);
1027                     }
1028                 }
1029                 else
1030                     pendingMinimize = TRUE;
1031             }
1032             else if (!state->minimized && [self isMiniaturized])
1033             {
1034                 ignore_windowDeminiaturize = TRUE;
1035                 [self deminiaturize:nil];
1036                 discard |= event_mask_for_type(WINDOW_LOST_FOCUS);
1037             }
1039             if (discard)
1040                 [queue discardEventsMatchingMask:discard forWindow:self];
1041         }
1043         if (state->maximized != maximized)
1044         {
1045             maximized = state->maximized;
1046             [self adjustFeaturesForState];
1048             if (!maximized && [self inLiveResize])
1049                 [self sendResizeStartQuery];
1050         }
1052         behavior = NSWindowCollectionBehaviorDefault;
1053         if (state->excluded_by_expose)
1054             behavior |= NSWindowCollectionBehaviorTransient;
1055         else
1056             behavior |= NSWindowCollectionBehaviorManaged;
1057         if (state->excluded_by_cycle)
1058         {
1059             behavior |= NSWindowCollectionBehaviorIgnoresCycle;
1060             if ([self isOrderedIn])
1061                 [NSApp removeWindowsItem:self];
1062         }
1063         else
1064         {
1065             behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
1066             if ([self isOrderedIn])
1067                 [NSApp addWindowsItem:self title:[self title] filename:NO];
1068         }
1069         [self adjustFullScreenBehavior:behavior];
1070     }
1072     - (BOOL) addChildWineWindow:(WineWindow*)child assumeVisible:(BOOL)assumeVisible
1073     {
1074         BOOL reordered = FALSE;
1076         if ([self isVisible] && (assumeVisible || [child isVisible]) && (self.floating || !child.floating))
1077         {
1078             if ([self level] > [child level])
1079                 [child setLevel:[self level]];
1080             [self addChildWindow:child ordered:NSWindowAbove];
1081             [child checkWineDisplayLink];
1082             [latentChildWindows removeObjectIdenticalTo:child];
1083             child.latentParentWindow = nil;
1084             reordered = TRUE;
1085         }
1086         else
1087         {
1088             if (!latentChildWindows)
1089                 latentChildWindows = [[NSMutableArray alloc] init];
1090             if (![latentChildWindows containsObject:child])
1091                 [latentChildWindows addObject:child];
1092             child.latentParentWindow = self;
1093         }
1095         return reordered;
1096     }
1098     - (BOOL) addChildWineWindow:(WineWindow*)child
1099     {
1100         return [self addChildWineWindow:child assumeVisible:FALSE];
1101     }
1103     - (void) removeChildWineWindow:(WineWindow*)child
1104     {
1105         [self removeChildWindow:child];
1106         if (child.latentParentWindow == self)
1107             child.latentParentWindow = nil;
1108         [latentChildWindows removeObjectIdenticalTo:child];
1109     }
1111     - (BOOL) becameEligibleParentOrChild
1112     {
1113         BOOL reordered = FALSE;
1114         NSUInteger count;
1116         if (latentParentWindow.floating || !self.floating)
1117         {
1118             // If we aren't visible currently, we assume that we should be and soon
1119             // will be.  So, if the latent parent is visible that's enough to assume
1120             // we can establish the parent-child relationship in Cocoa.  That will
1121             // actually make us visible, which is fine.
1122             if ([latentParentWindow addChildWineWindow:self assumeVisible:TRUE])
1123                 reordered = TRUE;
1124         }
1126         // Here, though, we may not actually be visible yet and adding a child
1127         // won't make us visible.  The caller will have to call this method
1128         // again after actually making us visible.
1129         if ([self isVisible] && (count = [latentChildWindows count]))
1130         {
1131             NSMutableIndexSet* indexesToRemove = [NSMutableIndexSet indexSet];
1132             NSUInteger i;
1134             for (i = 0; i < count; i++)
1135             {
1136                 WineWindow* child = [latentChildWindows objectAtIndex:i];
1137                 if ([child isVisible] && (self.floating || !child.floating))
1138                 {
1139                     if (child.latentParentWindow == self)
1140                     {
1141                         if ([self level] > [child level])
1142                             [child setLevel:[self level]];
1143                         [self addChildWindow:child ordered:NSWindowAbove];
1144                         child.latentParentWindow = nil;
1145                         reordered = TRUE;
1146                     }
1147                     else
1148                         ERR(@"shouldn't happen: %@ thinks %@ is a latent child, but it doesn't agree\n", self, child);
1149                     [indexesToRemove addIndex:i];
1150                 }
1151             }
1153             [latentChildWindows removeObjectsAtIndexes:indexesToRemove];
1154         }
1156         return reordered;
1157     }
1159     - (void) becameIneligibleChild
1160     {
1161         WineWindow* parent = (WineWindow*)[self parentWindow];
1162         if (parent)
1163         {
1164             if (!parent->latentChildWindows)
1165                 parent->latentChildWindows = [[NSMutableArray alloc] init];
1166             [parent->latentChildWindows insertObject:self atIndex:0];
1167             self.latentParentWindow = parent;
1168             [parent removeChildWindow:self];
1169         }
1170     }
1172     - (void) becameIneligibleParentOrChild
1173     {
1174         NSArray* childWindows = [self childWineWindows];
1176         [self becameIneligibleChild];
1178         if ([childWindows count])
1179         {
1180             WineWindow* child;
1182             for (child in childWindows)
1183             {
1184                 child.latentParentWindow = self;
1185                 [self removeChildWindow:child];
1186             }
1188             if (latentChildWindows)
1189                 [latentChildWindows replaceObjectsInRange:NSMakeRange(0, 0) withObjectsFromArray:childWindows];
1190             else
1191                 latentChildWindows = [childWindows mutableCopy];
1192         }
1193     }
1195     // Determine if, among Wine windows, this window is directly above or below
1196     // a given other Wine window with no other Wine window intervening.
1197     // Intervening non-Wine windows are ignored.
1198     - (BOOL) isOrdered:(NSWindowOrderingMode)orderingMode relativeTo:(WineWindow*)otherWindow
1199     {
1200         NSNumber* windowNumber;
1201         NSNumber* otherWindowNumber;
1202         NSArray* windowNumbers;
1203         NSUInteger windowIndex, otherWindowIndex, lowIndex, highIndex, i;
1205         if (![self isVisible] || ![otherWindow isVisible])
1206             return FALSE;
1208         windowNumber = [NSNumber numberWithInteger:[self windowNumber]];
1209         otherWindowNumber = [NSNumber numberWithInteger:[otherWindow windowNumber]];
1210         windowNumbers = [[self class] windowNumbersWithOptions:0];
1211         windowIndex = [windowNumbers indexOfObject:windowNumber];
1212         otherWindowIndex = [windowNumbers indexOfObject:otherWindowNumber];
1214         if (windowIndex == NSNotFound || otherWindowIndex == NSNotFound)
1215             return FALSE;
1217         if (orderingMode == NSWindowAbove)
1218         {
1219             lowIndex = windowIndex;
1220             highIndex = otherWindowIndex;
1221         }
1222         else if (orderingMode == NSWindowBelow)
1223         {
1224             lowIndex = otherWindowIndex;
1225             highIndex = windowIndex;
1226         }
1227         else
1228             return FALSE;
1230         if (highIndex <= lowIndex)
1231             return FALSE;
1233         for (i = lowIndex + 1; i < highIndex; i++)
1234         {
1235             NSInteger interveningWindowNumber = [[windowNumbers objectAtIndex:i] integerValue];
1236             NSWindow* interveningWindow = [NSApp windowWithWindowNumber:interveningWindowNumber];
1237             if ([interveningWindow isKindOfClass:[WineWindow class]])
1238                 return FALSE;
1239         }
1241         return TRUE;
1242     }
1244     - (void) order:(NSWindowOrderingMode)mode childWindow:(WineWindow*)child relativeTo:(WineWindow*)other
1245     {
1246         NSMutableArray* windowNumbers;
1247         NSNumber* childWindowNumber;
1248         NSUInteger otherIndex, limit;
1249         NSArray* origChildren;
1250         NSMutableArray* children;
1252         // Get the z-order from the window server and modify it to reflect the
1253         // requested window ordering.
1254         windowNumbers = [[[[self class] windowNumbersWithOptions:NSWindowNumberListAllSpaces] mutableCopy] autorelease];
1255         childWindowNumber = [NSNumber numberWithInteger:[child windowNumber]];
1256         [windowNumbers removeObject:childWindowNumber];
1257         otherIndex = [windowNumbers indexOfObject:[NSNumber numberWithInteger:[other windowNumber]]];
1258         [windowNumbers insertObject:childWindowNumber atIndex:otherIndex + (mode == NSWindowAbove ? 0 : 1)];
1260         // Get our child windows and sort them in the reverse of the desired
1261         // z-order (back-to-front).
1262         origChildren = [self childWineWindows];
1263         children = [[origChildren mutableCopy] autorelease];
1264         [children sortWithOptions:NSSortStable
1265                   usingComparator:^NSComparisonResult(id obj1, id obj2){
1266             NSNumber* window1Number = [NSNumber numberWithInteger:[obj1 windowNumber]];
1267             NSNumber* window2Number = [NSNumber numberWithInteger:[obj2 windowNumber]];
1268             NSUInteger index1 = [windowNumbers indexOfObject:window1Number];
1269             NSUInteger index2 = [windowNumbers indexOfObject:window2Number];
1270             if (index1 == NSNotFound)
1271             {
1272                 if (index2 == NSNotFound)
1273                     return NSOrderedSame;
1274                 else
1275                     return NSOrderedAscending;
1276             }
1277             else if (index2 == NSNotFound)
1278                 return NSOrderedDescending;
1279             else if (index1 < index2)
1280                 return NSOrderedDescending;
1281             else if (index2 < index1)
1282                 return NSOrderedAscending;
1284             return NSOrderedSame;
1285         }];
1287         // If the current and desired children arrays match up to a point, leave
1288         // those matching children alone.
1289         limit = MIN([origChildren count], [children count]);
1290         for (otherIndex = 0; otherIndex < limit; otherIndex++)
1291         {
1292             if ([origChildren objectAtIndex:otherIndex] != [children objectAtIndex:otherIndex])
1293                 break;
1294         }
1295         [children removeObjectsInRange:NSMakeRange(0, otherIndex)];
1297         // Remove all of the child windows and re-add them back-to-front so they
1298         // are in the desired order.
1299         for (other in children)
1300             [self removeChildWindow:other];
1301         for (other in children)
1302             [self addChildWindow:other ordered:NSWindowAbove];
1303     }
1305     /* Returns whether or not the window was ordered in, which depends on if
1306        its frame intersects any screen. */
1307     - (void) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next activate:(BOOL)activate
1308     {
1309         WineApplicationController* controller = [WineApplicationController sharedController];
1310         if (![self isMiniaturized])
1311         {
1312             BOOL needAdjustWindowLevels = FALSE;
1313             BOOL wasVisible;
1315             [controller transformProcessToForeground];
1316             [NSApp unhide:nil];
1317             wasVisible = [self isVisible];
1319             if (activate)
1320                 [NSApp activateIgnoringOtherApps:YES];
1322             NSDisableScreenUpdates();
1324             if ([self becameEligibleParentOrChild])
1325                 needAdjustWindowLevels = TRUE;
1327             if (prev || next)
1328             {
1329                 WineWindow* other = [prev isVisible] ? prev : next;
1330                 NSWindowOrderingMode orderingMode = [prev isVisible] ? NSWindowBelow : NSWindowAbove;
1332                 if (![self isOrdered:orderingMode relativeTo:other])
1333                 {
1334                     WineWindow* parent = (WineWindow*)[self parentWindow];
1335                     WineWindow* otherParent = (WineWindow*)[other parentWindow];
1337                     // This window level may not be right for this window based
1338                     // on floating-ness, fullscreen-ness, etc.  But we set it
1339                     // temporarily to allow us to order the windows properly.
1340                     // Then the levels get fixed by -adjustWindowLevels.
1341                     if ([self level] != [other level])
1342                         [self setLevel:[other level]];
1343                     [self orderWindow:orderingMode relativeTo:[other windowNumber]];
1344                     [self checkWineDisplayLink];
1346                     // The above call to -[NSWindow orderWindow:relativeTo:] won't
1347                     // reorder windows which are both children of the same parent
1348                     // relative to each other, so do that separately.
1349                     if (parent && parent == otherParent)
1350                         [parent order:orderingMode childWindow:self relativeTo:other];
1352                     needAdjustWindowLevels = TRUE;
1353                 }
1354             }
1355             else
1356             {
1357                 // Again, temporarily set level to make sure we can order to
1358                 // the right place.
1359                 next = [controller frontWineWindow];
1360                 if (next && [self level] < [next level])
1361                     [self setLevel:[next level]];
1362                 [self orderFront:nil];
1363                 [self checkWineDisplayLink];
1364                 needAdjustWindowLevels = TRUE;
1365             }
1367             if ([self becameEligibleParentOrChild])
1368                 needAdjustWindowLevels = TRUE;
1370             if (needAdjustWindowLevels)
1371             {
1372                 if (!wasVisible && fullscreen && [self isOnActiveSpace])
1373                     [controller updateFullscreenWindows];
1374                 [controller adjustWindowLevels];
1375             }
1377             if (pendingMinimize)
1378             {
1379                 [super miniaturize:nil];
1380                 pendingMinimize = FALSE;
1381             }
1383             NSEnableScreenUpdates();
1385             /* Cocoa may adjust the frame when the window is ordered onto the screen.
1386                Generate a frame-changed event just in case.  The back end will ignore
1387                it if nothing actually changed. */
1388             [self windowDidResize:nil];
1390             if (![self isExcludedFromWindowsMenu])
1391                 [NSApp addWindowsItem:self title:[self title] filename:NO];
1392         }
1393     }
1395     - (void) doOrderOut
1396     {
1397         WineApplicationController* controller = [WineApplicationController sharedController];
1398         BOOL wasVisible = [self isVisible];
1399         BOOL wasOnActiveSpace = [self isOnActiveSpace];
1401         if ([self isMiniaturized])
1402             pendingMinimize = TRUE;
1404         WineWindow* parent = (WineWindow*)self.parentWindow;
1405         if ([parent isKindOfClass:[WineWindow class]])
1406             [parent grabDockIconSnapshotFromWindow:self force:NO];
1408         [self becameIneligibleParentOrChild];
1409         if ([self isMiniaturized])
1410         {
1411             fakingClose = TRUE;
1412             [self close];
1413             fakingClose = FALSE;
1414         }
1415         else
1416             [self orderOut:nil];
1417         [self checkWineDisplayLink];
1418         savedVisibleState = FALSE;
1419         if (wasVisible && wasOnActiveSpace && fullscreen)
1420             [controller updateFullscreenWindows];
1421         [controller adjustWindowLevels];
1422         [NSApp removeWindowsItem:self];
1424         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD) |
1425                                          event_mask_for_type(WINDOW_GOT_FOCUS) |
1426                                          event_mask_for_type(WINDOW_LOST_FOCUS) |
1427                                          event_mask_for_type(WINDOW_MAXIMIZE_REQUESTED) |
1428                                          event_mask_for_type(WINDOW_MINIMIZE_REQUESTED) |
1429                                          event_mask_for_type(WINDOW_RESTORE_REQUESTED)
1430                                forWindow:self];
1431     }
1433     - (void) updateFullscreen
1434     {
1435         NSRect contentRect = [self contentRectForFrameRect:[self frame]];
1436         BOOL nowFullscreen = !([self styleMask] & NSFullScreenWindowMask) && screen_covered_by_rect(contentRect, [NSScreen screens]);
1438         if (nowFullscreen != fullscreen)
1439         {
1440             WineApplicationController* controller = [WineApplicationController sharedController];
1442             fullscreen = nowFullscreen;
1443             if ([self isVisible] && [self isOnActiveSpace])
1444                 [controller updateFullscreenWindows];
1446             [controller adjustWindowLevels];
1447         }
1448     }
1450     - (void) setFrameFromWine:(NSRect)contentRect
1451     {
1452         /* Origin is (left, top) in a top-down space.  Need to convert it to
1453            (left, bottom) in a bottom-up space. */
1454         [[WineApplicationController sharedController] flipRect:&contentRect];
1456         /* The back end is establishing a new window size and position.  It's
1457            not interested in any stale events regarding those that may be sitting
1458            in the queue. */
1459         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
1460                                forWindow:self];
1462         if (!NSIsEmptyRect(contentRect))
1463         {
1464             NSRect frame, oldFrame;
1466             oldFrame = [self frame];
1467             frame = [self frameRectForContentRect:contentRect];
1468             if (!NSEqualRects(frame, oldFrame))
1469             {
1470                 BOOL equalSizes = NSEqualSizes(frame.size, oldFrame.size);
1471                 BOOL needEnableScreenUpdates = FALSE;
1473                 if ([self preventResizing])
1474                 {
1475                     // Allow the following calls to -setFrame:display: to work even
1476                     // if they would violate the content size constraints. This
1477                     // shouldn't be necessary since the content size constraints are
1478                     // documented to not constrain that method, but it seems to be.
1479                     [self setContentMinSize:NSZeroSize];
1480                     [self setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
1481                 }
1483                 if (equalSizes && [[self childWineWindows] count])
1484                 {
1485                     // If we change the window frame such that the origin moves
1486                     // but the size doesn't change, then Cocoa moves child
1487                     // windows with the parent.  We don't want that so we fake
1488                     // a change of the size and then change it back.
1489                     NSRect bogusFrame = frame;
1490                     bogusFrame.size.width++;
1492                     NSDisableScreenUpdates();
1493                     needEnableScreenUpdates = TRUE;
1495                     ignore_windowResize = TRUE;
1496                     [self setFrame:bogusFrame display:NO];
1497                     ignore_windowResize = FALSE;
1498                 }
1500                 [self setFrame:frame display:YES];
1501                 if ([self preventResizing])
1502                 {
1503                     [self setContentMinSize:contentRect.size];
1504                     [self setContentMaxSize:contentRect.size];
1505                 }
1507                 if (needEnableScreenUpdates)
1508                     NSEnableScreenUpdates();
1510                 if (!equalSizes)
1511                     [self updateColorSpace];
1513                 if (!enteringFullScreen &&
1514                     [[NSProcessInfo processInfo] systemUptime] - enteredFullScreenTime > 1.0)
1515                     nonFullscreenFrame = frame;
1517                 [self updateFullscreen];
1519                 if ([self isOrderedIn])
1520                 {
1521                     /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
1522                        event.  The back end will ignore it if nothing actually changed. */
1523                     [self windowDidResize:nil];
1524                 }
1525             }
1526         }
1527     }
1529     - (void) setMacDrvParentWindow:(WineWindow*)parent
1530     {
1531         WineWindow* oldParent = (WineWindow*)[self parentWindow];
1532         if ((oldParent && oldParent != parent) || (!oldParent && latentParentWindow != parent))
1533         {
1534             [oldParent removeChildWineWindow:self];
1535             [latentParentWindow removeChildWineWindow:self];
1536             if ([parent addChildWineWindow:self])
1537                 [[WineApplicationController sharedController] adjustWindowLevels];
1538         }
1539     }
1541     - (void) setDisabled:(BOOL)newValue
1542     {
1543         if (disabled != newValue)
1544         {
1545             disabled = newValue;
1546             [self adjustFeaturesForState];
1547         }
1548     }
1550     - (BOOL) needsTransparency
1551     {
1552         return self.shape || self.colorKeyed || self.usePerPixelAlpha ||
1553                 (gl_surface_mode == GL_SURFACE_BEHIND && [[self.contentView valueForKeyPath:@"subviews.@max.hasGLContext"] boolValue]);
1554     }
1556     - (void) checkTransparency
1557     {
1558         if (![self isOpaque] && !self.needsTransparency)
1559         {
1560             self.shapeChangedSinceLastDraw = TRUE;
1561             [[self contentView] setNeedsDisplay:YES];
1562             [self setBackgroundColor:[NSColor windowBackgroundColor]];
1563             [self setOpaque:YES];
1564         }
1565         else if ([self isOpaque] && self.needsTransparency)
1566         {
1567             self.shapeChangedSinceLastDraw = TRUE;
1568             [[self contentView] setNeedsDisplay:YES];
1569             [self setBackgroundColor:[NSColor clearColor]];
1570             [self setOpaque:NO];
1571         }
1572     }
1574     - (void) setShape:(NSBezierPath*)newShape
1575     {
1576         if (shape == newShape) return;
1578         if (shape)
1579         {
1580             [[self contentView] setNeedsDisplayInRect:[shape bounds]];
1581             [shape release];
1582         }
1583         if (newShape)
1584             [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
1586         shape = [newShape copy];
1587         self.shapeChangedSinceLastDraw = TRUE;
1589         [self checkTransparency];
1590     }
1592     - (void) makeFocused:(BOOL)activate
1593     {
1594         if (activate)
1595         {
1596             [[WineApplicationController sharedController] transformProcessToForeground];
1597             [NSApp activateIgnoringOtherApps:YES];
1598         }
1600         causing_becomeKeyWindow = self;
1601         [self makeKeyWindow];
1602         causing_becomeKeyWindow = nil;
1604         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS) |
1605                                          event_mask_for_type(WINDOW_LOST_FOCUS)
1606                                forWindow:self];
1607     }
1609     - (void) postKey:(uint16_t)keyCode
1610              pressed:(BOOL)pressed
1611            modifiers:(NSUInteger)modifiers
1612                event:(NSEvent*)theEvent
1613     {
1614         macdrv_event* event;
1615         CGEventRef cgevent;
1616         WineApplicationController* controller = [WineApplicationController sharedController];
1618         event = macdrv_create_event(pressed ? KEY_PRESS : KEY_RELEASE, self);
1619         event->key.keycode   = keyCode;
1620         event->key.modifiers = modifiers;
1621         event->key.time_ms   = [controller ticksForEventTime:[theEvent timestamp]];
1623         if ((cgevent = [theEvent CGEvent]))
1624         {
1625             CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent,
1626                                                         kCGKeyboardEventKeyboardType);
1627             if (keyboardType != controller.keyboardType)
1628             {
1629                 controller.keyboardType = keyboardType;
1630                 [controller keyboardSelectionDidChange];
1631             }
1632         }
1634         [queue postEvent:event];
1636         macdrv_release_event(event);
1638         [controller noteKey:keyCode pressed:pressed];
1639     }
1641     - (void) postKeyEvent:(NSEvent *)theEvent
1642     {
1643         [self flagsChanged:theEvent];
1644         [self postKey:[theEvent keyCode]
1645               pressed:[theEvent type] == NSKeyDown
1646             modifiers:adjusted_modifiers_for_option_behavior([theEvent modifierFlags])
1647                 event:theEvent];
1648     }
1650     - (void) setWineMinSize:(NSSize)minSize maxSize:(NSSize)maxSize
1651     {
1652         savedContentMinSize = minSize;
1653         savedContentMaxSize = maxSize;
1654         if (![self preventResizing])
1655         {
1656             [self setContentMinSize:minSize];
1657             [self setContentMaxSize:maxSize];
1658         }
1659     }
1661     - (WineWindow*) ancestorWineWindow
1662     {
1663         WineWindow* ancestor = self;
1664         for (;;)
1665         {
1666             WineWindow* parent = (WineWindow*)[ancestor parentWindow];
1667             if ([parent isKindOfClass:[WineWindow class]])
1668                 ancestor = parent;
1669             else
1670                 break;
1671         }
1672         return ancestor;
1673     }
1675     - (void) postBroughtForwardEvent
1676     {
1677         macdrv_event* event = macdrv_create_event(WINDOW_BROUGHT_FORWARD, self);
1678         [queue postEvent:event];
1679         macdrv_release_event(event);
1680     }
1682     - (void) updateForCursorClipping
1683     {
1684         [self adjustFeaturesForState];
1685     }
1687     - (void) endWindowDragging
1688     {
1689         if (draggingPhase)
1690         {
1691             if (draggingPhase == 3)
1692             {
1693                 macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, self);
1694                 [queue postEvent:event];
1695                 macdrv_release_event(event);
1696             }
1698             draggingPhase = 0;
1699             [[WineApplicationController sharedController] window:self isBeingDragged:NO];
1700         }
1701     }
1703     - (NSMutableDictionary*) displayIDToDisplayLinkMap
1704     {
1705         static NSMutableDictionary* displayIDToDisplayLinkMap;
1706         if (!displayIDToDisplayLinkMap)
1707         {
1708             displayIDToDisplayLinkMap = [[NSMutableDictionary alloc] init];
1710             [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationDidChangeScreenParametersNotification
1711                                                               object:NSApp
1712                                                                queue:nil
1713                                                           usingBlock:^(NSNotification *note){
1714                 NSMutableSet* badDisplayIDs = [NSMutableSet setWithArray:displayIDToDisplayLinkMap.allKeys];
1715                 NSSet* validDisplayIDs = [NSSet setWithArray:[[NSScreen screens] valueForKeyPath:@"deviceDescription.NSScreenNumber"]];
1716                 [badDisplayIDs minusSet:validDisplayIDs];
1717                 [displayIDToDisplayLinkMap removeObjectsForKeys:[badDisplayIDs allObjects]];
1718             }];
1719         }
1720         return displayIDToDisplayLinkMap;
1721     }
1723     - (WineDisplayLink*) wineDisplayLink
1724     {
1725         if (!_lastDisplayID)
1726             return nil;
1728         NSMutableDictionary* displayIDToDisplayLinkMap = [self displayIDToDisplayLinkMap];
1729         return [displayIDToDisplayLinkMap objectForKey:[NSNumber numberWithUnsignedInt:_lastDisplayID]];
1730     }
1732     - (void) checkWineDisplayLink
1733     {
1734         NSScreen* screen = self.screen;
1735         if (![self isVisible] || ![self isOnActiveSpace] || [self isMiniaturized] || [self isEmptyShaped])
1736             screen = nil;
1737 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
1738         if ([self respondsToSelector:@selector(occlusionState)] && !(self.occlusionState & NSWindowOcclusionStateVisible))
1739             screen = nil;
1740 #endif
1742         NSNumber* displayIDNumber = [screen.deviceDescription objectForKey:@"NSScreenNumber"];
1743         CGDirectDisplayID displayID = [displayIDNumber unsignedIntValue];
1744         if (displayID == _lastDisplayID)
1745             return;
1747         NSMutableDictionary* displayIDToDisplayLinkMap = [self displayIDToDisplayLinkMap];
1749         if (_lastDisplayID)
1750         {
1751             WineDisplayLink* link = [displayIDToDisplayLinkMap objectForKey:[NSNumber numberWithUnsignedInt:_lastDisplayID]];
1752             [link removeWindow:self];
1753         }
1754         if (displayID)
1755         {
1756             WineDisplayLink* link = [displayIDToDisplayLinkMap objectForKey:displayIDNumber];
1757             if (!link)
1758             {
1759                 link = [[[WineDisplayLink alloc] initWithDisplayID:displayID] autorelease];
1760                 [displayIDToDisplayLinkMap setObject:link forKey:displayIDNumber];
1761             }
1762             [link addWindow:self];
1763             [self displayIfNeeded];
1764         }
1765         _lastDisplayID = displayID;
1766     }
1768     - (BOOL) isEmptyShaped
1769     {
1770         return (self.shapeData.length == sizeof(CGRectZero) && !memcmp(self.shapeData.bytes, &CGRectZero, sizeof(CGRectZero)));
1771     }
1773     - (BOOL) canProvideSnapshot
1774     {
1775         return (self.windowNumber > 0 && ![self isEmptyShaped]);
1776     }
1778     - (void) grabDockIconSnapshotFromWindow:(WineWindow*)window force:(BOOL)force
1779     {
1780         if (![self isEmptyShaped])
1781             return;
1783         NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
1784         if (!force && now < lastDockIconSnapshot + 1)
1785             return;
1787         if (window)
1788         {
1789             if (![window canProvideSnapshot])
1790                 return;
1791         }
1792         else
1793         {
1794             CGFloat bestArea;
1795             for (WineWindow* childWindow in self.childWindows)
1796             {
1797                 if (![childWindow isKindOfClass:[WineWindow class]] || ![childWindow canProvideSnapshot])
1798                     continue;
1800                 NSSize size = childWindow.frame.size;
1801                 CGFloat area = size.width * size.height;
1802                 if (!window || area > bestArea)
1803                 {
1804                     window = childWindow;
1805                     bestArea = area;
1806                 }
1807             }
1809             if (!window)
1810                 return;
1811         }
1813         const void* windowID = (const void*)(CGWindowID)window.windowNumber;
1814         CFArrayRef windowIDs = CFArrayCreate(NULL, &windowID, 1, NULL);
1815         CGImageRef windowImage = CGWindowListCreateImageFromArray(CGRectNull, windowIDs, kCGWindowImageBoundsIgnoreFraming);
1816         CFRelease(windowIDs);
1817         if (!windowImage)
1818             return;
1820         NSImage* appImage = [NSApp applicationIconImage];
1821         if (!appImage)
1822             appImage = [NSImage imageNamed:NSImageNameApplicationIcon];
1824         NSImage* dockIcon = [[[NSImage alloc] initWithSize:NSMakeSize(256, 256)] autorelease];
1825         [dockIcon lockFocus];
1827         CGContextRef cgcontext = [[NSGraphicsContext currentContext] graphicsPort];
1829         CGRect rect = CGRectMake(8, 8, 240, 240);
1830         size_t width = CGImageGetWidth(windowImage);
1831         size_t height = CGImageGetHeight(windowImage);
1832         if (width > height)
1833         {
1834             rect.size.height *= height / (double)width;
1835             rect.origin.y += (CGRectGetWidth(rect) - CGRectGetHeight(rect)) / 2;
1836         }
1837         else if (width != height)
1838         {
1839             rect.size.width *= width / (double)height;
1840             rect.origin.x += (CGRectGetHeight(rect) - CGRectGetWidth(rect)) / 2;
1841         }
1843         CGContextDrawImage(cgcontext, rect, windowImage);
1844         [appImage drawInRect:NSMakeRect(156, 4, 96, 96)];
1846         [dockIcon unlockFocus];
1848         CGImageRelease(windowImage);
1850         NSImageView* imageView = (NSImageView*)self.dockTile.contentView;
1851         if (![imageView isKindOfClass:[NSImageView class]])
1852         {
1853             imageView = [[[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 256, 256)] autorelease];
1854             imageView.imageScaling = NSImageScaleProportionallyUpOrDown;
1855             self.dockTile.contentView = imageView;
1856         }
1857         imageView.image = dockIcon;
1858         [self.dockTile display];
1859         lastDockIconSnapshot = now;
1860     }
1862     - (void) checkEmptyShaped
1863     {
1864         if (self.dockTile.contentView && ![self isEmptyShaped])
1865         {
1866             self.dockTile.contentView = nil;
1867             lastDockIconSnapshot = 0;
1868         }
1869         [self checkWineDisplayLink];
1870     }
1873     /*
1874      * ---------- NSWindow method overrides ----------
1875      */
1876     - (BOOL) canBecomeKeyWindow
1877     {
1878         if (causing_becomeKeyWindow == self) return YES;
1879         if (self.disabled || self.noActivate) return NO;
1880         return [self isKeyWindow];
1881     }
1883     - (BOOL) canBecomeMainWindow
1884     {
1885         return [self canBecomeKeyWindow];
1886     }
1888     - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
1889     {
1890         // If a window is sized to completely cover a screen, then it's in
1891         // full-screen mode.  In that case, we don't allow NSWindow to constrain
1892         // it.
1893         NSArray* screens = [NSScreen screens];
1894         NSRect contentRect = [self contentRectForFrameRect:frameRect];
1895         if (!screen_covered_by_rect(contentRect, screens) &&
1896             frame_intersects_screens(frameRect, screens))
1897             frameRect = [super constrainFrameRect:frameRect toScreen:screen];
1898         return frameRect;
1899     }
1901     // This private method of NSWindow is called as Cocoa reacts to the display
1902     // configuration changing.  Among other things, it adjusts the window's
1903     // frame based on how the screen(s) changed size.  That tells Wine that the
1904     // window has been moved.  We don't want that.  Rather, we want to make
1905     // sure that the WinAPI notion of the window position is maintained/
1906     // restored, possibly undoing or overriding Cocoa's adjustment.
1907     //
1908     // So, we queue a REASSERT_WINDOW_POSITION event to the back end before
1909     // Cocoa has a chance to adjust the frame, thus preceding any resulting
1910     // WINDOW_FRAME_CHANGED event that may get queued.  The back end will
1911     // reassert its notion of the position.  That call won't get processed
1912     // until after this method returns, so it will override whatever this
1913     // method does to the window position.  It will also discard any pending
1914     // WINDOW_FRAME_CHANGED events.
1915     //
1916     // Unfortunately, the only way I've found to know when Cocoa is _about to_
1917     // adjust the window's position due to a display change is to hook into
1918     // this private method.  This private method has remained stable from 10.6
1919     // through 10.11.  If it does change, the most likely thing is that it
1920     // will be removed and no longer called and this fix will simply stop
1921     // working.  The only real danger would be if Apple changed the return type
1922     // to a struct or floating-point type, which would change the calling
1923     // convention.
1924     - (id) _displayChanged
1925     {
1926         macdrv_event* event = macdrv_create_event(REASSERT_WINDOW_POSITION, self);
1927         [queue postEvent:event];
1928         macdrv_release_event(event);
1930         return [super _displayChanged];
1931     }
1933     - (BOOL) isExcludedFromWindowsMenu
1934     {
1935         return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
1936     }
1938     - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
1939     {
1940         BOOL ret = [super validateMenuItem:menuItem];
1942         if ([menuItem action] == @selector(makeKeyAndOrderFront:))
1943             ret = [self isKeyWindow] || (!self.disabled && !self.noActivate);
1944         if ([menuItem action] == @selector(toggleFullScreen:) && (self.disabled || maximized))
1945             ret = NO;
1947         return ret;
1948     }
1950     /* We don't call this.  It's the action method of the items in the Window menu. */
1951     - (void) makeKeyAndOrderFront:(id)sender
1952     {
1953         if ([self isMiniaturized])
1954             [self deminiaturize:nil];
1955         [self orderBelow:nil orAbove:nil activate:NO];
1956         [[self ancestorWineWindow] postBroughtForwardEvent];
1958         if (![self isKeyWindow] && !self.disabled && !self.noActivate)
1959             [[WineApplicationController sharedController] windowGotFocus:self];
1960     }
1962     - (void) sendEvent:(NSEvent*)event
1963     {
1964         NSEventType type = event.type;
1966         /* NSWindow consumes certain key-down events as part of Cocoa's keyboard
1967            interface control.  For example, Control-Tab switches focus among
1968            views.  We want to bypass that feature, so directly route key-down
1969            events to -keyDown:. */
1970         if (type == NSKeyDown)
1971             [[self firstResponder] keyDown:event];
1972         else
1973         {
1974             if (!draggingPhase && maximized && ![self isMovable] &&
1975                 ![self allowsMovingWithMaximized:YES] && [self allowsMovingWithMaximized:NO] &&
1976                 type == NSLeftMouseDown && (self.styleMask & NSTitledWindowMask))
1977             {
1978                 NSRect titleBar = self.frame;
1979                 NSRect contentRect = [self contentRectForFrameRect:titleBar];
1980                 titleBar.size.height = NSMaxY(titleBar) - NSMaxY(contentRect);
1981                 titleBar.origin.y = NSMaxY(contentRect);
1983                 dragStartPosition = [self convertBaseToScreen:event.locationInWindow];
1985                 if (NSMouseInRect(dragStartPosition, titleBar, NO))
1986                 {
1987                     static const NSWindowButton buttons[] = {
1988                         NSWindowCloseButton,
1989                         NSWindowMiniaturizeButton,
1990                         NSWindowZoomButton,
1991                         NSWindowFullScreenButton,
1992                     };
1993                     BOOL hitButton = NO;
1994                     int i;
1996                     for (i = 0; i < sizeof(buttons) / sizeof(buttons[0]); i++)
1997                     {
1998                         NSButton* button;
2000                         if (buttons[i] == NSWindowFullScreenButton && ![self respondsToSelector:@selector(toggleFullScreen:)])
2001                             continue;
2003                         button = [self standardWindowButton:buttons[i]];
2004                         if ([button hitTest:[button.superview convertPoint:event.locationInWindow fromView:nil]])
2005                         {
2006                             hitButton = YES;
2007                             break;
2008                         }
2009                     }
2011                     if (!hitButton)
2012                     {
2013                         draggingPhase = 1;
2014                         dragWindowStartPosition = NSMakePoint(NSMinX(titleBar), NSMaxY(titleBar));
2015                         [[WineApplicationController sharedController] window:self isBeingDragged:YES];
2016                     }
2017                 }
2018             }
2019             else if (draggingPhase && (type == NSLeftMouseDragged || type == NSLeftMouseUp))
2020             {
2021                 if ([self isMovable])
2022                 {
2023                     NSPoint point = [self convertBaseToScreen:event.locationInWindow];
2024                     NSPoint newTopLeft = dragWindowStartPosition;
2026                     newTopLeft.x += point.x - dragStartPosition.x;
2027                     newTopLeft.y += point.y - dragStartPosition.y;
2029                     if (draggingPhase == 2)
2030                     {
2031                         macdrv_event* event = macdrv_create_event(WINDOW_DRAG_BEGIN, self);
2032                         [queue postEvent:event];
2033                         macdrv_release_event(event);
2035                         draggingPhase = 3;
2036                     }
2038                     [self setFrameTopLeftPoint:newTopLeft];
2039                 }
2040                 else if (draggingPhase == 1 && type == NSLeftMouseDragged)
2041                 {
2042                     macdrv_event* event;
2043                     NSRect frame = [self contentRectForFrameRect:self.frame];
2045                     [[WineApplicationController sharedController] flipRect:&frame];
2047                     event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self);
2048                     event->window_restore_requested.keep_frame = TRUE;
2049                     event->window_restore_requested.frame = NSRectToCGRect(frame);
2050                     [queue postEvent:event];
2051                     macdrv_release_event(event);
2053                     draggingPhase = 2;
2054                 }
2056                 if (type == NSLeftMouseUp)
2057                     [self endWindowDragging];
2058             }
2060             [super sendEvent:event];
2061         }
2062     }
2064     - (void) miniaturize:(id)sender
2065     {
2066         macdrv_event* event = macdrv_create_event(WINDOW_MINIMIZE_REQUESTED, self);
2067         [queue postEvent:event];
2068         macdrv_release_event(event);
2070         WineWindow* parent = (WineWindow*)self.parentWindow;
2071         if ([parent isKindOfClass:[WineWindow class]])
2072             [parent grabDockIconSnapshotFromWindow:self force:YES];
2073     }
2075     - (void) toggleFullScreen:(id)sender
2076     {
2077         if (!self.disabled && !maximized)
2078             [super toggleFullScreen:sender];
2079     }
2081     - (void) setViewsNeedDisplay:(BOOL)value
2082     {
2083         if (value && ![self viewsNeedDisplay])
2084         {
2085             WineDisplayLink* link = [self wineDisplayLink];
2086             if (link)
2087             {
2088                 NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
2089                 if (_lastDisplayTime + [link refreshPeriod] < now)
2090                     [self setAutodisplay:YES];
2091                 else
2092                 {
2093                     [link start];
2094                     _lastDisplayTime = now;
2095                 }
2096             }
2097         }
2098         [super setViewsNeedDisplay:value];
2099     }
2101     - (void) display
2102     {
2103         _lastDisplayTime = [[NSProcessInfo processInfo] systemUptime];
2104         [super display];
2105         [self setAutodisplay:NO];
2106     }
2108     - (void) displayIfNeeded
2109     {
2110         _lastDisplayTime = [[NSProcessInfo processInfo] systemUptime];
2111         [super displayIfNeeded];
2112         [self setAutodisplay:NO];
2113     }
2115     - (NSArray*) childWineWindows
2116     {
2117         NSArray* childWindows = self.childWindows;
2118         NSIndexSet* indexes = [childWindows indexesOfObjectsPassingTest:^BOOL(id child, NSUInteger idx, BOOL *stop){
2119             return [child isKindOfClass:[WineWindow class]];
2120         }];
2121         return [childWindows objectsAtIndexes:indexes];
2122     }
2124     // We normally use the generic/calibrated RGB color space for the window,
2125     // rather than the device color space, to avoid expensive color conversion
2126     // which slows down drawing.  However, for windows displaying OpenGL, having
2127     // a different color space than the screen greatly reduces frame rates, often
2128     // limiting it to the display refresh rate.
2129     //
2130     // To avoid this, we switch back to the screen color space whenever the
2131     // window is covered by a view with an attached OpenGL context.
2132     - (void) updateColorSpace
2133     {
2134         NSRect contentRect = [[self contentView] frame];
2135         BOOL coveredByGLView = FALSE;
2136         for (WineContentView* view in [[self contentView] subviews])
2137         {
2138             if ([view hasGLContext])
2139             {
2140                 NSRect frame = [view convertRect:[view bounds] toView:nil];
2141                 if (NSContainsRect(frame, contentRect))
2142                 {
2143                     coveredByGLView = TRUE;
2144                     break;
2145                 }
2146             }
2147         }
2149         if (coveredByGLView)
2150             [self setColorSpace:nil];
2151         else
2152             [self setColorSpace:[NSColorSpace genericRGBColorSpace]];
2153     }
2155     - (void) updateForGLSubviews
2156     {
2157         [self updateColorSpace];
2158         if (gl_surface_mode == GL_SURFACE_BEHIND)
2159             [self checkTransparency];
2160     }
2163     /*
2164      * ---------- NSResponder method overrides ----------
2165      */
2166     - (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; }
2168     - (void) flagsChanged:(NSEvent *)theEvent
2169     {
2170         static const struct {
2171             NSUInteger  mask;
2172             uint16_t    keycode;
2173         } modifiers[] = {
2174             { NX_ALPHASHIFTMASK,        kVK_CapsLock },
2175             { NX_DEVICELSHIFTKEYMASK,   kVK_Shift },
2176             { NX_DEVICERSHIFTKEYMASK,   kVK_RightShift },
2177             { NX_DEVICELCTLKEYMASK,     kVK_Control },
2178             { NX_DEVICERCTLKEYMASK,     kVK_RightControl },
2179             { NX_DEVICELALTKEYMASK,     kVK_Option },
2180             { NX_DEVICERALTKEYMASK,     kVK_RightOption },
2181             { NX_DEVICELCMDKEYMASK,     kVK_Command },
2182             { NX_DEVICERCMDKEYMASK,     kVK_RightCommand },
2183         };
2185         NSUInteger modifierFlags = adjusted_modifiers_for_option_behavior([theEvent modifierFlags]);
2186         NSUInteger changed;
2187         int i, last_changed;
2189         fix_device_modifiers_by_generic(&modifierFlags);
2190         changed = modifierFlags ^ lastModifierFlags;
2192         last_changed = -1;
2193         for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++)
2194             if (changed & modifiers[i].mask)
2195                 last_changed = i;
2197         for (i = 0; i <= last_changed; i++)
2198         {
2199             if (changed & modifiers[i].mask)
2200             {
2201                 BOOL pressed = (modifierFlags & modifiers[i].mask) != 0;
2203                 if (i == last_changed)
2204                     lastModifierFlags = modifierFlags;
2205                 else
2206                 {
2207                     lastModifierFlags ^= modifiers[i].mask;
2208                     fix_generic_modifiers_by_device(&lastModifierFlags);
2209                 }
2211                 // Caps lock generates one event for each press-release action.
2212                 // We need to simulate a pair of events for each actual event.
2213                 if (modifiers[i].mask == NX_ALPHASHIFTMASK)
2214                 {
2215                     [self postKey:modifiers[i].keycode
2216                           pressed:TRUE
2217                         modifiers:lastModifierFlags
2218                             event:(NSEvent*)theEvent];
2219                     pressed = FALSE;
2220                 }
2222                 [self postKey:modifiers[i].keycode
2223                       pressed:pressed
2224                     modifiers:lastModifierFlags
2225                         event:(NSEvent*)theEvent];
2226             }
2227         }
2228     }
2230     - (void) applicationWillHide
2231     {
2232         savedVisibleState = [self isVisible];
2233     }
2235     - (void) applicationDidUnhide
2236     {
2237         if ([self isVisible])
2238             [self becameEligibleParentOrChild];
2239     }
2242     /*
2243      * ---------- NSWindowDelegate methods ----------
2244      */
2245     - (NSSize) window:(NSWindow*)window willUseFullScreenContentSize:(NSSize)proposedSize
2246     {
2247         macdrv_query* query;
2248         NSSize size;
2250         query = macdrv_create_query();
2251         query->type = QUERY_MIN_MAX_INFO;
2252         query->window = (macdrv_window)[self retain];
2253         [self.queue query:query timeout:0.5];
2254         macdrv_release_query(query);
2256         size = [self contentMaxSize];
2257         if (proposedSize.width < size.width)
2258             size.width = proposedSize.width;
2259         if (proposedSize.height < size.height)
2260             size.height = proposedSize.height;
2261         return size;
2262     }
2264     - (void)windowDidBecomeKey:(NSNotification *)notification
2265     {
2266         WineApplicationController* controller = [WineApplicationController sharedController];
2267         NSEvent* event = [controller lastFlagsChanged];
2268         if (event)
2269             [self flagsChanged:event];
2271         if (causing_becomeKeyWindow == self) return;
2273         [controller windowGotFocus:self];
2274     }
2276     - (void) windowDidChangeOcclusionState:(NSNotification*)notification
2277     {
2278         [self checkWineDisplayLink];
2279     }
2281     - (void) windowDidChangeScreen:(NSNotification*)notification
2282     {
2283         [self checkWineDisplayLink];
2284     }
2286     - (void)windowDidDeminiaturize:(NSNotification *)notification
2287     {
2288         WineApplicationController* controller = [WineApplicationController sharedController];
2290         if (!ignore_windowDeminiaturize)
2291             [self postDidUnminimizeEvent];
2292         ignore_windowDeminiaturize = FALSE;
2294         [self becameEligibleParentOrChild];
2296         if (fullscreen && [self isOnActiveSpace])
2297             [controller updateFullscreenWindows];
2298         [controller adjustWindowLevels];
2300         if (![self parentWindow])
2301             [self postBroughtForwardEvent];
2303         if (!self.disabled && !self.noActivate)
2304         {
2305             causing_becomeKeyWindow = self;
2306             [self makeKeyWindow];
2307             causing_becomeKeyWindow = nil;
2308             [controller windowGotFocus:self];
2309         }
2311         [self windowDidResize:notification];
2312         [self checkWineDisplayLink];
2313     }
2315     - (void) windowDidEndLiveResize:(NSNotification *)notification
2316     {
2317         if (!maximized)
2318         {
2319             macdrv_event* event = macdrv_create_event(WINDOW_RESIZE_ENDED, self);
2320             [queue postEvent:event];
2321             macdrv_release_event(event);
2322         }
2323     }
2325     - (void) windowDidEnterFullScreen:(NSNotification*)notification
2326     {
2327         enteringFullScreen = FALSE;
2328         enteredFullScreenTime = [[NSProcessInfo processInfo] systemUptime];
2329     }
2331     - (void) windowDidExitFullScreen:(NSNotification*)notification
2332     {
2333         exitingFullScreen = FALSE;
2334         [self setFrame:nonFullscreenFrame display:YES animate:NO];
2335         [self windowDidResize:nil];
2336     }
2338     - (void) windowDidFailToEnterFullScreen:(NSWindow*)window
2339     {
2340         enteringFullScreen = FALSE;
2341         enteredFullScreenTime = 0;
2342     }
2344     - (void) windowDidFailToExitFullScreen:(NSWindow*)window
2345     {
2346         exitingFullScreen = FALSE;
2347         [self windowDidResize:nil];
2348     }
2350     - (void)windowDidMiniaturize:(NSNotification *)notification
2351     {
2352         if (fullscreen && [self isOnActiveSpace])
2353             [[WineApplicationController sharedController] updateFullscreenWindows];
2354         [self checkWineDisplayLink];
2355     }
2357     - (void)windowDidMove:(NSNotification *)notification
2358     {
2359         [self windowDidResize:notification];
2360     }
2362     - (void)windowDidResignKey:(NSNotification *)notification
2363     {
2364         macdrv_event* event;
2366         if (causing_becomeKeyWindow) return;
2368         event = macdrv_create_event(WINDOW_LOST_FOCUS, self);
2369         [queue postEvent:event];
2370         macdrv_release_event(event);
2371     }
2373     - (void)windowDidResize:(NSNotification *)notification
2374     {
2375         macdrv_event* event;
2376         NSRect frame = [self frame];
2378         if ([self inLiveResize])
2379         {
2380             if (NSMinX(frame) != NSMinX(frameAtResizeStart))
2381                 resizingFromLeft = TRUE;
2382             if (NSMaxY(frame) != NSMaxY(frameAtResizeStart))
2383                 resizingFromTop = TRUE;
2384         }
2386         frame = [self contentRectForFrameRect:frame];
2388         if (ignore_windowResize || exitingFullScreen) return;
2390         if ([self preventResizing])
2391         {
2392             [self setContentMinSize:frame.size];
2393             [self setContentMaxSize:frame.size];
2394         }
2396         [[WineApplicationController sharedController] flipRect:&frame];
2398         /* Coalesce events by discarding any previous ones still in the queue. */
2399         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
2400                                forWindow:self];
2402         event = macdrv_create_event(WINDOW_FRAME_CHANGED, self);
2403         event->window_frame_changed.frame = NSRectToCGRect(frame);
2404         event->window_frame_changed.fullscreen = ([self styleMask] & NSFullScreenWindowMask) != 0;
2405         event->window_frame_changed.in_resize = [self inLiveResize];
2406         [queue postEvent:event];
2407         macdrv_release_event(event);
2409         [[[self contentView] inputContext] invalidateCharacterCoordinates];
2410         [self updateFullscreen];
2411     }
2413     - (BOOL)windowShouldClose:(id)sender
2414     {
2415         macdrv_event* event = macdrv_create_event(WINDOW_CLOSE_REQUESTED, self);
2416         [queue postEvent:event];
2417         macdrv_release_event(event);
2418         return NO;
2419     }
2421     - (BOOL) windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame
2422     {
2423         if (maximized)
2424         {
2425             macdrv_event* event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self);
2426             [queue postEvent:event];
2427             macdrv_release_event(event);
2428             return NO;
2429         }
2430         else if (!resizable)
2431         {
2432             macdrv_event* event = macdrv_create_event(WINDOW_MAXIMIZE_REQUESTED, self);
2433             [queue postEvent:event];
2434             macdrv_release_event(event);
2435             return NO;
2436         }
2438         return YES;
2439     }
2441     - (void) windowWillClose:(NSNotification*)notification
2442     {
2443         WineWindow* child;
2445         if (fakingClose) return;
2446         if (latentParentWindow)
2447         {
2448             [latentParentWindow->latentChildWindows removeObjectIdenticalTo:self];
2449             self.latentParentWindow = nil;
2450         }
2452         for (child in latentChildWindows)
2453         {
2454             if (child.latentParentWindow == self)
2455                 child.latentParentWindow = nil;
2456         }
2457         [latentChildWindows removeAllObjects];
2458     }
2460     - (void) windowWillEnterFullScreen:(NSNotification*)notification
2461     {
2462         enteringFullScreen = TRUE;
2463         nonFullscreenFrame = [self frame];
2464     }
2466     - (void) windowWillExitFullScreen:(NSNotification*)notification
2467     {
2468         exitingFullScreen = TRUE;
2469     }
2471     - (void)windowWillMiniaturize:(NSNotification *)notification
2472     {
2473         [self becameIneligibleParentOrChild];
2474         [self grabDockIconSnapshotFromWindow:nil force:NO];
2475     }
2477     - (NSSize) windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize
2478     {
2479         if ([self inLiveResize])
2480         {
2481             if (maximized)
2482                 return self.frame.size;
2484             NSRect rect;
2485             macdrv_query* query;
2487             rect = [self frame];
2488             if (resizingFromLeft)
2489                 rect.origin.x = NSMaxX(rect) - frameSize.width;
2490             if (!resizingFromTop)
2491                 rect.origin.y = NSMaxY(rect) - frameSize.height;
2492             rect.size = frameSize;
2493             rect = [self contentRectForFrameRect:rect];
2494             [[WineApplicationController sharedController] flipRect:&rect];
2496             query = macdrv_create_query();
2497             query->type = QUERY_RESIZE_SIZE;
2498             query->window = (macdrv_window)[self retain];
2499             query->resize_size.rect = NSRectToCGRect(rect);
2500             query->resize_size.from_left = resizingFromLeft;
2501             query->resize_size.from_top = resizingFromTop;
2503             if ([self.queue query:query timeout:0.1])
2504             {
2505                 rect = NSRectFromCGRect(query->resize_size.rect);
2506                 rect = [self frameRectForContentRect:rect];
2507                 frameSize = rect.size;
2508             }
2510             macdrv_release_query(query);
2511         }
2513         return frameSize;
2514     }
2516     - (void) windowWillStartLiveResize:(NSNotification *)notification
2517     {
2518         [self endWindowDragging];
2520         if (maximized)
2521         {
2522             macdrv_event* event;
2523             NSRect frame = [self contentRectForFrameRect:self.frame];
2525             [[WineApplicationController sharedController] flipRect:&frame];
2527             event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self);
2528             event->window_restore_requested.keep_frame = TRUE;
2529             event->window_restore_requested.frame = NSRectToCGRect(frame);
2530             [queue postEvent:event];
2531             macdrv_release_event(event);
2532         }
2533         else
2534             [self sendResizeStartQuery];
2536         frameAtResizeStart = [self frame];
2537         resizingFromLeft = resizingFromTop = FALSE;
2538     }
2540     - (NSRect) windowWillUseStandardFrame:(NSWindow*)window defaultFrame:(NSRect)proposedFrame
2541     {
2542         macdrv_query* query;
2543         NSRect currentContentRect, proposedContentRect, newContentRect, screenRect;
2544         NSSize maxSize;
2546         query = macdrv_create_query();
2547         query->type = QUERY_MIN_MAX_INFO;
2548         query->window = (macdrv_window)[self retain];
2549         [self.queue query:query timeout:0.5];
2550         macdrv_release_query(query);
2552         currentContentRect = [self contentRectForFrameRect:[self frame]];
2553         proposedContentRect = [self contentRectForFrameRect:proposedFrame];
2555         maxSize = [self contentMaxSize];
2556         newContentRect.size.width = MIN(NSWidth(proposedContentRect), maxSize.width);
2557         newContentRect.size.height = MIN(NSHeight(proposedContentRect), maxSize.height);
2559         // Try to keep the top-left corner where it is.
2560         newContentRect.origin.x = NSMinX(currentContentRect);
2561         newContentRect.origin.y = NSMaxY(currentContentRect) - NSHeight(newContentRect);
2563         // If that pushes the bottom or right off the screen, pull it up and to the left.
2564         screenRect = [self contentRectForFrameRect:[[self screen] visibleFrame]];
2565         if (NSMaxX(newContentRect) > NSMaxX(screenRect))
2566             newContentRect.origin.x = NSMaxX(screenRect) - NSWidth(newContentRect);
2567         if (NSMinY(newContentRect) < NSMinY(screenRect))
2568             newContentRect.origin.y = NSMinY(screenRect);
2570         // If that pushes the top or left off the screen, push it down and the right
2571         // again.  Do this last because the top-left corner is more important than the
2572         // bottom-right.
2573         if (NSMinX(newContentRect) < NSMinX(screenRect))
2574             newContentRect.origin.x = NSMinX(screenRect);
2575         if (NSMaxY(newContentRect) > NSMaxY(screenRect))
2576             newContentRect.origin.y = NSMaxY(screenRect) - NSHeight(newContentRect);
2578         return [self frameRectForContentRect:newContentRect];
2579     }
2582     /*
2583      * ---------- NSPasteboardOwner methods ----------
2584      */
2585     - (void) pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
2586     {
2587         macdrv_query* query = macdrv_create_query();
2588         query->type = QUERY_PASTEBOARD_DATA;
2589         query->window = (macdrv_window)[self retain];
2590         query->pasteboard_data.type = (CFStringRef)[type copy];
2592         [self.queue query:query timeout:3];
2593         macdrv_release_query(query);
2594     }
2597     /*
2598      * ---------- NSDraggingDestination methods ----------
2599      */
2600     - (NSDragOperation) draggingEntered:(id <NSDraggingInfo>)sender
2601     {
2602         return [self draggingUpdated:sender];
2603     }
2605     - (void) draggingExited:(id <NSDraggingInfo>)sender
2606     {
2607         // This isn't really a query.  We don't need any response.  However, it
2608         // has to be processed in a similar manner as the other drag-and-drop
2609         // queries in order to maintain the proper order of operations.
2610         macdrv_query* query = macdrv_create_query();
2611         query->type = QUERY_DRAG_EXITED;
2612         query->window = (macdrv_window)[self retain];
2614         [self.queue query:query timeout:0.1];
2615         macdrv_release_query(query);
2616     }
2618     - (NSDragOperation) draggingUpdated:(id <NSDraggingInfo>)sender
2619     {
2620         NSDragOperation ret;
2621         NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
2622         NSPasteboard* pb = [sender draggingPasteboard];
2624         macdrv_query* query = macdrv_create_query();
2625         query->type = QUERY_DRAG_OPERATION;
2626         query->window = (macdrv_window)[self retain];
2627         query->drag_operation.x = pt.x;
2628         query->drag_operation.y = pt.y;
2629         query->drag_operation.offered_ops = [sender draggingSourceOperationMask];
2630         query->drag_operation.accepted_op = NSDragOperationNone;
2631         query->drag_operation.pasteboard = (CFTypeRef)[pb retain];
2633         [self.queue query:query timeout:3];
2634         ret = query->status ? query->drag_operation.accepted_op : NSDragOperationNone;
2635         macdrv_release_query(query);
2637         return ret;
2638     }
2640     - (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
2641     {
2642         BOOL ret;
2643         NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
2644         NSPasteboard* pb = [sender draggingPasteboard];
2646         macdrv_query* query = macdrv_create_query();
2647         query->type = QUERY_DRAG_DROP;
2648         query->window = (macdrv_window)[self retain];
2649         query->drag_drop.x = pt.x;
2650         query->drag_drop.y = pt.y;
2651         query->drag_drop.op = [sender draggingSourceOperationMask];
2652         query->drag_drop.pasteboard = (CFTypeRef)[pb retain];
2654         [self.queue query:query timeout:3 * 60 processEvents:YES];
2655         ret = query->status;
2656         macdrv_release_query(query);
2658         return ret;
2659     }
2661     - (BOOL) wantsPeriodicDraggingUpdates
2662     {
2663         return NO;
2664     }
2666 @end
2669 /***********************************************************************
2670  *              macdrv_create_cocoa_window
2672  * Create a Cocoa window with the given content frame and features (e.g.
2673  * title bar, close box, etc.).
2674  */
2675 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
2676         CGRect frame, void* hwnd, macdrv_event_queue queue)
2678     __block WineWindow* window;
2680     OnMainThread(^{
2681         window = [[WineWindow createWindowWithFeatures:wf
2682                                            windowFrame:NSRectFromCGRect(frame)
2683                                                   hwnd:hwnd
2684                                                  queue:(WineEventQueue*)queue] retain];
2685     });
2687     return (macdrv_window)window;
2690 /***********************************************************************
2691  *              macdrv_destroy_cocoa_window
2693  * Destroy a Cocoa window.
2694  */
2695 void macdrv_destroy_cocoa_window(macdrv_window w)
2697     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2698     WineWindow* window = (WineWindow*)w;
2700     OnMainThread(^{
2701         [window doOrderOut];
2702         [window close];
2703     });
2704     [window.queue discardEventsMatchingMask:-1 forWindow:window];
2705     [window release];
2707     [pool release];
2710 /***********************************************************************
2711  *              macdrv_get_window_hwnd
2713  * Get the hwnd that was set for the window at creation.
2714  */
2715 void* macdrv_get_window_hwnd(macdrv_window w)
2717     WineWindow* window = (WineWindow*)w;
2718     return window.hwnd;
2721 /***********************************************************************
2722  *              macdrv_set_cocoa_window_features
2724  * Update a Cocoa window's features.
2725  */
2726 void macdrv_set_cocoa_window_features(macdrv_window w,
2727         const struct macdrv_window_features* wf)
2729     WineWindow* window = (WineWindow*)w;
2731     OnMainThread(^{
2732         [window setWindowFeatures:wf];
2733     });
2736 /***********************************************************************
2737  *              macdrv_set_cocoa_window_state
2739  * Update a Cocoa window's state.
2740  */
2741 void macdrv_set_cocoa_window_state(macdrv_window w,
2742         const struct macdrv_window_state* state)
2744     WineWindow* window = (WineWindow*)w;
2746     OnMainThread(^{
2747         [window setMacDrvState:state];
2748     });
2751 /***********************************************************************
2752  *              macdrv_set_cocoa_window_title
2754  * Set a Cocoa window's title.
2755  */
2756 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
2757         size_t length)
2759     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2760     WineWindow* window = (WineWindow*)w;
2761     NSString* titleString;
2763     if (title)
2764         titleString = [NSString stringWithCharacters:title length:length];
2765     else
2766         titleString = @"";
2767     OnMainThreadAsync(^{
2768         [window setTitle:titleString];
2769         if ([window isOrderedIn] && ![window isExcludedFromWindowsMenu])
2770             [NSApp changeWindowsItem:window title:titleString filename:NO];
2771     });
2773     [pool release];
2776 /***********************************************************************
2777  *              macdrv_order_cocoa_window
2779  * Reorder a Cocoa window relative to other windows.  If prev is
2780  * non-NULL, it is ordered below that window.  Else, if next is non-NULL,
2781  * it is ordered above that window.  Otherwise, it is ordered to the
2782  * front.
2783  */
2784 void macdrv_order_cocoa_window(macdrv_window w, macdrv_window p,
2785         macdrv_window n, int activate)
2787     WineWindow* window = (WineWindow*)w;
2788     WineWindow* prev = (WineWindow*)p;
2789     WineWindow* next = (WineWindow*)n;
2791     OnMainThreadAsync(^{
2792         [window orderBelow:prev
2793                    orAbove:next
2794                   activate:activate];
2795     });
2796     [window.queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD)
2797                                   forWindow:window];
2798     [next.queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD)
2799                                 forWindow:next];
2802 /***********************************************************************
2803  *              macdrv_hide_cocoa_window
2805  * Hides a Cocoa window.
2806  */
2807 void macdrv_hide_cocoa_window(macdrv_window w)
2809     WineWindow* window = (WineWindow*)w;
2811     OnMainThread(^{
2812         [window doOrderOut];
2813     });
2816 /***********************************************************************
2817  *              macdrv_set_cocoa_window_frame
2819  * Move a Cocoa window.
2820  */
2821 void macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
2823     WineWindow* window = (WineWindow*)w;
2825     OnMainThread(^{
2826         [window setFrameFromWine:NSRectFromCGRect(*new_frame)];
2827     });
2830 /***********************************************************************
2831  *              macdrv_get_cocoa_window_frame
2833  * Gets the frame of a Cocoa window.
2834  */
2835 void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
2837     WineWindow* window = (WineWindow*)w;
2839     OnMainThread(^{
2840         NSRect frame;
2842         frame = [window contentRectForFrameRect:[window frame]];
2843         [[WineApplicationController sharedController] flipRect:&frame];
2844         *out_frame = NSRectToCGRect(frame);
2845     });
2848 /***********************************************************************
2849  *              macdrv_set_cocoa_parent_window
2851  * Sets the parent window for a Cocoa window.  If parent is NULL, clears
2852  * the parent window.
2853  */
2854 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
2856     WineWindow* window = (WineWindow*)w;
2858     OnMainThread(^{
2859         [window setMacDrvParentWindow:(WineWindow*)parent];
2860     });
2863 /***********************************************************************
2864  *              macdrv_set_window_surface
2865  */
2866 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
2868     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2869     WineWindow* window = (WineWindow*)w;
2871     OnMainThread(^{
2872         window.surface = surface;
2873         window.surface_mutex = mutex;
2874     });
2876     [pool release];
2879 /***********************************************************************
2880  *              macdrv_window_needs_display
2882  * Mark a window as needing display in a specified rect (in non-client
2883  * area coordinates).
2884  */
2885 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
2887     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2888     WineWindow* window = (WineWindow*)w;
2890     OnMainThreadAsync(^{
2891         [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
2892     });
2894     [pool release];
2897 /***********************************************************************
2898  *              macdrv_set_window_shape
2900  * Sets the shape of a Cocoa window from an array of rectangles.  If
2901  * rects is NULL, resets the window's shape to its frame.
2902  */
2903 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
2905     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2906     WineWindow* window = (WineWindow*)w;
2908     OnMainThread(^{
2909         if (!rects || !count)
2910         {
2911             window.shape = nil;
2912             window.shapeData = nil;
2913             [window checkEmptyShaped];
2914         }
2915         else
2916         {
2917             size_t length = sizeof(*rects) * count;
2918             if (window.shapeData.length != length || memcmp(window.shapeData.bytes, rects, length))
2919             {
2920                 NSBezierPath* path;
2921                 unsigned int i;
2923                 path = [NSBezierPath bezierPath];
2924                 for (i = 0; i < count; i++)
2925                     [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
2926                 window.shape = path;
2927                 window.shapeData = [NSData dataWithBytes:rects length:length];
2928                 [window checkEmptyShaped];
2929             }
2930         }
2931     });
2933     [pool release];
2936 /***********************************************************************
2937  *              macdrv_set_window_alpha
2938  */
2939 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
2941     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2942     WineWindow* window = (WineWindow*)w;
2944     [window setAlphaValue:alpha];
2946     [pool release];
2949 /***********************************************************************
2950  *              macdrv_set_window_color_key
2951  */
2952 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
2953                                  CGFloat keyBlue)
2955     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2956     WineWindow* window = (WineWindow*)w;
2958     OnMainThread(^{
2959         window.colorKeyed       = TRUE;
2960         window.colorKeyRed      = keyRed;
2961         window.colorKeyGreen    = keyGreen;
2962         window.colorKeyBlue     = keyBlue;
2963         [window checkTransparency];
2964     });
2966     [pool release];
2969 /***********************************************************************
2970  *              macdrv_clear_window_color_key
2971  */
2972 void macdrv_clear_window_color_key(macdrv_window w)
2974     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2975     WineWindow* window = (WineWindow*)w;
2977     OnMainThread(^{
2978         window.colorKeyed = FALSE;
2979         [window checkTransparency];
2980     });
2982     [pool release];
2985 /***********************************************************************
2986  *              macdrv_window_use_per_pixel_alpha
2987  */
2988 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
2990     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2991     WineWindow* window = (WineWindow*)w;
2993     OnMainThread(^{
2994         window.usePerPixelAlpha = use_per_pixel_alpha;
2995         [window checkTransparency];
2996     });
2998     [pool release];
3001 /***********************************************************************
3002  *              macdrv_give_cocoa_window_focus
3004  * Makes the Cocoa window "key" (gives it keyboard focus).  This also
3005  * orders it front and, if its frame was not within the desktop bounds,
3006  * Cocoa will typically move it on-screen.
3007  */
3008 void macdrv_give_cocoa_window_focus(macdrv_window w, int activate)
3010     WineWindow* window = (WineWindow*)w;
3012     OnMainThread(^{
3013         [window makeFocused:activate];
3014     });
3017 /***********************************************************************
3018  *              macdrv_set_window_min_max_sizes
3020  * Sets the window's minimum and maximum content sizes.
3021  */
3022 void macdrv_set_window_min_max_sizes(macdrv_window w, CGSize min_size, CGSize max_size)
3024     WineWindow* window = (WineWindow*)w;
3026     OnMainThread(^{
3027         [window setWineMinSize:NSSizeFromCGSize(min_size) maxSize:NSSizeFromCGSize(max_size)];
3028     });
3031 /***********************************************************************
3032  *              macdrv_create_view
3034  * Creates and returns a view in the specified rect of the window.  The
3035  * caller is responsible for calling macdrv_dispose_view() on the view
3036  * when it is done with it.
3037  */
3038 macdrv_view macdrv_create_view(macdrv_window w, CGRect rect)
3040     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3041     WineWindow* window = (WineWindow*)w;
3042     __block WineContentView* view;
3044     if (CGRectIsNull(rect)) rect = CGRectZero;
3046     OnMainThread(^{
3047         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
3049         view = [[WineContentView alloc] initWithFrame:NSRectFromCGRect(rect)];
3050         [view setAutoresizesSubviews:NO];
3051         [nc addObserver:view
3052                selector:@selector(updateGLContexts)
3053                    name:NSViewGlobalFrameDidChangeNotification
3054                  object:view];
3055         [nc addObserver:view
3056                selector:@selector(updateGLContexts)
3057                    name:NSApplicationDidChangeScreenParametersNotification
3058                  object:NSApp];
3059         [[window contentView] addSubview:view];
3060         [window updateForGLSubviews];
3061     });
3063     [pool release];
3064     return (macdrv_view)view;
3067 /***********************************************************************
3068  *              macdrv_dispose_view
3070  * Destroys a view previously returned by macdrv_create_view.
3071  */
3072 void macdrv_dispose_view(macdrv_view v)
3074     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3075     WineContentView* view = (WineContentView*)v;
3077     OnMainThread(^{
3078         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
3079         WineWindow* window = (WineWindow*)[view window];
3081         [nc removeObserver:view
3082                       name:NSViewGlobalFrameDidChangeNotification
3083                     object:view];
3084         [nc removeObserver:view
3085                       name:NSApplicationDidChangeScreenParametersNotification
3086                     object:NSApp];
3087         [view removeFromSuperview];
3088         [view release];
3089         [window updateForGLSubviews];
3090     });
3092     [pool release];
3095 /***********************************************************************
3096  *              macdrv_set_view_window_and_frame
3098  * Move a view to a new window and/or position within its window.  If w
3099  * is NULL, leave the view in its current window and just change its
3100  * frame.
3101  */
3102 void macdrv_set_view_window_and_frame(macdrv_view v, macdrv_window w, CGRect rect)
3104     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3105     WineContentView* view = (WineContentView*)v;
3106     WineWindow* window = (WineWindow*)w;
3108     if (CGRectIsNull(rect)) rect = CGRectZero;
3110     OnMainThread(^{
3111         BOOL changedWindow = (window && window != [view window]);
3112         NSRect newFrame = NSRectFromCGRect(rect);
3113         NSRect oldFrame = [view frame];
3114         BOOL needUpdateWindowForGLSubviews = FALSE;
3116         if (changedWindow)
3117         {
3118             WineWindow* oldWindow = (WineWindow*)[view window];
3119             [view removeFromSuperview];
3120             [oldWindow updateForGLSubviews];
3121             [[window contentView] addSubview:view];
3122             needUpdateWindowForGLSubviews = TRUE;
3123         }
3125         if (!NSEqualRects(oldFrame, newFrame))
3126         {
3127             if (!changedWindow)
3128                 [[view superview] setNeedsDisplayInRect:oldFrame];
3129             if (NSEqualPoints(oldFrame.origin, newFrame.origin))
3130                 [view setFrameSize:newFrame.size];
3131             else if (NSEqualSizes(oldFrame.size, newFrame.size))
3132                 [view setFrameOrigin:newFrame.origin];
3133             else
3134                 [view setFrame:newFrame];
3135             [view setNeedsDisplay:YES];
3136             needUpdateWindowForGLSubviews = TRUE;
3137         }
3139         if (needUpdateWindowForGLSubviews)
3140             [(WineWindow*)[view window] updateForGLSubviews];
3141     });
3143     [pool release];
3146 /***********************************************************************
3147  *              macdrv_add_view_opengl_context
3149  * Add an OpenGL context to the list being tracked for each view.
3150  */
3151 void macdrv_add_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
3153     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3154     WineContentView* view = (WineContentView*)v;
3155     WineOpenGLContext *context = (WineOpenGLContext*)c;
3157     OnMainThread(^{
3158         [view addGLContext:context];
3159     });
3161     [pool release];
3164 /***********************************************************************
3165  *              macdrv_remove_view_opengl_context
3167  * Add an OpenGL context to the list being tracked for each view.
3168  */
3169 void macdrv_remove_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
3171     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3172     WineContentView* view = (WineContentView*)v;
3173     WineOpenGLContext *context = (WineOpenGLContext*)c;
3175     OnMainThreadAsync(^{
3176         [view removeGLContext:context];
3177     });
3179     [pool release];
3182 /***********************************************************************
3183  *              macdrv_window_background_color
3185  * Returns the standard Mac window background color as a 32-bit value of
3186  * the form 0x00rrggbb.
3187  */
3188 uint32_t macdrv_window_background_color(void)
3190     static uint32_t result;
3191     static dispatch_once_t once;
3193     // Annoyingly, [NSColor windowBackgroundColor] refuses to convert to other
3194     // color spaces (RGB or grayscale).  So, the only way to get RGB values out
3195     // of it is to draw with it.
3196     dispatch_once(&once, ^{
3197         OnMainThread(^{
3198             unsigned char rgbx[4];
3199             unsigned char *planes = rgbx;
3200             NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&planes
3201                                                                                pixelsWide:1
3202                                                                                pixelsHigh:1
3203                                                                             bitsPerSample:8
3204                                                                           samplesPerPixel:3
3205                                                                                  hasAlpha:NO
3206                                                                                  isPlanar:NO
3207                                                                            colorSpaceName:NSCalibratedRGBColorSpace
3208                                                                              bitmapFormat:0
3209                                                                               bytesPerRow:4
3210                                                                              bitsPerPixel:32];
3211             [NSGraphicsContext saveGraphicsState];
3212             [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
3213             [[NSColor windowBackgroundColor] set];
3214             NSRectFill(NSMakeRect(0, 0, 1, 1));
3215             [NSGraphicsContext restoreGraphicsState];
3216             [bitmap release];
3217             result = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
3218         });
3219     });
3221     return result;
3224 /***********************************************************************
3225  *              macdrv_send_text_input_event
3226  */
3227 int macdrv_send_text_input_event(int pressed, unsigned int flags, int repeat, int keyc, void* data)
3229     __block BOOL ret;
3231     OnMainThread(^{
3232         WineWindow* window = (WineWindow*)[NSApp keyWindow];
3233         if (![window isKindOfClass:[WineWindow class]])
3234         {
3235             window = (WineWindow*)[NSApp mainWindow];
3236             if (![window isKindOfClass:[WineWindow class]])
3237                 window = [[WineApplicationController sharedController] frontWineWindow];
3238         }
3240         if (window)
3241         {
3242             NSUInteger localFlags = flags;
3243             CGEventRef c;
3244             NSEvent* event;
3246             window.imeData = data;
3247             fix_device_modifiers_by_generic(&localFlags);
3249             // An NSEvent created with +keyEventWithType:... is internally marked
3250             // as synthetic and doesn't get sent through input methods.  But one
3251             // created from a CGEvent doesn't have that problem.
3252             c = CGEventCreateKeyboardEvent(NULL, keyc, pressed);
3253             CGEventSetFlags(c, localFlags);
3254             CGEventSetIntegerValueField(c, kCGKeyboardEventAutorepeat, repeat);
3255             event = [NSEvent eventWithCGEvent:c];
3256             CFRelease(c);
3258             window.commandDone = FALSE;
3259             ret = [[[window contentView] inputContext] handleEvent:event] && !window.commandDone;
3260         }
3261         else
3262             ret = FALSE;
3263     });
3265     return ret;