kernel32: Get rid of the last parameter to PROFILE_CopyEntry().
[wine.git] / dlls / winemac.drv / cocoa_window.m
blobedbfab518e3cfe0a8982590746db1047a39774af
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 #include "config.h"
23 #import <Carbon/Carbon.h>
24 #import <CoreVideo/CoreVideo.h>
25 #ifdef HAVE_METAL_METAL_H
26 #import <Metal/Metal.h>
27 #import <QuartzCore/QuartzCore.h>
28 #endif
30 #import "cocoa_window.h"
32 #include "macdrv_cocoa.h"
33 #import "cocoa_app.h"
34 #import "cocoa_event.h"
35 #import "cocoa_opengl.h"
38 #if !defined(MAC_OS_X_VERSION_10_7) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
39 enum {
40     NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7,
41     NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8,
42     NSWindowFullScreenButton = 7,
43     NSFullScreenWindowMask = 1 << 14,
46 @interface NSWindow (WineFullScreenExtensions)
47     - (void) toggleFullScreen:(id)sender;
48 @end
49 #endif
52 #if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
53 /* Additional Mac virtual keycode, to complement those in Carbon's <HIToolbox/Events.h>. */
54 enum {
55     kVK_RightCommand              = 0x36, /* Invented for Wine; was unused */
57 #endif
60 static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf)
62     NSUInteger style_mask;
64     if (wf->title_bar)
65     {
66         style_mask = NSTitledWindowMask;
67         if (wf->close_button) style_mask |= NSClosableWindowMask;
68         if (wf->minimize_button) style_mask |= NSMiniaturizableWindowMask;
69         if (wf->resizable || wf->maximize_button) style_mask |= NSResizableWindowMask;
70         if (wf->utility) style_mask |= NSUtilityWindowMask;
71     }
72     else style_mask = NSBorderlessWindowMask;
74     return style_mask;
78 static BOOL frame_intersects_screens(NSRect frame, NSArray* screens)
80     NSScreen* screen;
81     for (screen in screens)
82     {
83         if (NSIntersectsRect(frame, [screen frame]))
84             return TRUE;
85     }
86     return FALSE;
90 static NSScreen* screen_covered_by_rect(NSRect rect, NSArray* screens)
92     for (NSScreen* screen in screens)
93     {
94         if (NSContainsRect(rect, [screen frame]))
95             return screen;
96     }
97     return nil;
101 /* We rely on the supposedly device-dependent modifier flags to distinguish the
102    keys on the left side of the keyboard from those on the right.  Some event
103    sources don't set those device-depdendent flags.  If we see a device-independent
104    flag for a modifier without either corresponding device-dependent flag, assume
105    the left one. */
106 static inline void fix_device_modifiers_by_generic(NSUInteger* modifiers)
108     if ((*modifiers & (NX_COMMANDMASK | NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) == NX_COMMANDMASK)
109         *modifiers |= NX_DEVICELCMDKEYMASK;
110     if ((*modifiers & (NX_SHIFTMASK | NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) == NX_SHIFTMASK)
111         *modifiers |= NX_DEVICELSHIFTKEYMASK;
112     if ((*modifiers & (NX_CONTROLMASK | NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) == NX_CONTROLMASK)
113         *modifiers |= NX_DEVICELCTLKEYMASK;
114     if ((*modifiers & (NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) == NX_ALTERNATEMASK)
115         *modifiers |= NX_DEVICELALTKEYMASK;
118 /* As we manipulate individual bits of a modifier mask, we can end up with
119    inconsistent sets of flags.  In particular, we might set or clear one of the
120    left/right-specific bits, but not the corresponding non-side-specific bit.
121    Fix that.  If either side-specific bit is set, set the non-side-specific bit,
122    otherwise clear it. */
123 static inline void fix_generic_modifiers_by_device(NSUInteger* modifiers)
125     if (*modifiers & (NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK))
126         *modifiers |= NX_COMMANDMASK;
127     else
128         *modifiers &= ~NX_COMMANDMASK;
129     if (*modifiers & (NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK))
130         *modifiers |= NX_SHIFTMASK;
131     else
132         *modifiers &= ~NX_SHIFTMASK;
133     if (*modifiers & (NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK))
134         *modifiers |= NX_CONTROLMASK;
135     else
136         *modifiers &= ~NX_CONTROLMASK;
137     if (*modifiers & (NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK))
138         *modifiers |= NX_ALTERNATEMASK;
139     else
140         *modifiers &= ~NX_ALTERNATEMASK;
143 static inline NSUInteger adjusted_modifiers_for_settings(NSUInteger modifiers)
145     fix_device_modifiers_by_generic(&modifiers);
146     NSUInteger new_modifiers = modifiers & ~(NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK |
147                                              NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK);
149     // The MACDRV keyboard driver translates Command keys to Alt. If the
150     // Option key (NX_DEVICE[LR]ALTKEYMASK) should behave like Alt in
151     // Windows, rewrite it to Command (NX_DEVICE[LR]CMDKEYMASK).
152     if (modifiers & NX_DEVICELALTKEYMASK)
153         new_modifiers |= left_option_is_alt ? NX_DEVICELCMDKEYMASK : NX_DEVICELALTKEYMASK;
154     if (modifiers & NX_DEVICERALTKEYMASK)
155         new_modifiers |= right_option_is_alt ? NX_DEVICERCMDKEYMASK : NX_DEVICERALTKEYMASK;
157     if (modifiers & NX_DEVICELCMDKEYMASK)
158         new_modifiers |= left_command_is_ctrl ? NX_DEVICELCTLKEYMASK : NX_DEVICELCMDKEYMASK;
159     if (modifiers & NX_DEVICERCMDKEYMASK)
160         new_modifiers |= right_command_is_ctrl ? NX_DEVICERCTLKEYMASK : NX_DEVICERCMDKEYMASK;
162     fix_generic_modifiers_by_device(&new_modifiers);
163     return new_modifiers;
167 @interface NSWindow (WineAccessPrivateMethods)
168     - (id) _displayChanged;
169 @end
172 @interface WineDisplayLink : NSObject
174     CGDirectDisplayID _displayID;
175     CVDisplayLinkRef _link;
176     NSMutableSet* _windows;
178     NSTimeInterval _actualRefreshPeriod;
179     NSTimeInterval _nominalRefreshPeriod;
181     NSTimeInterval _lastDisplayTime;
184     - (id) initWithDisplayID:(CGDirectDisplayID)displayID;
186     - (void) addWindow:(WineWindow*)window;
187     - (void) removeWindow:(WineWindow*)window;
189     - (NSTimeInterval) refreshPeriod;
191     - (void) start;
193 @end
195 @implementation WineDisplayLink
197 static CVReturn WineDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* inNow, const CVTimeStamp* inOutputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext);
199     - (id) initWithDisplayID:(CGDirectDisplayID)displayID
200     {
201         self = [super init];
202         if (self)
203         {
204             CVReturn status = CVDisplayLinkCreateWithCGDisplay(displayID, &_link);
205             if (status == kCVReturnSuccess && !_link)
206                 status = kCVReturnError;
207             if (status == kCVReturnSuccess)
208                 status = CVDisplayLinkSetOutputCallback(_link, WineDisplayLinkCallback, self);
209             if (status != kCVReturnSuccess)
210             {
211                 [self release];
212                 return nil;
213             }
215             _displayID = displayID;
216             _windows = [[NSMutableSet alloc] init];
217         }
218         return self;
219     }
221     - (void) dealloc
222     {
223         if (_link)
224         {
225             CVDisplayLinkStop(_link);
226             CVDisplayLinkRelease(_link);
227         }
228         [_windows release];
229         [super dealloc];
230     }
232     - (void) addWindow:(WineWindow*)window
233     {
234         BOOL firstWindow;
235         @synchronized(self) {
236             firstWindow = !_windows.count;
237             [_windows addObject:window];
238         }
239         if (firstWindow || !CVDisplayLinkIsRunning(_link))
240             [self start];
241     }
243     - (void) removeWindow:(WineWindow*)window
244     {
245         BOOL lastWindow = FALSE;
246         @synchronized(self) {
247             BOOL hadWindows = _windows.count > 0;
248             [_windows removeObject:window];
249             if (hadWindows && !_windows.count)
250                 lastWindow = TRUE;
251         }
252         if (lastWindow && CVDisplayLinkIsRunning(_link))
253             CVDisplayLinkStop(_link);
254     }
256     - (void) fire
257     {
258         NSSet* windows;
259         @synchronized(self) {
260             windows = [_windows copy];
261         }
262         dispatch_async(dispatch_get_main_queue(), ^{
263             BOOL anyDisplayed = FALSE;
264             for (WineWindow* window in windows)
265             {
266                 if ([window viewsNeedDisplay])
267                 {
268                     [window displayIfNeeded];
269                     anyDisplayed = YES;
270                 }
271             }
273             NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
274             if (anyDisplayed)
275                 _lastDisplayTime = now;
276             else if (_lastDisplayTime + 2.0 < now)
277                 CVDisplayLinkStop(_link);
278         });
279         [windows release];
280     }
282     - (NSTimeInterval) refreshPeriod
283     {
284         if (_actualRefreshPeriod || (_actualRefreshPeriod = CVDisplayLinkGetActualOutputVideoRefreshPeriod(_link)))
285             return _actualRefreshPeriod;
287         if (_nominalRefreshPeriod)
288             return _nominalRefreshPeriod;
290         CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(_link);
291         if (time.flags & kCVTimeIsIndefinite)
292             return 1.0 / 60.0;
293         _nominalRefreshPeriod = time.timeValue / (double)time.timeScale;
294         return _nominalRefreshPeriod;
295     }
297     - (void) start
298     {
299         _lastDisplayTime = [[NSProcessInfo processInfo] systemUptime];
300         CVDisplayLinkStart(_link);
301     }
303 static CVReturn WineDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* inNow, const CVTimeStamp* inOutputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
305     WineDisplayLink* link = displayLinkContext;
306     [link fire];
307     return kCVReturnSuccess;
310 @end
313 @interface WineBaseView : NSView
314 @end
317 #ifdef HAVE_METAL_METAL_H
318 @interface WineMetalView : WineBaseView
320     id<MTLDevice> _device;
323     - (id) initWithFrame:(NSRect)frame device:(id<MTLDevice>)device;
325 @end
326 #endif
329 @interface WineContentView : WineBaseView <NSTextInputClient>
331     NSMutableArray* glContexts;
332     NSMutableArray* pendingGlContexts;
333     BOOL _everHadGLContext;
334     BOOL _cachedHasGLDescendant;
335     BOOL _cachedHasGLDescendantValid;
336     BOOL clearedGlSurface;
338     NSMutableAttributedString* markedText;
339     NSRange markedTextSelection;
341     int backingSize[2];
343 #ifdef HAVE_METAL_METAL_H
344     WineMetalView *_metalView;
345 #endif
348 @property (readonly, nonatomic) BOOL everHadGLContext;
350     - (void) addGLContext:(WineOpenGLContext*)context;
351     - (void) removeGLContext:(WineOpenGLContext*)context;
352     - (void) updateGLContexts;
354     - (void) wine_getBackingSize:(int*)outBackingSize;
355     - (void) wine_setBackingSize:(const int*)newBackingSize;
357 #ifdef HAVE_METAL_METAL_H
358     - (WineMetalView*) newMetalViewWithDevice:(id<MTLDevice>)device;
359 #endif
361 @end
364 @interface WineWindow ()
366 @property (readwrite, nonatomic) BOOL disabled;
367 @property (readwrite, nonatomic) BOOL noActivate;
368 @property (readwrite, nonatomic) BOOL floating;
369 @property (readwrite, nonatomic) BOOL drawnSinceShown;
370 @property (readwrite, getter=isFakingClose, nonatomic) BOOL fakingClose;
371 @property (retain, nonatomic) NSWindow* latentParentWindow;
373 @property (nonatomic) void* hwnd;
374 @property (retain, readwrite, nonatomic) WineEventQueue* queue;
376 @property (nonatomic) void* surface;
377 @property (nonatomic) pthread_mutex_t* surface_mutex;
379 @property (copy, nonatomic) NSBezierPath* shape;
380 @property (copy, nonatomic) NSData* shapeData;
381 @property (nonatomic) BOOL shapeChangedSinceLastDraw;
382 @property (readonly, nonatomic) BOOL needsTransparency;
384 @property (nonatomic) BOOL colorKeyed;
385 @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
386 @property (nonatomic) BOOL usePerPixelAlpha;
388 @property (assign, nonatomic) void* imeData;
389 @property (nonatomic) BOOL commandDone;
391 @property (readonly, copy, nonatomic) NSArray* childWineWindows;
393     - (void) updateForGLSubviews;
395     - (BOOL) becameEligibleParentOrChild;
396     - (void) becameIneligibleChild;
398     - (void) windowDidDrawContent;
400 @end
403 @implementation WineBaseView
405     - (void) setRetinaMode:(int)mode
406     {
407         for (WineBaseView* subview in [self subviews])
408         {
409             if ([subview isKindOfClass:[WineBaseView class]])
410                 [subview setRetinaMode:mode];
411         }
412     }
414     - (BOOL) acceptsFirstMouse:(NSEvent*)theEvent
415     {
416         return YES;
417     }
419     - (BOOL) preservesContentDuringLiveResize
420     {
421         // Returning YES from this tells Cocoa to keep our view's content during
422         // a Cocoa-driven resize.  In theory, we're also supposed to override
423         // -setFrameSize: to mark exposed sections as needing redisplay, but
424         // user32 will take care of that in a roundabout way.  This way, we don't
425         // redraw until the window surface is flushed.
426         //
427         // This doesn't do anything when we resize the window ourselves.
428         return YES;
429     }
431     - (BOOL)acceptsFirstResponder
432     {
433         return [[self window] contentView] == self;
434     }
436     - (BOOL) mouseDownCanMoveWindow
437     {
438         return NO;
439     }
441     - (NSFocusRingType) focusRingType
442     {
443         return NSFocusRingTypeNone;
444     }
446 @end
449 @implementation WineContentView
451 @synthesize everHadGLContext = _everHadGLContext;
453     - (void) dealloc
454     {
455         [markedText release];
456         [glContexts release];
457         [pendingGlContexts release];
458         [super dealloc];
459     }
461     - (BOOL) isFlipped
462     {
463         return YES;
464     }
466     - (void) drawRect:(NSRect)rect
467     {
468         WineWindow* window = (WineWindow*)[self window];
470         for (WineOpenGLContext* context in pendingGlContexts)
471         {
472             if (!clearedGlSurface)
473             {
474                 context.shouldClearToBlack = TRUE;
475                 clearedGlSurface = TRUE;
476             }
477             context.needsUpdate = TRUE;
478         }
479         [glContexts addObjectsFromArray:pendingGlContexts];
480         [pendingGlContexts removeAllObjects];
482         if ([window contentView] != self)
483             return;
485         if (window.drawnSinceShown && window.shapeChangedSinceLastDraw && window.shape && !window.colorKeyed && !window.usePerPixelAlpha)
486         {
487             [[NSColor clearColor] setFill];
488             NSRectFill(rect);
490             [window.shape addClip];
492             [[NSColor windowBackgroundColor] setFill];
493             NSRectFill(rect);
494         }
496         if (window.surface && window.surface_mutex &&
497             !pthread_mutex_lock(window.surface_mutex))
498         {
499             const CGRect* rects;
500             int count;
502             if (get_surface_blit_rects(window.surface, &rects, &count) && count)
503             {
504                 CGRect dirtyRect = cgrect_win_from_mac(NSRectToCGRect(rect));
505                 CGContextRef context;
506                 int i;
508                 [window.shape addClip];
510                 context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
511                 CGContextSetBlendMode(context, kCGBlendModeCopy);
512                 CGContextSetInterpolationQuality(context, retina_on ? kCGInterpolationHigh : kCGInterpolationNone);
514                 for (i = 0; i < count; i++)
515                 {
516                     CGRect imageRect;
517                     CGImageRef image;
519                     imageRect = CGRectIntersection(rects[i], dirtyRect);
520                     image = create_surface_image(window.surface, &imageRect, FALSE);
522                     if (image)
523                     {
524                         if (window.colorKeyed)
525                         {
526                             CGImageRef maskedImage;
527                             CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5,
528                                                      window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5,
529                                                      window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 };
530                             maskedImage = CGImageCreateWithMaskingColors(image, components);
531                             if (maskedImage)
532                             {
533                                 CGImageRelease(image);
534                                 image = maskedImage;
535                             }
536                         }
538                         CGContextDrawImage(context, cgrect_mac_from_win(imageRect), image);
540                         CGImageRelease(image);
541                     }
542                 }
544                 [window windowDidDrawContent];
545             }
547             pthread_mutex_unlock(window.surface_mutex);
548         }
550         // If the window may be transparent, then we have to invalidate the
551         // shadow every time we draw.  Also, if this is the first time we've
552         // drawn since changing from transparent to opaque.
553         if (window.drawnSinceShown && (window.colorKeyed || window.usePerPixelAlpha || window.shapeChangedSinceLastDraw))
554         {
555             window.shapeChangedSinceLastDraw = FALSE;
556             [window invalidateShadow];
557         }
558     }
560     - (void) addGLContext:(WineOpenGLContext*)context
561     {
562         BOOL hadContext = _everHadGLContext;
563         if (!glContexts)
564             glContexts = [[NSMutableArray alloc] init];
565         if (!pendingGlContexts)
566             pendingGlContexts = [[NSMutableArray alloc] init];
568         if ([[self window] windowNumber] > 0 && !NSIsEmptyRect([self visibleRect]))
569         {
570             [glContexts addObject:context];
571             if (!clearedGlSurface)
572             {
573                 context.shouldClearToBlack = TRUE;
574                 clearedGlSurface = TRUE;
575             }
576             context.needsUpdate = TRUE;
577         }
578         else
579         {
580             [pendingGlContexts addObject:context];
581             [self setNeedsDisplay:YES];
582         }
584         _everHadGLContext = YES;
585         if (!hadContext)
586             [self invalidateHasGLDescendant];
587         [(WineWindow*)[self window] updateForGLSubviews];
588     }
590     - (void) removeGLContext:(WineOpenGLContext*)context
591     {
592         [glContexts removeObjectIdenticalTo:context];
593         [pendingGlContexts removeObjectIdenticalTo:context];
594         [(WineWindow*)[self window] updateForGLSubviews];
595     }
597     - (void) updateGLContexts:(BOOL)reattach
598     {
599         for (WineOpenGLContext* context in glContexts)
600         {
601             context.needsUpdate = TRUE;
602             if (reattach)
603                 context.needsReattach = TRUE;
604         }
605     }
607     - (void) updateGLContexts
608     {
609         [self updateGLContexts:NO];
610     }
612     - (BOOL) _hasGLDescendant
613     {
614         if ([self isHidden])
615             return NO;
616         if (_everHadGLContext)
617             return YES;
618         for (WineContentView* view in [self subviews])
619         {
620             if ([view isKindOfClass:[WineContentView class]] && [view hasGLDescendant])
621                 return YES;
622         }
623         return NO;
624     }
626     - (BOOL) hasGLDescendant
627     {
628         if (!_cachedHasGLDescendantValid)
629         {
630             _cachedHasGLDescendant = [self _hasGLDescendant];
631             _cachedHasGLDescendantValid = YES;
632         }
633         return _cachedHasGLDescendant;
634     }
636     - (void) invalidateHasGLDescendant
637     {
638         BOOL invalidateAncestors = _cachedHasGLDescendantValid;
639         _cachedHasGLDescendantValid = NO;
640         if (invalidateAncestors && self != [[self window] contentView])
641         {
642             WineContentView* superview = (WineContentView*)[self superview];
643             if ([superview isKindOfClass:[WineContentView class]])
644                 [superview invalidateHasGLDescendant];
645         }
646     }
648     - (void) wine_getBackingSize:(int*)outBackingSize
649     {
650         @synchronized(self) {
651             memcpy(outBackingSize, backingSize, sizeof(backingSize));
652         }
653     }
654     - (void) wine_setBackingSize:(const int*)newBackingSize
655     {
656         @synchronized(self) {
657             memcpy(backingSize, newBackingSize, sizeof(backingSize));
658         }
659     }
661 #ifdef HAVE_METAL_METAL_H
662     - (WineMetalView*) newMetalViewWithDevice:(id<MTLDevice>)device
663     {
664         if (_metalView) return _metalView;
666         WineMetalView* view = [[WineMetalView alloc] initWithFrame:[self bounds] device:device];
667         [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
668         [self setAutoresizesSubviews:YES];
669         [self addSubview:view positioned:NSWindowBelow relativeTo:nil];
670         _metalView = view;
672         [(WineWindow*)self.window windowDidDrawContent];
674         return _metalView;
675     }
676 #endif
678     - (void) setRetinaMode:(int)mode
679     {
680         double scale = mode ? 0.5 : 2.0;
681         NSRect frame = self.frame;
682         frame.origin.x *= scale;
683         frame.origin.y *= scale;
684         frame.size.width *= scale;
685         frame.size.height *= scale;
686         [self setFrame:frame];
687         [self updateGLContexts];
689         [super setRetinaMode:mode];
690     }
692     - (void) viewDidHide
693     {
694         [super viewDidHide];
695         [self invalidateHasGLDescendant];
696     }
698     - (void) viewDidUnhide
699     {
700         [super viewDidUnhide];
701         [self updateGLContexts:YES];
702         [self invalidateHasGLDescendant];
703     }
705     - (void) clearMarkedText
706     {
707         [markedText deleteCharactersInRange:NSMakeRange(0, [markedText length])];
708         markedTextSelection = NSMakeRange(0, 0);
709         [[self inputContext] discardMarkedText];
710     }
712     - (void) completeText:(NSString*)text
713     {
714         macdrv_event* event;
715         WineWindow* window = (WineWindow*)[self window];
717         event = macdrv_create_event(IM_SET_TEXT, window);
718         event->im_set_text.data = [window imeData];
719         event->im_set_text.text = (CFStringRef)[text copy];
720         event->im_set_text.complete = TRUE;
722         [[window queue] postEvent:event];
724         macdrv_release_event(event);
726         [self clearMarkedText];
727     }
729     - (void) didAddSubview:(NSView*)subview
730     {
731         if ([subview isKindOfClass:[WineContentView class]])
732         {
733             WineContentView* view = (WineContentView*)subview;
734             if (!view->_cachedHasGLDescendantValid || view->_cachedHasGLDescendant)
735                 [self invalidateHasGLDescendant];
736         }
737         [super didAddSubview:subview];
738     }
740     - (void) willRemoveSubview:(NSView*)subview
741     {
742         if ([subview isKindOfClass:[WineContentView class]])
743         {
744             WineContentView* view = (WineContentView*)subview;
745             if (!view->_cachedHasGLDescendantValid || view->_cachedHasGLDescendant)
746                 [self invalidateHasGLDescendant];
747         }
748 #ifdef HAVE_METAL_METAL_H
749         if (subview == _metalView)
750             _metalView = nil;
751 #endif
752         [super willRemoveSubview:subview];
753     }
755     - (void) setLayer:(CALayer*)newLayer
756     {
757         [super setLayer:newLayer];
758         [self updateGLContexts];
759     }
761     /*
762      * ---------- NSTextInputClient methods ----------
763      */
764     - (NSTextInputContext*) inputContext
765     {
766         if (!markedText)
767             markedText = [[NSMutableAttributedString alloc] init];
768         return [super inputContext];
769     }
771     - (void) insertText:(id)string replacementRange:(NSRange)replacementRange
772     {
773         if ([string isKindOfClass:[NSAttributedString class]])
774             string = [string string];
776         if ([string isKindOfClass:[NSString class]])
777             [self completeText:string];
778     }
780     - (void) doCommandBySelector:(SEL)aSelector
781     {
782         [(WineWindow*)[self window] setCommandDone:TRUE];
783     }
785     - (void) setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
786     {
787         if ([string isKindOfClass:[NSAttributedString class]])
788             string = [string string];
790         if ([string isKindOfClass:[NSString class]])
791         {
792             macdrv_event* event;
793             WineWindow* window = (WineWindow*)[self window];
795             if (replacementRange.location == NSNotFound)
796                 replacementRange = NSMakeRange(0, [markedText length]);
798             [markedText replaceCharactersInRange:replacementRange withString:string];
799             markedTextSelection = selectedRange;
800             markedTextSelection.location += replacementRange.location;
802             event = macdrv_create_event(IM_SET_TEXT, window);
803             event->im_set_text.data = [window imeData];
804             event->im_set_text.text = (CFStringRef)[[markedText string] copy];
805             event->im_set_text.complete = FALSE;
806             event->im_set_text.cursor_pos = markedTextSelection.location + markedTextSelection.length;
808             [[window queue] postEvent:event];
810             macdrv_release_event(event);
812             [[self inputContext] invalidateCharacterCoordinates];
813         }
814     }
816     - (void) unmarkText
817     {
818         [self completeText:nil];
819     }
821     - (NSRange) selectedRange
822     {
823         return markedTextSelection;
824     }
826     - (NSRange) markedRange
827     {
828         NSRange range = NSMakeRange(0, [markedText length]);
829         if (!range.length)
830             range.location = NSNotFound;
831         return range;
832     }
834     - (BOOL) hasMarkedText
835     {
836         return [markedText length] > 0;
837     }
839     - (NSAttributedString*) attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
840     {
841         if (aRange.location >= [markedText length])
842             return nil;
844         aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
845         if (actualRange)
846             *actualRange = aRange;
847         return [markedText attributedSubstringFromRange:aRange];
848     }
850     - (NSArray*) validAttributesForMarkedText
851     {
852         return [NSArray array];
853     }
855     - (NSRect) firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
856     {
857         macdrv_query* query;
858         WineWindow* window = (WineWindow*)[self window];
859         NSRect ret;
861         aRange = NSIntersectionRange(aRange, NSMakeRange(0, [markedText length]));
863         query = macdrv_create_query();
864         query->type = QUERY_IME_CHAR_RECT;
865         query->window = (macdrv_window)[window retain];
866         query->ime_char_rect.data = [window imeData];
867         query->ime_char_rect.range = CFRangeMake(aRange.location, aRange.length);
869         if ([window.queue query:query timeout:0.3 flags:WineQueryNoPreemptWait])
870         {
871             aRange = NSMakeRange(query->ime_char_rect.range.location, query->ime_char_rect.range.length);
872             ret = NSRectFromCGRect(cgrect_mac_from_win(query->ime_char_rect.rect));
873             [[WineApplicationController sharedController] flipRect:&ret];
874         }
875         else
876             ret = NSMakeRect(100, 100, aRange.length ? 1 : 0, 12);
878         macdrv_release_query(query);
880         if (actualRange)
881             *actualRange = aRange;
882         return ret;
883     }
885     - (NSUInteger) characterIndexForPoint:(NSPoint)aPoint
886     {
887         return NSNotFound;
888     }
890     - (NSInteger) windowLevel
891     {
892         return [[self window] level];
893     }
895 @end
898 #ifdef HAVE_METAL_METAL_H
899 @implementation WineMetalView
901     - (id) initWithFrame:(NSRect)frame device:(id<MTLDevice>)device
902     {
903         self = [super initWithFrame:frame];
904         if (self)
905         {
906             _device = [device retain];
907             self.wantsLayer = YES;
908             self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawNever;
909         }
910         return self;
911     }
913     - (void) dealloc
914     {
915         [_device release];
916         [super dealloc];
917     }
919     - (void) setRetinaMode:(int)mode
920     {
921         self.layer.contentsScale = mode ? 2.0 : 1.0;
922         [super setRetinaMode:mode];
923     }
925     - (CALayer*) makeBackingLayer
926     {
927         CAMetalLayer *layer = [CAMetalLayer layer];
928         layer.device = _device;
929         layer.framebufferOnly = YES;
930         layer.magnificationFilter = kCAFilterNearest;
931         layer.backgroundColor = CGColorGetConstantColor(kCGColorBlack);
932         layer.contentsScale = retina_on ? 2.0 : 1.0;
933         return layer;
934     }
936     - (BOOL) isOpaque
937     {
938         return YES;
939     }
941 @end
942 #endif
945 @implementation WineWindow
947     static WineWindow* causing_becomeKeyWindow;
949     @synthesize disabled, noActivate, floating, fullscreen, fakingClose, latentParentWindow, hwnd, queue;
950     @synthesize drawnSinceShown;
951     @synthesize surface, surface_mutex;
952     @synthesize shape, shapeData, shapeChangedSinceLastDraw;
953     @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
954     @synthesize usePerPixelAlpha;
955     @synthesize imeData, commandDone;
957     + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
958                                  windowFrame:(NSRect)window_frame
959                                         hwnd:(void*)hwnd
960                                        queue:(WineEventQueue*)queue
961     {
962         WineWindow* window;
963         WineContentView* contentView;
964         NSTrackingArea* trackingArea;
965         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
967         [[WineApplicationController sharedController] flipRect:&window_frame];
969         window = [[[self alloc] initWithContentRect:window_frame
970                                           styleMask:style_mask_for_features(wf)
971                                             backing:NSBackingStoreBuffered
972                                               defer:YES] autorelease];
974         if (!window) return nil;
976         /* Standardize windows to eliminate differences between titled and
977            borderless windows and between NSWindow and NSPanel. */
978         [window setHidesOnDeactivate:NO];
979         [window setReleasedWhenClosed:NO];
981         [window setOneShot:YES];
982         [window disableCursorRects];
983         [window setShowsResizeIndicator:NO];
984         [window setHasShadow:wf->shadow];
985         [window setAcceptsMouseMovedEvents:YES];
986         [window setDelegate:window];
987         [window setBackgroundColor:[NSColor clearColor]];
988         [window setOpaque:NO];
989         window.hwnd = hwnd;
990         window.queue = queue;
991         window->savedContentMinSize = NSZeroSize;
992         window->savedContentMaxSize = NSMakeSize(FLT_MAX, FLT_MAX);
993         window->resizable = wf->resizable;
994         window->_lastDisplayTime = [[NSDate distantPast] timeIntervalSinceReferenceDate];
996         [window registerForDraggedTypes:[NSArray arrayWithObjects:(NSString*)kUTTypeData,
997                                                                   (NSString*)kUTTypeContent,
998                                                                   nil]];
1000         contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
1001         if (!contentView)
1002             return nil;
1003         [contentView setAutoresizesSubviews:NO];
1005         /* We use tracking areas in addition to setAcceptsMouseMovedEvents:YES
1006            because they give us mouse moves in the background. */
1007         trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds]
1008                                                      options:(NSTrackingMouseMoved |
1009                                                               NSTrackingActiveAlways |
1010                                                               NSTrackingInVisibleRect)
1011                                                        owner:window
1012                                                     userInfo:nil] autorelease];
1013         if (!trackingArea)
1014             return nil;
1015         [contentView addTrackingArea:trackingArea];
1017         [window setContentView:contentView];
1018         [window setInitialFirstResponder:contentView];
1020         [nc addObserver:window
1021                selector:@selector(updateFullscreen)
1022                    name:NSApplicationDidChangeScreenParametersNotification
1023                  object:NSApp];
1024         [window updateFullscreen];
1026         [nc addObserver:window
1027                selector:@selector(applicationWillHide)
1028                    name:NSApplicationWillHideNotification
1029                  object:NSApp];
1030         [nc addObserver:window
1031                selector:@selector(applicationDidUnhide)
1032                    name:NSApplicationDidUnhideNotification
1033                  object:NSApp];
1035         [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:window
1036                                                               selector:@selector(checkWineDisplayLink)
1037                                                                   name:NSWorkspaceActiveSpaceDidChangeNotification
1038                                                                 object:[NSWorkspace sharedWorkspace]];
1040         [window setFrameAndWineFrame:[window frameRectForContentRect:window_frame]];
1042         return window;
1043     }
1045     - (void) dealloc
1046     {
1047         [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
1048         [[NSNotificationCenter defaultCenter] removeObserver:self];
1049         [queue release];
1050         [latentChildWindows release];
1051         [latentParentWindow release];
1052         [shape release];
1053         [shapeData release];
1054         [super dealloc];
1055     }
1057     - (BOOL) preventResizing
1058     {
1059         BOOL preventForClipping = cursor_clipping_locks_windows && [[WineApplicationController sharedController] clippingCursor];
1060         return ([self styleMask] & NSResizableWindowMask) && (disabled || !resizable || preventForClipping);
1061     }
1063     - (BOOL) allowsMovingWithMaximized:(BOOL)inMaximized
1064     {
1065         if (allow_immovable_windows && (disabled || inMaximized))
1066             return NO;
1067         else if (cursor_clipping_locks_windows && [[WineApplicationController sharedController] clippingCursor])
1068             return NO;
1069         else
1070             return YES;
1071     }
1073     - (void) adjustFeaturesForState
1074     {
1075         NSUInteger style = [self styleMask];
1077         if (style & NSClosableWindowMask)
1078             [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
1079         if (style & NSMiniaturizableWindowMask)
1080             [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
1081         if (style & NSResizableWindowMask)
1082             [[self standardWindowButton:NSWindowZoomButton] setEnabled:!self.disabled];
1083         if ([self respondsToSelector:@selector(toggleFullScreen:)])
1084         {
1085             if ([self collectionBehavior] & NSWindowCollectionBehaviorFullScreenPrimary)
1086                 [[self standardWindowButton:NSWindowFullScreenButton] setEnabled:!self.disabled];
1087         }
1089         if ([self preventResizing])
1090         {
1091             NSSize size = [self contentRectForFrameRect:self.wine_fractionalFrame].size;
1092             [self setContentMinSize:size];
1093             [self setContentMaxSize:size];
1094         }
1095         else
1096         {
1097             [self setContentMaxSize:savedContentMaxSize];
1098             [self setContentMinSize:savedContentMinSize];
1099         }
1101         if (allow_immovable_windows || cursor_clipping_locks_windows)
1102             [self setMovable:[self allowsMovingWithMaximized:maximized]];
1103     }
1105     - (void) adjustFullScreenBehavior:(NSWindowCollectionBehavior)behavior
1106     {
1107         if ([self respondsToSelector:@selector(toggleFullScreen:)])
1108         {
1109             NSUInteger style = [self styleMask];
1111             if (behavior & NSWindowCollectionBehaviorParticipatesInCycle &&
1112                 style & NSResizableWindowMask && !(style & NSUtilityWindowMask) && !maximized &&
1113                 !(self.parentWindow || self.latentParentWindow))
1114             {
1115                 behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
1116                 behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary;
1117             }
1118             else
1119             {
1120                 behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
1121                 behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary;
1122                 if (style & NSFullScreenWindowMask)
1123                     [super toggleFullScreen:nil];
1124             }
1125         }
1127         if (behavior != [self collectionBehavior])
1128         {
1129             [self setCollectionBehavior:behavior];
1130             [self adjustFeaturesForState];
1131         }
1132     }
1134     - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
1135     {
1136         static const NSUInteger usedStyles = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask |
1137                                              NSResizableWindowMask | NSUtilityWindowMask | NSBorderlessWindowMask;
1138         NSUInteger currentStyle = [self styleMask];
1139         NSUInteger newStyle = style_mask_for_features(wf) | (currentStyle & ~usedStyles);
1141         if (newStyle != currentStyle)
1142         {
1143             NSString* title = [[[self title] copy] autorelease];
1144             BOOL showingButtons = (currentStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
1145             BOOL shouldShowButtons = (newStyle & (NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)) != 0;
1146             if (shouldShowButtons != showingButtons && !((newStyle ^ currentStyle) & NSClosableWindowMask))
1147             {
1148                 // -setStyleMask: is buggy on 10.7+ with respect to NSResizableWindowMask.
1149                 // If transitioning from NSTitledWindowMask | NSResizableWindowMask to
1150                 // just NSTitledWindowMask, the window buttons should disappear rather
1151                 // than just being disabled.  But they don't.  Similarly in reverse.
1152                 // The workaround is to also toggle NSClosableWindowMask at the same time.
1153                 [self setStyleMask:newStyle ^ NSClosableWindowMask];
1154             }
1155             [self setStyleMask:newStyle];
1157             // -setStyleMask: resets the firstResponder to the window.  Set it
1158             // back to the content view.
1159             if ([[self contentView] acceptsFirstResponder])
1160                 [self makeFirstResponder:[self contentView]];
1162             [self adjustFullScreenBehavior:[self collectionBehavior]];
1164             if ([[self title] length] == 0 && [title length] > 0)
1165                 [self setTitle:title];
1166         }
1168         resizable = wf->resizable;
1169         [self adjustFeaturesForState];
1170         [self setHasShadow:wf->shadow];
1171     }
1173     // Indicates if the window would be visible if the app were not hidden.
1174     - (BOOL) wouldBeVisible
1175     {
1176         return [NSApp isHidden] ? savedVisibleState : [self isVisible];
1177     }
1179     - (BOOL) isOrderedIn
1180     {
1181         return [self wouldBeVisible] || [self isMiniaturized];
1182     }
1184     - (NSInteger) minimumLevelForActive:(BOOL)active
1185     {
1186         NSInteger level;
1188         if (self.floating && (active || topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_ALL ||
1189                               (topmost_float_inactive == TOPMOST_FLOAT_INACTIVE_NONFULLSCREEN && !fullscreen)))
1190             level = NSFloatingWindowLevel;
1191         else
1192             level = NSNormalWindowLevel;
1194         if (active)
1195         {
1196             BOOL captured;
1198             captured = (fullscreen || [self screen]) && [[WineApplicationController sharedController] areDisplaysCaptured];
1200             if (captured || fullscreen)
1201             {
1202                 if (captured)
1203                     level = CGShieldingWindowLevel() + 1; /* Need +1 or we don't get mouse moves */
1204                 else
1205                     level = NSStatusWindowLevel + 1;
1207                 if (self.floating)
1208                     level++;
1209             }
1210         }
1212         return level;
1213     }
1215     - (void) postDidUnminimizeEvent
1216     {
1217         macdrv_event* event;
1219         /* Coalesce events by discarding any previous ones still in the queue. */
1220         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_DID_UNMINIMIZE)
1221                                forWindow:self];
1223         event = macdrv_create_event(WINDOW_DID_UNMINIMIZE, self);
1224         [queue postEvent:event];
1225         macdrv_release_event(event);
1226     }
1228     - (void) sendResizeStartQuery
1229     {
1230         macdrv_query* query = macdrv_create_query();
1231         query->type = QUERY_RESIZE_START;
1232         query->window = (macdrv_window)[self retain];
1234         [self.queue query:query timeout:0.3];
1235         macdrv_release_query(query);
1236     }
1238     - (void) setMacDrvState:(const struct macdrv_window_state*)state
1239     {
1240         NSWindowCollectionBehavior behavior;
1242         self.disabled = state->disabled;
1243         self.noActivate = state->no_activate;
1245         if (self.floating != state->floating)
1246         {
1247             self.floating = state->floating;
1248             if (state->floating)
1249             {
1250                 // Became floating.  If child of non-floating window, make that
1251                 // relationship latent.
1252                 WineWindow* parent = (WineWindow*)[self parentWindow];
1253                 if (parent && !parent.floating)
1254                     [self becameIneligibleChild];
1255             }
1256             else
1257             {
1258                 // Became non-floating.  If parent of floating children, make that
1259                 // relationship latent.
1260                 WineWindow* child;
1261                 for (child in [self childWineWindows])
1262                 {
1263                     if (child.floating)
1264                         [child becameIneligibleChild];
1265                 }
1266             }
1268             // Check our latent relationships.  If floating status was the only
1269             // reason they were latent, then make them active.
1270             if ([self isVisible])
1271                 [self becameEligibleParentOrChild];
1273             [[WineApplicationController sharedController] adjustWindowLevels];
1274         }
1276         if (state->minimized_valid)
1277         {
1278             macdrv_event_mask discard = event_mask_for_type(WINDOW_DID_UNMINIMIZE);
1280             pendingMinimize = FALSE;
1281             if (state->minimized && ![self isMiniaturized])
1282             {
1283                 if ([self wouldBeVisible])
1284                 {
1285                     if ([self styleMask] & NSFullScreenWindowMask)
1286                     {
1287                         [self postDidUnminimizeEvent];
1288                         discard &= ~event_mask_for_type(WINDOW_DID_UNMINIMIZE);
1289                     }
1290                     else
1291                     {
1292                         [self setStyleMask:([self styleMask] | NSMiniaturizableWindowMask)];
1293                         [super miniaturize:nil];
1294                         discard |= event_mask_for_type(WINDOW_BROUGHT_FORWARD) |
1295                                    event_mask_for_type(WINDOW_GOT_FOCUS) |
1296                                    event_mask_for_type(WINDOW_LOST_FOCUS);
1297                     }
1298                 }
1299                 else
1300                     pendingMinimize = TRUE;
1301             }
1302             else if (!state->minimized && [self isMiniaturized])
1303             {
1304                 ignore_windowDeminiaturize = TRUE;
1305                 [self deminiaturize:nil];
1306                 discard |= event_mask_for_type(WINDOW_LOST_FOCUS);
1307             }
1309             if (discard)
1310                 [queue discardEventsMatchingMask:discard forWindow:self];
1311         }
1313         if (state->maximized != maximized)
1314         {
1315             maximized = state->maximized;
1316             [self adjustFeaturesForState];
1318             if (!maximized && [self inLiveResize])
1319                 [self sendResizeStartQuery];
1320         }
1322         behavior = NSWindowCollectionBehaviorDefault;
1323         if (state->excluded_by_expose)
1324             behavior |= NSWindowCollectionBehaviorTransient;
1325         else
1326             behavior |= NSWindowCollectionBehaviorManaged;
1327         if (state->excluded_by_cycle)
1328         {
1329             behavior |= NSWindowCollectionBehaviorIgnoresCycle;
1330             if ([self isOrderedIn])
1331                 [NSApp removeWindowsItem:self];
1332         }
1333         else
1334         {
1335             behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
1336             if ([self isOrderedIn])
1337                 [NSApp addWindowsItem:self title:[self title] filename:NO];
1338         }
1339         [self adjustFullScreenBehavior:behavior];
1340     }
1342     - (BOOL) addChildWineWindow:(WineWindow*)child assumeVisible:(BOOL)assumeVisible
1343     {
1344         BOOL reordered = FALSE;
1346         if ([self isVisible] && (assumeVisible || [child isVisible]) && (self.floating || !child.floating))
1347         {
1348             if ([self level] > [child level])
1349                 [child setLevel:[self level]];
1350             if (![child isVisible])
1351                 [child setAutodisplay:YES];
1352             [self addChildWindow:child ordered:NSWindowAbove];
1353             [child checkWineDisplayLink];
1354             [latentChildWindows removeObjectIdenticalTo:child];
1355             child.latentParentWindow = nil;
1356             reordered = TRUE;
1357         }
1358         else
1359         {
1360             if (!latentChildWindows)
1361                 latentChildWindows = [[NSMutableArray alloc] init];
1362             if (![latentChildWindows containsObject:child])
1363                 [latentChildWindows addObject:child];
1364             child.latentParentWindow = self;
1365         }
1367         return reordered;
1368     }
1370     - (BOOL) addChildWineWindow:(WineWindow*)child
1371     {
1372         return [self addChildWineWindow:child assumeVisible:FALSE];
1373     }
1375     - (void) removeChildWineWindow:(WineWindow*)child
1376     {
1377         [self removeChildWindow:child];
1378         if (child.latentParentWindow == self)
1379             child.latentParentWindow = nil;
1380         [latentChildWindows removeObjectIdenticalTo:child];
1381     }
1383     - (void) setChildWineWindows:(NSArray*)childWindows
1384     {
1385         NSArray* origChildren;
1386         NSUInteger count, start, limit, i;
1388         origChildren = self.childWineWindows;
1390         // If the current and desired children arrays match up to a point, leave
1391         // those matching children alone.
1392         count = childWindows.count;
1393         limit = MIN(origChildren.count, count);
1394         for (start = 0; start < limit; start++)
1395         {
1396             if ([origChildren objectAtIndex:start] != [childWindows objectAtIndex:start])
1397                 break;
1398         }
1400         // Remove all of the child windows and re-add them back-to-front so they
1401         // are in the desired order.
1402         for (i = start; i < count; i++)
1403         {
1404             WineWindow* child = [childWindows objectAtIndex:i];
1405             [self removeChildWindow:child];
1406         }
1407         for (i = start; i < count; i++)
1408         {
1409             WineWindow* child = [childWindows objectAtIndex:i];
1410             [self addChildWindow:child ordered:NSWindowAbove];
1411         }
1412     }
1414     static NSComparisonResult compare_windows_back_to_front(NSWindow* window1, NSWindow* window2, NSArray* windowNumbers)
1415     {
1416         NSNumber* window1Number = [NSNumber numberWithInteger:[window1 windowNumber]];
1417         NSNumber* window2Number = [NSNumber numberWithInteger:[window2 windowNumber]];
1418         NSUInteger index1 = [windowNumbers indexOfObject:window1Number];
1419         NSUInteger index2 = [windowNumbers indexOfObject:window2Number];
1420         if (index1 == NSNotFound)
1421         {
1422             if (index2 == NSNotFound)
1423                 return NSOrderedSame;
1424             else
1425                 return NSOrderedAscending;
1426         }
1427         else if (index2 == NSNotFound)
1428             return NSOrderedDescending;
1429         else if (index1 < index2)
1430             return NSOrderedDescending;
1431         else if (index2 < index1)
1432             return NSOrderedAscending;
1434         return NSOrderedSame;
1435     }
1437     - (BOOL) becameEligibleParentOrChild
1438     {
1439         BOOL reordered = FALSE;
1440         NSUInteger count;
1442         if (latentParentWindow.floating || !self.floating)
1443         {
1444             // If we aren't visible currently, we assume that we should be and soon
1445             // will be.  So, if the latent parent is visible that's enough to assume
1446             // we can establish the parent-child relationship in Cocoa.  That will
1447             // actually make us visible, which is fine.
1448             if ([latentParentWindow addChildWineWindow:self assumeVisible:TRUE])
1449                 reordered = TRUE;
1450         }
1452         // Here, though, we may not actually be visible yet and adding a child
1453         // won't make us visible.  The caller will have to call this method
1454         // again after actually making us visible.
1455         if ([self isVisible] && (count = [latentChildWindows count]))
1456         {
1457             NSMutableArray* windowNumbers;
1458             NSMutableArray* childWindows = [[self.childWineWindows mutableCopy] autorelease];
1459             NSMutableIndexSet* indexesToRemove = [NSMutableIndexSet indexSet];
1460             NSUInteger i;
1462             windowNumbers = [[[[self class] windowNumbersWithOptions:NSWindowNumberListAllSpaces] mutableCopy] autorelease];
1464             for (i = 0; i < count; i++)
1465             {
1466                 WineWindow* child = [latentChildWindows objectAtIndex:i];
1467                 if ([child isVisible] && (self.floating || !child.floating))
1468                 {
1469                     if (child.latentParentWindow == self)
1470                     {
1471                         if ([self level] > [child level])
1472                             [child setLevel:[self level]];
1473                         [childWindows addObject:child];
1474                         child.latentParentWindow = nil;
1475                         reordered = TRUE;
1476                     }
1477                     else
1478                         ERR(@"shouldn't happen: %@ thinks %@ is a latent child, but it doesn't agree\n", self, child);
1479                     [indexesToRemove addIndex:i];
1480                 }
1481             }
1483             [latentChildWindows removeObjectsAtIndexes:indexesToRemove];
1485             [childWindows sortWithOptions:NSSortStable
1486                           usingComparator:^NSComparisonResult(id obj1, id obj2){
1487                 return compare_windows_back_to_front(obj1, obj2, windowNumbers);
1488             }];
1489             [self setChildWineWindows:childWindows];
1490         }
1492         return reordered;
1493     }
1495     - (void) becameIneligibleChild
1496     {
1497         WineWindow* parent = (WineWindow*)[self parentWindow];
1498         if (parent)
1499         {
1500             if (!parent->latentChildWindows)
1501                 parent->latentChildWindows = [[NSMutableArray alloc] init];
1502             [parent->latentChildWindows insertObject:self atIndex:0];
1503             self.latentParentWindow = parent;
1504             [parent removeChildWindow:self];
1505         }
1506     }
1508     - (void) becameIneligibleParentOrChild
1509     {
1510         NSArray* childWindows = [self childWineWindows];
1512         [self becameIneligibleChild];
1514         if ([childWindows count])
1515         {
1516             WineWindow* child;
1518             for (child in childWindows)
1519             {
1520                 child.latentParentWindow = self;
1521                 [self removeChildWindow:child];
1522             }
1524             if (latentChildWindows)
1525                 [latentChildWindows replaceObjectsInRange:NSMakeRange(0, 0) withObjectsFromArray:childWindows];
1526             else
1527                 latentChildWindows = [childWindows mutableCopy];
1528         }
1529     }
1531     // Determine if, among Wine windows, this window is directly above or below
1532     // a given other Wine window with no other Wine window intervening.
1533     // Intervening non-Wine windows are ignored.
1534     - (BOOL) isOrdered:(NSWindowOrderingMode)orderingMode relativeTo:(WineWindow*)otherWindow
1535     {
1536         NSNumber* windowNumber;
1537         NSNumber* otherWindowNumber;
1538         NSArray* windowNumbers;
1539         NSUInteger windowIndex, otherWindowIndex, lowIndex, highIndex, i;
1541         if (![self isVisible] || ![otherWindow isVisible])
1542             return FALSE;
1544         windowNumber = [NSNumber numberWithInteger:[self windowNumber]];
1545         otherWindowNumber = [NSNumber numberWithInteger:[otherWindow windowNumber]];
1546         windowNumbers = [[self class] windowNumbersWithOptions:0];
1547         windowIndex = [windowNumbers indexOfObject:windowNumber];
1548         otherWindowIndex = [windowNumbers indexOfObject:otherWindowNumber];
1550         if (windowIndex == NSNotFound || otherWindowIndex == NSNotFound)
1551             return FALSE;
1553         if (orderingMode == NSWindowAbove)
1554         {
1555             lowIndex = windowIndex;
1556             highIndex = otherWindowIndex;
1557         }
1558         else if (orderingMode == NSWindowBelow)
1559         {
1560             lowIndex = otherWindowIndex;
1561             highIndex = windowIndex;
1562         }
1563         else
1564             return FALSE;
1566         if (highIndex <= lowIndex)
1567             return FALSE;
1569         for (i = lowIndex + 1; i < highIndex; i++)
1570         {
1571             NSInteger interveningWindowNumber = [[windowNumbers objectAtIndex:i] integerValue];
1572             NSWindow* interveningWindow = [NSApp windowWithWindowNumber:interveningWindowNumber];
1573             if ([interveningWindow isKindOfClass:[WineWindow class]])
1574                 return FALSE;
1575         }
1577         return TRUE;
1578     }
1580     - (void) order:(NSWindowOrderingMode)mode childWindow:(WineWindow*)child relativeTo:(WineWindow*)other
1581     {
1582         NSMutableArray* windowNumbers;
1583         NSNumber* childWindowNumber;
1584         NSUInteger otherIndex;
1585         NSArray* origChildren;
1586         NSMutableArray* children;
1588         // Get the z-order from the window server and modify it to reflect the
1589         // requested window ordering.
1590         windowNumbers = [[[[self class] windowNumbersWithOptions:NSWindowNumberListAllSpaces] mutableCopy] autorelease];
1591         childWindowNumber = [NSNumber numberWithInteger:[child windowNumber]];
1592         [windowNumbers removeObject:childWindowNumber];
1593         if (other)
1594         {
1595             otherIndex = [windowNumbers indexOfObject:[NSNumber numberWithInteger:[other windowNumber]]];
1596             [windowNumbers insertObject:childWindowNumber atIndex:otherIndex + (mode == NSWindowAbove ? 0 : 1)];
1597         }
1598         else if (mode == NSWindowAbove)
1599             [windowNumbers insertObject:childWindowNumber atIndex:0];
1600         else
1601             [windowNumbers addObject:childWindowNumber];
1603         // Get our child windows and sort them in the reverse of the desired
1604         // z-order (back-to-front).
1605         origChildren = [self childWineWindows];
1606         children = [[origChildren mutableCopy] autorelease];
1607         [children sortWithOptions:NSSortStable
1608                   usingComparator:^NSComparisonResult(id obj1, id obj2){
1609             return compare_windows_back_to_front(obj1, obj2, windowNumbers);
1610         }];
1612         [self setChildWineWindows:children];
1613     }
1615     // Search the ancestor windows of self and other to find a place where some ancestors are siblings of each other.
1616     // There are three possible results in terms of the values of *ancestor and *ancestorOfOther on return:
1617     //      (non-nil, non-nil)  there is a level in the window tree where the two windows have sibling ancestors
1618     //                          if *ancestor has a parent Wine window, then it's the parent of the other ancestor, too
1619     //                          otherwise, the two ancestors are each roots of disjoint window trees
1620     //      (nil, non-nil)      the other window is a descendent of self and *ancestorOfOther is the direct child
1621     //      (non-nil, nil)      self is a descendent of other and *ancestor is the direct child
1622     - (void) getSiblingWindowsForWindow:(WineWindow*)other ancestor:(WineWindow**)ancestor ancestorOfOther:(WineWindow**)ancestorOfOther
1623     {
1624         NSMutableArray* otherAncestors = [NSMutableArray arrayWithObject:other];
1625         WineWindow* child;
1626         WineWindow* parent;
1627         for (child = other;
1628              (parent = (WineWindow*)child.parentWindow) && [parent isKindOfClass:[WineWindow class]];
1629              child = parent)
1630         {
1631             if (parent == self)
1632             {
1633                 *ancestor = nil;
1634                 *ancestorOfOther = child;
1635                 return;
1636             }
1638             [otherAncestors addObject:parent];
1639         }
1641         for (child = self;
1642              (parent = (WineWindow*)child.parentWindow) && [parent isKindOfClass:[WineWindow class]];
1643              child = parent)
1644         {
1645             NSUInteger index = [otherAncestors indexOfObjectIdenticalTo:parent];
1646             if (index != NSNotFound)
1647             {
1648                 *ancestor = child;
1649                 if (index == 0)
1650                     *ancestorOfOther = nil;
1651                 else
1652                     *ancestorOfOther = [otherAncestors objectAtIndex:index - 1];
1653                 return;
1654             }
1655         }
1657         *ancestor = child;
1658         *ancestorOfOther = otherAncestors.lastObject;;
1659     }
1661     /* Returns whether or not the window was ordered in, which depends on if
1662        its frame intersects any screen. */
1663     - (void) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next activate:(BOOL)activate
1664     {
1665         WineApplicationController* controller = [WineApplicationController sharedController];
1666         if (![self isMiniaturized])
1667         {
1668             BOOL needAdjustWindowLevels = FALSE;
1669             BOOL wasVisible;
1670             WineWindow* parent;
1671             WineWindow* child;
1673             [controller transformProcessToForeground];
1674             if ([NSApp isHidden])
1675                 [NSApp unhide:nil];
1676             wasVisible = [self isVisible];
1678             if (activate)
1679                 [NSApp activateIgnoringOtherApps:YES];
1681             NSDisableScreenUpdates();
1683             if ([self becameEligibleParentOrChild])
1684                 needAdjustWindowLevels = TRUE;
1686             if (prev || next)
1687             {
1688                 WineWindow* other = [prev isVisible] ? prev : next;
1689                 NSWindowOrderingMode orderingMode = [prev isVisible] ? NSWindowBelow : NSWindowAbove;
1691                 if (![self isOrdered:orderingMode relativeTo:other])
1692                 {
1693                     WineWindow* ancestor;
1694                     WineWindow* ancestorOfOther;
1696                     [self getSiblingWindowsForWindow:other ancestor:&ancestor ancestorOfOther:&ancestorOfOther];
1697                     if (ancestor)
1698                     {
1699                         [self setAutodisplay:YES];
1700                         if (ancestorOfOther)
1701                         {
1702                             // This window level may not be right for this window based
1703                             // on floating-ness, fullscreen-ness, etc.  But we set it
1704                             // temporarily to allow us to order the windows properly.
1705                             // Then the levels get fixed by -adjustWindowLevels.
1706                             if ([ancestor level] != [ancestorOfOther level])
1707                                 [ancestor setLevel:[ancestorOfOther level]];
1709                             parent = (WineWindow*)ancestor.parentWindow;
1710                             if ([parent isKindOfClass:[WineWindow class]])
1711                                 [parent order:orderingMode childWindow:ancestor relativeTo:ancestorOfOther];
1712                             else
1713                                 [ancestor orderWindow:orderingMode relativeTo:[ancestorOfOther windowNumber]];
1714                         }
1716                         if (!ancestorOfOther || ancestor != self)
1717                         {
1718                             for (child = self;
1719                                  (parent = (WineWindow*)child.parentWindow);
1720                                  child = parent)
1721                             {
1722                                 if ([parent isKindOfClass:[WineWindow class]])
1723                                     [parent order:-orderingMode childWindow:child relativeTo:nil];
1724                                 if (parent == ancestor)
1725                                     break;
1726                             }
1727                         }
1729                         [self checkWineDisplayLink];
1730                         needAdjustWindowLevels = TRUE;
1731                     }
1732                 }
1733             }
1734             else
1735             {
1736                 for (child = self;
1737                      (parent = (WineWindow*)child.parentWindow) && [parent isKindOfClass:[WineWindow class]];
1738                      child = parent)
1739                 {
1740                     [parent order:NSWindowAbove childWindow:child relativeTo:nil];
1741                 }
1743                 // Again, temporarily set level to make sure we can order to
1744                 // the right place.
1745                 next = [controller frontWineWindow];
1746                 if (next && [self level] < [next level])
1747                     [self setLevel:[next level]];
1748                 [self setAutodisplay:YES];
1749                 [self orderFront:nil];
1750                 [self checkWineDisplayLink];
1751                 needAdjustWindowLevels = TRUE;
1752             }
1753             pendingOrderOut = FALSE;
1755             if ([self becameEligibleParentOrChild])
1756                 needAdjustWindowLevels = TRUE;
1758             if (needAdjustWindowLevels)
1759             {
1760                 if (!wasVisible && fullscreen && [self isOnActiveSpace])
1761                     [controller updateFullscreenWindows];
1762                 [controller adjustWindowLevels];
1763             }
1765             if (pendingMinimize)
1766             {
1767                 [self setStyleMask:([self styleMask] | NSMiniaturizableWindowMask)];
1768                 [super miniaturize:nil];
1769                 pendingMinimize = FALSE;
1770             }
1772             NSEnableScreenUpdates();
1774             /* Cocoa may adjust the frame when the window is ordered onto the screen.
1775                Generate a frame-changed event just in case.  The back end will ignore
1776                it if nothing actually changed. */
1777             [self windowDidResize:nil];
1779             if (![self isExcludedFromWindowsMenu])
1780                 [NSApp addWindowsItem:self title:[self title] filename:NO];
1781         }
1782     }
1784     - (void) doOrderOut
1785     {
1786         WineApplicationController* controller = [WineApplicationController sharedController];
1787         BOOL wasVisible = [self isVisible];
1788         BOOL wasOnActiveSpace = [self isOnActiveSpace];
1790         [self endWindowDragging];
1791         [controller windowWillOrderOut:self];
1793         if (enteringFullScreen || exitingFullScreen)
1794         {
1795             pendingOrderOut = TRUE;
1796             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD) |
1797                                              event_mask_for_type(WINDOW_GOT_FOCUS) |
1798                                              event_mask_for_type(WINDOW_LOST_FOCUS) |
1799                                              event_mask_for_type(WINDOW_MAXIMIZE_REQUESTED) |
1800                                              event_mask_for_type(WINDOW_MINIMIZE_REQUESTED) |
1801                                              event_mask_for_type(WINDOW_RESTORE_REQUESTED)
1802                                    forWindow:self];
1803             return;
1804         }
1806         pendingOrderOut = FALSE;
1808         if ([self isMiniaturized])
1809             pendingMinimize = TRUE;
1811         WineWindow* parent = (WineWindow*)self.parentWindow;
1812         if ([parent isKindOfClass:[WineWindow class]])
1813             [parent grabDockIconSnapshotFromWindow:self force:NO];
1815         [self becameIneligibleParentOrChild];
1816         if ([self isMiniaturized] || [self styleMask] & NSFullScreenWindowMask)
1817         {
1818             fakingClose = TRUE;
1819             [self close];
1820             fakingClose = FALSE;
1821         }
1822         else
1823             [self orderOut:nil];
1824         [self checkWineDisplayLink];
1825         [self setBackgroundColor:[NSColor clearColor]];
1826         [self setOpaque:NO];
1827         drawnSinceShown = NO;
1828         savedVisibleState = FALSE;
1829         if (wasVisible && wasOnActiveSpace && fullscreen)
1830             [controller updateFullscreenWindows];
1831         [controller adjustWindowLevels];
1832         [NSApp removeWindowsItem:self];
1834         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD) |
1835                                          event_mask_for_type(WINDOW_GOT_FOCUS) |
1836                                          event_mask_for_type(WINDOW_LOST_FOCUS) |
1837                                          event_mask_for_type(WINDOW_MAXIMIZE_REQUESTED) |
1838                                          event_mask_for_type(WINDOW_MINIMIZE_REQUESTED) |
1839                                          event_mask_for_type(WINDOW_RESTORE_REQUESTED)
1840                                forWindow:self];
1841     }
1843     - (void) updateFullscreen
1844     {
1845         NSRect contentRect = [self contentRectForFrameRect:self.wine_fractionalFrame];
1846         BOOL nowFullscreen = !([self styleMask] & NSFullScreenWindowMask) && screen_covered_by_rect(contentRect, [NSScreen screens]);
1848         if (nowFullscreen != fullscreen)
1849         {
1850             WineApplicationController* controller = [WineApplicationController sharedController];
1852             fullscreen = nowFullscreen;
1853             if ([self isVisible] && [self isOnActiveSpace])
1854                 [controller updateFullscreenWindows];
1856             [controller adjustWindowLevels];
1857         }
1858     }
1860     - (void) setFrameAndWineFrame:(NSRect)frame
1861     {
1862         [self setFrame:frame display:YES];
1864         wineFrame = frame;
1865         roundedWineFrame = self.frame;
1866         CGFloat junk;
1867 #if CGFLOAT_IS_DOUBLE
1868         if ((!modf(wineFrame.origin.x, &junk) && !modf(wineFrame.origin.y, &junk) &&
1869              !modf(wineFrame.size.width, &junk) && !modf(wineFrame.size.height, &junk)) ||
1870             fabs(wineFrame.origin.x - roundedWineFrame.origin.x) >= 1 ||
1871             fabs(wineFrame.origin.y - roundedWineFrame.origin.y) >= 1 ||
1872             fabs(wineFrame.size.width - roundedWineFrame.size.width) >= 1 ||
1873             fabs(wineFrame.size.height - roundedWineFrame.size.height) >= 1)
1874             roundedWineFrame = wineFrame;
1875 #else
1876         if ((!modff(wineFrame.origin.x, &junk) && !modff(wineFrame.origin.y, &junk) &&
1877              !modff(wineFrame.size.width, &junk) && !modff(wineFrame.size.height, &junk)) ||
1878             fabsf(wineFrame.origin.x - roundedWineFrame.origin.x) >= 1 ||
1879             fabsf(wineFrame.origin.y - roundedWineFrame.origin.y) >= 1 ||
1880             fabsf(wineFrame.size.width - roundedWineFrame.size.width) >= 1 ||
1881             fabsf(wineFrame.size.height - roundedWineFrame.size.height) >= 1)
1882             roundedWineFrame = wineFrame;
1883 #endif
1884     }
1886     - (void) setFrameFromWine:(NSRect)contentRect
1887     {
1888         /* Origin is (left, top) in a top-down space.  Need to convert it to
1889            (left, bottom) in a bottom-up space. */
1890         [[WineApplicationController sharedController] flipRect:&contentRect];
1892         /* The back end is establishing a new window size and position.  It's
1893            not interested in any stale events regarding those that may be sitting
1894            in the queue. */
1895         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
1896                                forWindow:self];
1898         if (!NSIsEmptyRect(contentRect))
1899         {
1900             NSRect frame, oldFrame;
1902             oldFrame = self.wine_fractionalFrame;
1903             frame = [self frameRectForContentRect:contentRect];
1904             if (!NSEqualRects(frame, oldFrame))
1905             {
1906                 BOOL equalSizes = NSEqualSizes(frame.size, oldFrame.size);
1907                 BOOL needEnableScreenUpdates = FALSE;
1909                 if ([self preventResizing])
1910                 {
1911                     // Allow the following calls to -setFrame:display: to work even
1912                     // if they would violate the content size constraints. This
1913                     // shouldn't be necessary since the content size constraints are
1914                     // documented to not constrain that method, but it seems to be.
1915                     [self setContentMinSize:NSZeroSize];
1916                     [self setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
1917                 }
1919                 if (equalSizes && [[self childWineWindows] count])
1920                 {
1921                     // If we change the window frame such that the origin moves
1922                     // but the size doesn't change, then Cocoa moves child
1923                     // windows with the parent.  We don't want that so we fake
1924                     // a change of the size and then change it back.
1925                     NSRect bogusFrame = frame;
1926                     bogusFrame.size.width++;
1928                     NSDisableScreenUpdates();
1929                     needEnableScreenUpdates = TRUE;
1931                     ignore_windowResize = TRUE;
1932                     [self setFrame:bogusFrame display:NO];
1933                     ignore_windowResize = FALSE;
1934                 }
1936                 [self setFrameAndWineFrame:frame];
1937                 if ([self preventResizing])
1938                 {
1939                     [self setContentMinSize:contentRect.size];
1940                     [self setContentMaxSize:contentRect.size];
1941                 }
1943                 if (needEnableScreenUpdates)
1944                     NSEnableScreenUpdates();
1946                 if (!enteringFullScreen &&
1947                     [[NSProcessInfo processInfo] systemUptime] - enteredFullScreenTime > 1.0)
1948                     nonFullscreenFrame = frame;
1950                 [self updateFullscreen];
1952                 if ([self isOrderedIn])
1953                 {
1954                     /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
1955                        event.  The back end will ignore it if nothing actually changed. */
1956                     [self windowDidResize:nil];
1957                 }
1958             }
1959         }
1960     }
1962     - (NSRect) wine_fractionalFrame
1963     {
1964         NSRect frame = self.frame;
1965         if (NSEqualRects(frame, roundedWineFrame))
1966             frame = wineFrame;
1967         return frame;
1968     }
1970     - (void) setMacDrvParentWindow:(WineWindow*)parent
1971     {
1972         WineWindow* oldParent = (WineWindow*)[self parentWindow];
1973         if ((oldParent && oldParent != parent) || (!oldParent && latentParentWindow != parent))
1974         {
1975             [oldParent removeChildWineWindow:self];
1976             [latentParentWindow removeChildWineWindow:self];
1977             if ([parent addChildWineWindow:self])
1978                 [[WineApplicationController sharedController] adjustWindowLevels];
1979             [self adjustFullScreenBehavior:[self collectionBehavior]];
1980         }
1981     }
1983     - (void) setDisabled:(BOOL)newValue
1984     {
1985         if (disabled != newValue)
1986         {
1987             disabled = newValue;
1988             [self adjustFeaturesForState];
1989         }
1990     }
1992     - (BOOL) needsTransparency
1993     {
1994         return self.shape || self.colorKeyed || self.usePerPixelAlpha ||
1995                 (gl_surface_mode == GL_SURFACE_BEHIND && [(WineContentView*)self.contentView hasGLDescendant]);
1996     }
1998     - (void) checkTransparency
1999     {
2000         if (![self isOpaque] && !self.needsTransparency)
2001         {
2002             self.shapeChangedSinceLastDraw = TRUE;
2003             [[self contentView] setNeedsDisplay:YES];
2004             [self setBackgroundColor:[NSColor windowBackgroundColor]];
2005             [self setOpaque:YES];
2006         }
2007         else if ([self isOpaque] && self.needsTransparency)
2008         {
2009             self.shapeChangedSinceLastDraw = TRUE;
2010             [[self contentView] setNeedsDisplay:YES];
2011             [self setBackgroundColor:[NSColor clearColor]];
2012             [self setOpaque:NO];
2013         }
2014     }
2016     - (void) setShape:(NSBezierPath*)newShape
2017     {
2018         if (shape == newShape) return;
2020         if (shape)
2021         {
2022             [[self contentView] setNeedsDisplayInRect:[shape bounds]];
2023             [shape release];
2024         }
2025         if (newShape)
2026             [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
2028         shape = [newShape copy];
2029         self.shapeChangedSinceLastDraw = TRUE;
2031         [self checkTransparency];
2032     }
2034     - (void) makeFocused:(BOOL)activate
2035     {
2036         if (activate)
2037         {
2038             [[WineApplicationController sharedController] transformProcessToForeground];
2039             [NSApp activateIgnoringOtherApps:YES];
2040         }
2042         causing_becomeKeyWindow = self;
2043         [self makeKeyWindow];
2044         causing_becomeKeyWindow = nil;
2046         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS) |
2047                                          event_mask_for_type(WINDOW_LOST_FOCUS)
2048                                forWindow:self];
2049     }
2051     - (void) postKey:(uint16_t)keyCode
2052              pressed:(BOOL)pressed
2053            modifiers:(NSUInteger)modifiers
2054                event:(NSEvent*)theEvent
2055     {
2056         macdrv_event* event;
2057         CGEventRef cgevent;
2058         WineApplicationController* controller = [WineApplicationController sharedController];
2060         event = macdrv_create_event(pressed ? KEY_PRESS : KEY_RELEASE, self);
2061         event->key.keycode   = keyCode;
2062         event->key.modifiers = modifiers;
2063         event->key.time_ms   = [controller ticksForEventTime:[theEvent timestamp]];
2065         if ((cgevent = [theEvent CGEvent]))
2066             controller.keyboardType = CGEventGetIntegerValueField(cgevent, kCGKeyboardEventKeyboardType);
2068         [queue postEvent:event];
2070         macdrv_release_event(event);
2072         [controller noteKey:keyCode pressed:pressed];
2073     }
2075     - (void) postKeyEvent:(NSEvent *)theEvent
2076     {
2077         [self flagsChanged:theEvent];
2078         [self postKey:[theEvent keyCode]
2079               pressed:[theEvent type] == NSKeyDown
2080             modifiers:adjusted_modifiers_for_settings([theEvent modifierFlags])
2081                 event:theEvent];
2082     }
2084     - (void) setWineMinSize:(NSSize)minSize maxSize:(NSSize)maxSize
2085     {
2086         savedContentMinSize = minSize;
2087         savedContentMaxSize = maxSize;
2088         if (![self preventResizing])
2089         {
2090             [self setContentMinSize:minSize];
2091             [self setContentMaxSize:maxSize];
2092         }
2093     }
2095     - (WineWindow*) ancestorWineWindow
2096     {
2097         WineWindow* ancestor = self;
2098         for (;;)
2099         {
2100             WineWindow* parent = (WineWindow*)[ancestor parentWindow];
2101             if ([parent isKindOfClass:[WineWindow class]])
2102                 ancestor = parent;
2103             else
2104                 break;
2105         }
2106         return ancestor;
2107     }
2109     - (void) postBroughtForwardEvent
2110     {
2111         macdrv_event* event = macdrv_create_event(WINDOW_BROUGHT_FORWARD, self);
2112         [queue postEvent:event];
2113         macdrv_release_event(event);
2114     }
2116     - (void) postWindowFrameChanged:(NSRect)frame fullscreen:(BOOL)isFullscreen resizing:(BOOL)resizing
2117     {
2118         macdrv_event* event;
2119         NSUInteger style = self.styleMask;
2121         if (isFullscreen)
2122             style |= NSFullScreenWindowMask;
2123         else
2124             style &= ~NSFullScreenWindowMask;
2125         frame = [[self class] contentRectForFrameRect:frame styleMask:style];
2126         [[WineApplicationController sharedController] flipRect:&frame];
2128         /* Coalesce events by discarding any previous ones still in the queue. */
2129         [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
2130                                forWindow:self];
2132         event = macdrv_create_event(WINDOW_FRAME_CHANGED, self);
2133         event->window_frame_changed.frame = cgrect_win_from_mac(NSRectToCGRect(frame));
2134         event->window_frame_changed.fullscreen = isFullscreen;
2135         event->window_frame_changed.in_resize = resizing;
2136         [queue postEvent:event];
2137         macdrv_release_event(event);
2138     }
2140     - (void) updateForCursorClipping
2141     {
2142         [self adjustFeaturesForState];
2143     }
2145     - (void) endWindowDragging
2146     {
2147         if (draggingPhase)
2148         {
2149             if (draggingPhase == 3)
2150             {
2151                 macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, self);
2152                 [queue postEvent:event];
2153                 macdrv_release_event(event);
2154             }
2156             draggingPhase = 0;
2157             [[WineApplicationController sharedController] window:self isBeingDragged:NO];
2158         }
2159     }
2161     - (NSMutableDictionary*) displayIDToDisplayLinkMap
2162     {
2163         static NSMutableDictionary* displayIDToDisplayLinkMap;
2164         if (!displayIDToDisplayLinkMap)
2165         {
2166             displayIDToDisplayLinkMap = [[NSMutableDictionary alloc] init];
2168             [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationDidChangeScreenParametersNotification
2169                                                               object:NSApp
2170                                                                queue:nil
2171                                                           usingBlock:^(NSNotification *note){
2172                 NSMutableSet* badDisplayIDs = [NSMutableSet setWithArray:displayIDToDisplayLinkMap.allKeys];
2173                 NSSet* validDisplayIDs = [NSSet setWithArray:[[NSScreen screens] valueForKeyPath:@"deviceDescription.NSScreenNumber"]];
2174                 [badDisplayIDs minusSet:validDisplayIDs];
2175                 [displayIDToDisplayLinkMap removeObjectsForKeys:[badDisplayIDs allObjects]];
2176             }];
2177         }
2178         return displayIDToDisplayLinkMap;
2179     }
2181     - (WineDisplayLink*) wineDisplayLink
2182     {
2183         if (!_lastDisplayID)
2184             return nil;
2186         NSMutableDictionary* displayIDToDisplayLinkMap = [self displayIDToDisplayLinkMap];
2187         return [displayIDToDisplayLinkMap objectForKey:[NSNumber numberWithUnsignedInt:_lastDisplayID]];
2188     }
2190     - (void) checkWineDisplayLink
2191     {
2192         NSScreen* screen = self.screen;
2193         if (![self isVisible] || ![self isOnActiveSpace] || [self isMiniaturized] || [self isEmptyShaped])
2194             screen = nil;
2195 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
2196         if ([self respondsToSelector:@selector(occlusionState)] && !(self.occlusionState & NSWindowOcclusionStateVisible))
2197             screen = nil;
2198 #endif
2200         NSNumber* displayIDNumber = [screen.deviceDescription objectForKey:@"NSScreenNumber"];
2201         CGDirectDisplayID displayID = [displayIDNumber unsignedIntValue];
2202         if (displayID == _lastDisplayID)
2203             return;
2205         NSMutableDictionary* displayIDToDisplayLinkMap = [self displayIDToDisplayLinkMap];
2207         if (_lastDisplayID)
2208         {
2209             WineDisplayLink* link = [displayIDToDisplayLinkMap objectForKey:[NSNumber numberWithUnsignedInt:_lastDisplayID]];
2210             [link removeWindow:self];
2211         }
2212         if (displayID)
2213         {
2214             WineDisplayLink* link = [displayIDToDisplayLinkMap objectForKey:displayIDNumber];
2215             if (!link)
2216             {
2217                 link = [[[WineDisplayLink alloc] initWithDisplayID:displayID] autorelease];
2218                 [displayIDToDisplayLinkMap setObject:link forKey:displayIDNumber];
2219             }
2220             [link addWindow:self];
2221             [self displayIfNeeded];
2222         }
2223         _lastDisplayID = displayID;
2224     }
2226     - (BOOL) isEmptyShaped
2227     {
2228         return (self.shapeData.length == sizeof(CGRectZero) && !memcmp(self.shapeData.bytes, &CGRectZero, sizeof(CGRectZero)));
2229     }
2231     - (BOOL) canProvideSnapshot
2232     {
2233         return (self.windowNumber > 0 && ![self isEmptyShaped]);
2234     }
2236     - (void) grabDockIconSnapshotFromWindow:(WineWindow*)window force:(BOOL)force
2237     {
2238         if (![self isEmptyShaped])
2239             return;
2241         NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
2242         if (!force && now < lastDockIconSnapshot + 1)
2243             return;
2245         if (window)
2246         {
2247             if (![window canProvideSnapshot])
2248                 return;
2249         }
2250         else
2251         {
2252             CGFloat bestArea;
2253             for (WineWindow* childWindow in self.childWindows)
2254             {
2255                 if (![childWindow isKindOfClass:[WineWindow class]] || ![childWindow canProvideSnapshot])
2256                     continue;
2258                 NSSize size = childWindow.frame.size;
2259                 CGFloat area = size.width * size.height;
2260                 if (!window || area > bestArea)
2261                 {
2262                     window = childWindow;
2263                     bestArea = area;
2264                 }
2265             }
2267             if (!window)
2268                 return;
2269         }
2271         const void* windowID = (const void*)(CGWindowID)window.windowNumber;
2272         CFArrayRef windowIDs = CFArrayCreate(NULL, &windowID, 1, NULL);
2273         CGImageRef windowImage = CGWindowListCreateImageFromArray(CGRectNull, windowIDs, kCGWindowImageBoundsIgnoreFraming);
2274         CFRelease(windowIDs);
2275         if (!windowImage)
2276             return;
2278         NSImage* appImage = [NSApp applicationIconImage];
2279         if (!appImage)
2280             appImage = [NSImage imageNamed:NSImageNameApplicationIcon];
2282         NSImage* dockIcon = [[[NSImage alloc] initWithSize:NSMakeSize(256, 256)] autorelease];
2283         [dockIcon lockFocus];
2285         CGContextRef cgcontext = [[NSGraphicsContext currentContext] graphicsPort];
2287         CGRect rect = CGRectMake(8, 8, 240, 240);
2288         size_t width = CGImageGetWidth(windowImage);
2289         size_t height = CGImageGetHeight(windowImage);
2290         if (width > height)
2291         {
2292             rect.size.height *= height / (double)width;
2293             rect.origin.y += (CGRectGetWidth(rect) - CGRectGetHeight(rect)) / 2;
2294         }
2295         else if (width != height)
2296         {
2297             rect.size.width *= width / (double)height;
2298             rect.origin.x += (CGRectGetHeight(rect) - CGRectGetWidth(rect)) / 2;
2299         }
2301         CGContextDrawImage(cgcontext, rect, windowImage);
2302         [appImage drawInRect:NSMakeRect(156, 4, 96, 96)
2303                     fromRect:NSZeroRect
2304                    operation:NSCompositeSourceOver
2305                     fraction:1
2306               respectFlipped:YES
2307                        hints:nil];
2309         [dockIcon unlockFocus];
2311         CGImageRelease(windowImage);
2313         NSImageView* imageView = (NSImageView*)self.dockTile.contentView;
2314         if (![imageView isKindOfClass:[NSImageView class]])
2315         {
2316             imageView = [[[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 256, 256)] autorelease];
2317             imageView.imageScaling = NSImageScaleProportionallyUpOrDown;
2318             self.dockTile.contentView = imageView;
2319         }
2320         imageView.image = dockIcon;
2321         [self.dockTile display];
2322         lastDockIconSnapshot = now;
2323     }
2325     - (void) checkEmptyShaped
2326     {
2327         if (self.dockTile.contentView && ![self isEmptyShaped])
2328         {
2329             self.dockTile.contentView = nil;
2330             lastDockIconSnapshot = 0;
2331         }
2332         [self checkWineDisplayLink];
2333     }
2336     /*
2337      * ---------- NSWindow method overrides ----------
2338      */
2339     - (BOOL) canBecomeKeyWindow
2340     {
2341         if (causing_becomeKeyWindow == self) return YES;
2342         if (self.disabled || self.noActivate) return NO;
2343         return [self isKeyWindow];
2344     }
2346     - (BOOL) canBecomeMainWindow
2347     {
2348         return [self canBecomeKeyWindow];
2349     }
2351     - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
2352     {
2353         // If a window is sized to completely cover a screen, then it's in
2354         // full-screen mode.  In that case, we don't allow NSWindow to constrain
2355         // it.
2356         NSArray* screens = [NSScreen screens];
2357         NSRect contentRect = [self contentRectForFrameRect:frameRect];
2358         if (!screen_covered_by_rect(contentRect, screens) &&
2359             frame_intersects_screens(frameRect, screens))
2360             frameRect = [super constrainFrameRect:frameRect toScreen:screen];
2361         return frameRect;
2362     }
2364     // This private method of NSWindow is called as Cocoa reacts to the display
2365     // configuration changing.  Among other things, it adjusts the window's
2366     // frame based on how the screen(s) changed size.  That tells Wine that the
2367     // window has been moved.  We don't want that.  Rather, we want to make
2368     // sure that the WinAPI notion of the window position is maintained/
2369     // restored, possibly undoing or overriding Cocoa's adjustment.
2370     //
2371     // So, we queue a REASSERT_WINDOW_POSITION event to the back end before
2372     // Cocoa has a chance to adjust the frame, thus preceding any resulting
2373     // WINDOW_FRAME_CHANGED event that may get queued.  The back end will
2374     // reassert its notion of the position.  That call won't get processed
2375     // until after this method returns, so it will override whatever this
2376     // method does to the window position.  It will also discard any pending
2377     // WINDOW_FRAME_CHANGED events.
2378     //
2379     // Unfortunately, the only way I've found to know when Cocoa is _about to_
2380     // adjust the window's position due to a display change is to hook into
2381     // this private method.  This private method has remained stable from 10.6
2382     // through 10.11.  If it does change, the most likely thing is that it
2383     // will be removed and no longer called and this fix will simply stop
2384     // working.  The only real danger would be if Apple changed the return type
2385     // to a struct or floating-point type, which would change the calling
2386     // convention.
2387     - (id) _displayChanged
2388     {
2389         macdrv_event* event = macdrv_create_event(REASSERT_WINDOW_POSITION, self);
2390         [queue postEvent:event];
2391         macdrv_release_event(event);
2393         return [super _displayChanged];
2394     }
2396     - (BOOL) isExcludedFromWindowsMenu
2397     {
2398         return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
2399     }
2401     - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
2402     {
2403         BOOL ret = [super validateMenuItem:menuItem];
2405         if ([menuItem action] == @selector(makeKeyAndOrderFront:))
2406             ret = [self isKeyWindow] || (!self.disabled && !self.noActivate);
2407         if ([menuItem action] == @selector(toggleFullScreen:) && (self.disabled || maximized))
2408             ret = NO;
2410         return ret;
2411     }
2413     /* We don't call this.  It's the action method of the items in the Window menu. */
2414     - (void) makeKeyAndOrderFront:(id)sender
2415     {
2416         if ([self isMiniaturized])
2417             [self deminiaturize:nil];
2418         [self orderBelow:nil orAbove:nil activate:NO];
2419         [[self ancestorWineWindow] postBroughtForwardEvent];
2421         if (![self isKeyWindow] && !self.disabled && !self.noActivate)
2422             [[WineApplicationController sharedController] windowGotFocus:self];
2423     }
2425     - (void) sendEvent:(NSEvent*)event
2426     {
2427         NSEventType type = event.type;
2429         /* NSWindow consumes certain key-down events as part of Cocoa's keyboard
2430            interface control.  For example, Control-Tab switches focus among
2431            views.  We want to bypass that feature, so directly route key-down
2432            events to -keyDown:. */
2433         if (type == NSKeyDown)
2434             [[self firstResponder] keyDown:event];
2435         else
2436         {
2437             if (!draggingPhase && maximized && ![self isMovable] &&
2438                 ![self allowsMovingWithMaximized:YES] && [self allowsMovingWithMaximized:NO] &&
2439                 type == NSLeftMouseDown && (self.styleMask & NSTitledWindowMask))
2440             {
2441                 NSRect titleBar = self.frame;
2442                 NSRect contentRect = [self contentRectForFrameRect:titleBar];
2443                 titleBar.size.height = NSMaxY(titleBar) - NSMaxY(contentRect);
2444                 titleBar.origin.y = NSMaxY(contentRect);
2446                 dragStartPosition = [self convertBaseToScreen:event.locationInWindow];
2448                 if (NSMouseInRect(dragStartPosition, titleBar, NO))
2449                 {
2450                     static const NSWindowButton buttons[] = {
2451                         NSWindowCloseButton,
2452                         NSWindowMiniaturizeButton,
2453                         NSWindowZoomButton,
2454                         NSWindowFullScreenButton,
2455                     };
2456                     BOOL hitButton = NO;
2457                     int i;
2459                     for (i = 0; i < sizeof(buttons) / sizeof(buttons[0]); i++)
2460                     {
2461                         NSButton* button;
2463                         if (buttons[i] == NSWindowFullScreenButton && ![self respondsToSelector:@selector(toggleFullScreen:)])
2464                             continue;
2466                         button = [self standardWindowButton:buttons[i]];
2467                         if ([button hitTest:[button.superview convertPoint:event.locationInWindow fromView:nil]])
2468                         {
2469                             hitButton = YES;
2470                             break;
2471                         }
2472                     }
2474                     if (!hitButton)
2475                     {
2476                         draggingPhase = 1;
2477                         dragWindowStartPosition = NSMakePoint(NSMinX(titleBar), NSMaxY(titleBar));
2478                         [[WineApplicationController sharedController] window:self isBeingDragged:YES];
2479                     }
2480                 }
2481             }
2482             else if (draggingPhase && (type == NSLeftMouseDragged || type == NSLeftMouseUp))
2483             {
2484                 if ([self isMovable])
2485                 {
2486                     NSPoint point = [self convertBaseToScreen:event.locationInWindow];
2487                     NSPoint newTopLeft = dragWindowStartPosition;
2489                     newTopLeft.x += point.x - dragStartPosition.x;
2490                     newTopLeft.y += point.y - dragStartPosition.y;
2492                     if (draggingPhase == 2)
2493                     {
2494                         macdrv_event* mevent = macdrv_create_event(WINDOW_DRAG_BEGIN, self);
2495                         mevent->window_drag_begin.no_activate = [event wine_commandKeyDown];
2496                         [queue postEvent:mevent];
2497                         macdrv_release_event(mevent);
2499                         draggingPhase = 3;
2500                     }
2502                     [self setFrameTopLeftPoint:newTopLeft];
2503                 }
2504                 else if (draggingPhase == 1 && type == NSLeftMouseDragged)
2505                 {
2506                     macdrv_event* event;
2507                     NSRect frame = [self contentRectForFrameRect:self.frame];
2509                     [[WineApplicationController sharedController] flipRect:&frame];
2511                     event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self);
2512                     event->window_restore_requested.keep_frame = TRUE;
2513                     event->window_restore_requested.frame = cgrect_win_from_mac(NSRectToCGRect(frame));
2514                     [queue postEvent:event];
2515                     macdrv_release_event(event);
2517                     draggingPhase = 2;
2518                 }
2520                 if (type == NSLeftMouseUp)
2521                     [self endWindowDragging];
2522             }
2524             [super sendEvent:event];
2525         }
2526     }
2528     - (void) miniaturize:(id)sender
2529     {
2530         macdrv_event* event = macdrv_create_event(WINDOW_MINIMIZE_REQUESTED, self);
2531         [queue postEvent:event];
2532         macdrv_release_event(event);
2534         WineWindow* parent = (WineWindow*)self.parentWindow;
2535         if ([parent isKindOfClass:[WineWindow class]])
2536             [parent grabDockIconSnapshotFromWindow:self force:YES];
2537     }
2539     - (void) toggleFullScreen:(id)sender
2540     {
2541         if (!self.disabled && !maximized)
2542             [super toggleFullScreen:sender];
2543     }
2545     - (void) setViewsNeedDisplay:(BOOL)value
2546     {
2547         if (value && ![self viewsNeedDisplay])
2548         {
2549             WineDisplayLink* link = [self wineDisplayLink];
2550             if (link)
2551             {
2552                 NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
2553                 if (_lastDisplayTime + [link refreshPeriod] < now)
2554                     [self setAutodisplay:YES];
2555                 else
2556                 {
2557                     [link start];
2558                     _lastDisplayTime = now;
2559                 }
2560             }
2561             else
2562                 [self setAutodisplay:YES];
2563         }
2564         [super setViewsNeedDisplay:value];
2565     }
2567     - (void) display
2568     {
2569         _lastDisplayTime = [[NSProcessInfo processInfo] systemUptime];
2570         [super display];
2571         if (_lastDisplayID)
2572             [self setAutodisplay:NO];
2573     }
2575     - (void) displayIfNeeded
2576     {
2577         _lastDisplayTime = [[NSProcessInfo processInfo] systemUptime];
2578         [super displayIfNeeded];
2579         if (_lastDisplayID)
2580             [self setAutodisplay:NO];
2581     }
2583     - (void) setFrame:(NSRect)frameRect display:(BOOL)flag
2584     {
2585         if (flag)
2586             [self setAutodisplay:YES];
2587         [super setFrame:frameRect display:flag];
2588     }
2590     - (void) setFrame:(NSRect)frameRect display:(BOOL)displayFlag animate:(BOOL)animateFlag
2591     {
2592         if (displayFlag)
2593             [self setAutodisplay:YES];
2594         [super setFrame:frameRect display:displayFlag animate:animateFlag];
2595     }
2597     - (void) windowDidDrawContent
2598     {
2599         if (!drawnSinceShown)
2600         {
2601             drawnSinceShown = YES;
2602             dispatch_async(dispatch_get_main_queue(), ^{
2603                 [self checkTransparency];
2604             });
2605         }
2606     }
2608     - (NSArray*) childWineWindows
2609     {
2610         NSArray* childWindows = self.childWindows;
2611         NSIndexSet* indexes = [childWindows indexesOfObjectsPassingTest:^BOOL(id child, NSUInteger idx, BOOL *stop){
2612             return [child isKindOfClass:[WineWindow class]];
2613         }];
2614         return [childWindows objectsAtIndexes:indexes];
2615     }
2617     - (void) updateForGLSubviews
2618     {
2619         if (gl_surface_mode == GL_SURFACE_BEHIND)
2620             [self checkTransparency];
2621     }
2623     - (void) setRetinaMode:(int)mode
2624     {
2625         NSRect frame;
2626         double scale = mode ? 0.5 : 2.0;
2627         NSAffineTransform* transform = [NSAffineTransform transform];
2629         [transform scaleBy:scale];
2631         if (shape)
2632             [shape transformUsingAffineTransform:transform];
2634         for (WineBaseView* subview in [self.contentView subviews])
2635         {
2636             if ([subview isKindOfClass:[WineBaseView class]])
2637                 [subview setRetinaMode:mode];
2638         }
2640         frame = [self contentRectForFrameRect:self.wine_fractionalFrame];
2641         frame.origin.x *= scale;
2642         frame.origin.y *= scale;
2643         frame.size.width *= scale;
2644         frame.size.height *= scale;
2645         frame = [self frameRectForContentRect:frame];
2647         savedContentMinSize = [transform transformSize:savedContentMinSize];
2648         if (savedContentMaxSize.width != FLT_MAX && savedContentMaxSize.width != CGFLOAT_MAX)
2649             savedContentMaxSize.width *= scale;
2650         if (savedContentMaxSize.height != FLT_MAX && savedContentMaxSize.height != CGFLOAT_MAX)
2651             savedContentMaxSize.height *= scale;
2653         self.contentMinSize = [transform transformSize:self.contentMinSize];
2654         NSSize temp = self.contentMaxSize;
2655         if (temp.width != FLT_MAX && temp.width != CGFLOAT_MAX)
2656             temp.width *= scale;
2657         if (temp.height != FLT_MAX && temp.height != CGFLOAT_MAX)
2658             temp.height *= scale;
2659         self.contentMaxSize = temp;
2661         ignore_windowResize = TRUE;
2662         [self setFrameAndWineFrame:frame];
2663         ignore_windowResize = FALSE;
2664     }
2667     /*
2668      * ---------- NSResponder method overrides ----------
2669      */
2670     - (void) keyDown:(NSEvent *)theEvent
2671     {
2672         if ([theEvent isARepeat])
2673         {
2674             if (!allowKeyRepeats)
2675                 return;
2676         }
2677         else
2678             allowKeyRepeats = YES;
2680         [self postKeyEvent:theEvent];
2681     }
2683     - (void) flagsChanged:(NSEvent *)theEvent
2684     {
2685         static const struct {
2686             NSUInteger  mask;
2687             uint16_t    keycode;
2688         } modifiers[] = {
2689             { NX_ALPHASHIFTMASK,        kVK_CapsLock },
2690             { NX_DEVICELSHIFTKEYMASK,   kVK_Shift },
2691             { NX_DEVICERSHIFTKEYMASK,   kVK_RightShift },
2692             { NX_DEVICELCTLKEYMASK,     kVK_Control },
2693             { NX_DEVICERCTLKEYMASK,     kVK_RightControl },
2694             { NX_DEVICELALTKEYMASK,     kVK_Option },
2695             { NX_DEVICERALTKEYMASK,     kVK_RightOption },
2696             { NX_DEVICELCMDKEYMASK,     kVK_Command },
2697             { NX_DEVICERCMDKEYMASK,     kVK_RightCommand },
2698         };
2700         NSUInteger modifierFlags = adjusted_modifiers_for_settings([theEvent modifierFlags]);
2701         NSUInteger changed;
2702         int i, last_changed;
2704         fix_device_modifiers_by_generic(&modifierFlags);
2705         changed = modifierFlags ^ lastModifierFlags;
2707         last_changed = -1;
2708         for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++)
2709             if (changed & modifiers[i].mask)
2710                 last_changed = i;
2712         for (i = 0; i <= last_changed; i++)
2713         {
2714             if (changed & modifiers[i].mask)
2715             {
2716                 BOOL pressed = (modifierFlags & modifiers[i].mask) != 0;
2718                 if (pressed)
2719                     allowKeyRepeats = NO;
2721                 if (i == last_changed)
2722                     lastModifierFlags = modifierFlags;
2723                 else
2724                 {
2725                     lastModifierFlags ^= modifiers[i].mask;
2726                     fix_generic_modifiers_by_device(&lastModifierFlags);
2727                 }
2729                 // Caps lock generates one event for each press-release action.
2730                 // We need to simulate a pair of events for each actual event.
2731                 if (modifiers[i].mask == NX_ALPHASHIFTMASK)
2732                 {
2733                     [self postKey:modifiers[i].keycode
2734                           pressed:TRUE
2735                         modifiers:lastModifierFlags
2736                             event:(NSEvent*)theEvent];
2737                     pressed = FALSE;
2738                 }
2740                 [self postKey:modifiers[i].keycode
2741                       pressed:pressed
2742                     modifiers:lastModifierFlags
2743                         event:(NSEvent*)theEvent];
2744             }
2745         }
2746     }
2748     - (void) applicationWillHide
2749     {
2750         savedVisibleState = [self isVisible];
2751     }
2753     - (void) applicationDidUnhide
2754     {
2755         if ([self isVisible])
2756             [self becameEligibleParentOrChild];
2757     }
2760     /*
2761      * ---------- NSWindowDelegate methods ----------
2762      */
2763     - (NSSize) window:(NSWindow*)window willUseFullScreenContentSize:(NSSize)proposedSize
2764     {
2765         macdrv_query* query;
2766         NSSize size;
2768         query = macdrv_create_query();
2769         query->type = QUERY_MIN_MAX_INFO;
2770         query->window = (macdrv_window)[self retain];
2771         [self.queue query:query timeout:0.5];
2772         macdrv_release_query(query);
2774         size = [self contentMaxSize];
2775         if (proposedSize.width < size.width)
2776             size.width = proposedSize.width;
2777         if (proposedSize.height < size.height)
2778             size.height = proposedSize.height;
2779         return size;
2780     }
2782     - (void)windowDidBecomeKey:(NSNotification *)notification
2783     {
2784         WineApplicationController* controller = [WineApplicationController sharedController];
2785         NSEvent* event = [controller lastFlagsChanged];
2786         if (event)
2787             [self flagsChanged:event];
2789         if (causing_becomeKeyWindow == self) return;
2791         [controller windowGotFocus:self];
2792     }
2794     - (void) windowDidChangeOcclusionState:(NSNotification*)notification
2795     {
2796         [self checkWineDisplayLink];
2797     }
2799     - (void) windowDidChangeScreen:(NSNotification*)notification
2800     {
2801         [self checkWineDisplayLink];
2802     }
2804     - (void)windowDidDeminiaturize:(NSNotification *)notification
2805     {
2806         WineApplicationController* controller = [WineApplicationController sharedController];
2808         if (!ignore_windowDeminiaturize)
2809             [self postDidUnminimizeEvent];
2810         ignore_windowDeminiaturize = FALSE;
2812         [self becameEligibleParentOrChild];
2814         if (fullscreen && [self isOnActiveSpace])
2815             [controller updateFullscreenWindows];
2816         [controller adjustWindowLevels];
2818         if (![self parentWindow])
2819             [self postBroughtForwardEvent];
2821         if (!self.disabled && !self.noActivate)
2822         {
2823             causing_becomeKeyWindow = self;
2824             [self makeKeyWindow];
2825             causing_becomeKeyWindow = nil;
2826             [controller windowGotFocus:self];
2827         }
2829         [self windowDidResize:notification];
2830         [self checkWineDisplayLink];
2831     }
2833     - (void) windowDidEndLiveResize:(NSNotification *)notification
2834     {
2835         if (!maximized)
2836         {
2837             macdrv_event* event = macdrv_create_event(WINDOW_RESIZE_ENDED, self);
2838             [queue postEvent:event];
2839             macdrv_release_event(event);
2840         }
2841     }
2843     - (void) windowDidEnterFullScreen:(NSNotification*)notification
2844     {
2845         enteringFullScreen = FALSE;
2846         enteredFullScreenTime = [[NSProcessInfo processInfo] systemUptime];
2847         if (pendingOrderOut)
2848             [self doOrderOut];
2849     }
2851     - (void) windowDidExitFullScreen:(NSNotification*)notification
2852     {
2853         exitingFullScreen = FALSE;
2854         [self setFrameAndWineFrame:nonFullscreenFrame];
2855         [self windowDidResize:nil];
2856         if (pendingOrderOut)
2857             [self doOrderOut];
2858     }
2860     - (void) windowDidFailToEnterFullScreen:(NSWindow*)window
2861     {
2862         enteringFullScreen = FALSE;
2863         enteredFullScreenTime = 0;
2864         if (pendingOrderOut)
2865             [self doOrderOut];
2866     }
2868     - (void) windowDidFailToExitFullScreen:(NSWindow*)window
2869     {
2870         exitingFullScreen = FALSE;
2871         [self windowDidResize:nil];
2872         if (pendingOrderOut)
2873             [self doOrderOut];
2874     }
2876     - (void)windowDidMiniaturize:(NSNotification *)notification
2877     {
2878         if (fullscreen && [self isOnActiveSpace])
2879             [[WineApplicationController sharedController] updateFullscreenWindows];
2880         [self checkWineDisplayLink];
2881     }
2883     - (void)windowDidMove:(NSNotification *)notification
2884     {
2885         [self windowDidResize:notification];
2886     }
2888     - (void)windowDidResignKey:(NSNotification *)notification
2889     {
2890         macdrv_event* event;
2892         if (causing_becomeKeyWindow) return;
2894         event = macdrv_create_event(WINDOW_LOST_FOCUS, self);
2895         [queue postEvent:event];
2896         macdrv_release_event(event);
2897     }
2899     - (void)windowDidResize:(NSNotification *)notification
2900     {
2901         NSRect frame = self.wine_fractionalFrame;
2903         if ([self inLiveResize])
2904         {
2905             if (NSMinX(frame) != NSMinX(frameAtResizeStart))
2906                 resizingFromLeft = TRUE;
2907             if (NSMaxY(frame) != NSMaxY(frameAtResizeStart))
2908                 resizingFromTop = TRUE;
2909         }
2911         if (ignore_windowResize || exitingFullScreen) return;
2913         if ([self preventResizing])
2914         {
2915             NSRect contentRect = [self contentRectForFrameRect:frame];
2916             [self setContentMinSize:contentRect.size];
2917             [self setContentMaxSize:contentRect.size];
2918         }
2920         [self postWindowFrameChanged:frame
2921                           fullscreen:([self styleMask] & NSFullScreenWindowMask) != 0
2922                             resizing:[self inLiveResize]];
2924         [[[self contentView] inputContext] invalidateCharacterCoordinates];
2925         [self updateFullscreen];
2926     }
2928     - (BOOL)windowShouldClose:(id)sender
2929     {
2930         macdrv_event* event = macdrv_create_event(WINDOW_CLOSE_REQUESTED, self);
2931         [queue postEvent:event];
2932         macdrv_release_event(event);
2933         return NO;
2934     }
2936     - (BOOL) windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame
2937     {
2938         if (maximized)
2939         {
2940             macdrv_event* event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self);
2941             [queue postEvent:event];
2942             macdrv_release_event(event);
2943             return NO;
2944         }
2945         else if (!resizable)
2946         {
2947             macdrv_event* event = macdrv_create_event(WINDOW_MAXIMIZE_REQUESTED, self);
2948             [queue postEvent:event];
2949             macdrv_release_event(event);
2950             return NO;
2951         }
2953         return YES;
2954     }
2956     - (void) windowWillClose:(NSNotification*)notification
2957     {
2958         WineWindow* child;
2960         if (fakingClose) return;
2961         if (latentParentWindow)
2962         {
2963             [latentParentWindow->latentChildWindows removeObjectIdenticalTo:self];
2964             self.latentParentWindow = nil;
2965         }
2967         for (child in latentChildWindows)
2968         {
2969             if (child.latentParentWindow == self)
2970                 child.latentParentWindow = nil;
2971         }
2972         [latentChildWindows removeAllObjects];
2973     }
2975     - (void) windowWillEnterFullScreen:(NSNotification*)notification
2976     {
2977         enteringFullScreen = TRUE;
2978         nonFullscreenFrame = self.wine_fractionalFrame;
2979     }
2981     - (void) windowWillExitFullScreen:(NSNotification*)notification
2982     {
2983         exitingFullScreen = TRUE;
2984         [self postWindowFrameChanged:nonFullscreenFrame fullscreen:FALSE resizing:FALSE];
2985     }
2987     - (void)windowWillMiniaturize:(NSNotification *)notification
2988     {
2989         [self becameIneligibleParentOrChild];
2990         [self grabDockIconSnapshotFromWindow:nil force:NO];
2991     }
2993     - (NSSize) windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize
2994     {
2995         if ([self inLiveResize])
2996         {
2997             if (maximized)
2998                 return self.wine_fractionalFrame.size;
3000             NSRect rect;
3001             macdrv_query* query;
3003             rect = [self frame];
3004             if (resizingFromLeft)
3005                 rect.origin.x = NSMaxX(rect) - frameSize.width;
3006             if (!resizingFromTop)
3007                 rect.origin.y = NSMaxY(rect) - frameSize.height;
3008             rect.size = frameSize;
3009             rect = [self contentRectForFrameRect:rect];
3010             [[WineApplicationController sharedController] flipRect:&rect];
3012             query = macdrv_create_query();
3013             query->type = QUERY_RESIZE_SIZE;
3014             query->window = (macdrv_window)[self retain];
3015             query->resize_size.rect = cgrect_win_from_mac(NSRectToCGRect(rect));
3016             query->resize_size.from_left = resizingFromLeft;
3017             query->resize_size.from_top = resizingFromTop;
3019             if ([self.queue query:query timeout:0.1])
3020             {
3021                 rect = NSRectFromCGRect(cgrect_mac_from_win(query->resize_size.rect));
3022                 rect = [self frameRectForContentRect:rect];
3023                 frameSize = rect.size;
3024             }
3026             macdrv_release_query(query);
3027         }
3029         return frameSize;
3030     }
3032     - (void) windowWillStartLiveResize:(NSNotification *)notification
3033     {
3034         [self endWindowDragging];
3036         if (maximized)
3037         {
3038             macdrv_event* event;
3039             NSRect frame = [self contentRectForFrameRect:self.frame];
3041             [[WineApplicationController sharedController] flipRect:&frame];
3043             event = macdrv_create_event(WINDOW_RESTORE_REQUESTED, self);
3044             event->window_restore_requested.keep_frame = TRUE;
3045             event->window_restore_requested.frame = cgrect_win_from_mac(NSRectToCGRect(frame));
3046             [queue postEvent:event];
3047             macdrv_release_event(event);
3048         }
3049         else
3050             [self sendResizeStartQuery];
3052         frameAtResizeStart = [self frame];
3053         resizingFromLeft = resizingFromTop = FALSE;
3054     }
3056     - (NSRect) windowWillUseStandardFrame:(NSWindow*)window defaultFrame:(NSRect)proposedFrame
3057     {
3058         macdrv_query* query;
3059         NSRect currentContentRect, proposedContentRect, newContentRect, screenRect;
3060         NSSize maxSize;
3062         query = macdrv_create_query();
3063         query->type = QUERY_MIN_MAX_INFO;
3064         query->window = (macdrv_window)[self retain];
3065         [self.queue query:query timeout:0.5];
3066         macdrv_release_query(query);
3068         currentContentRect = [self contentRectForFrameRect:[self frame]];
3069         proposedContentRect = [self contentRectForFrameRect:proposedFrame];
3071         maxSize = [self contentMaxSize];
3072         newContentRect.size.width = MIN(NSWidth(proposedContentRect), maxSize.width);
3073         newContentRect.size.height = MIN(NSHeight(proposedContentRect), maxSize.height);
3075         // Try to keep the top-left corner where it is.
3076         newContentRect.origin.x = NSMinX(currentContentRect);
3077         newContentRect.origin.y = NSMaxY(currentContentRect) - NSHeight(newContentRect);
3079         // If that pushes the bottom or right off the screen, pull it up and to the left.
3080         screenRect = [self contentRectForFrameRect:[[self screen] visibleFrame]];
3081         if (NSMaxX(newContentRect) > NSMaxX(screenRect))
3082             newContentRect.origin.x = NSMaxX(screenRect) - NSWidth(newContentRect);
3083         if (NSMinY(newContentRect) < NSMinY(screenRect))
3084             newContentRect.origin.y = NSMinY(screenRect);
3086         // If that pushes the top or left off the screen, push it down and the right
3087         // again.  Do this last because the top-left corner is more important than the
3088         // bottom-right.
3089         if (NSMinX(newContentRect) < NSMinX(screenRect))
3090             newContentRect.origin.x = NSMinX(screenRect);
3091         if (NSMaxY(newContentRect) > NSMaxY(screenRect))
3092             newContentRect.origin.y = NSMaxY(screenRect) - NSHeight(newContentRect);
3094         return [self frameRectForContentRect:newContentRect];
3095     }
3098     /*
3099      * ---------- NSPasteboardOwner methods ----------
3100      */
3101     - (void) pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
3102     {
3103         macdrv_query* query = macdrv_create_query();
3104         query->type = QUERY_PASTEBOARD_DATA;
3105         query->window = (macdrv_window)[self retain];
3106         query->pasteboard_data.type = (CFStringRef)[type copy];
3108         [self.queue query:query timeout:3];
3109         macdrv_release_query(query);
3110     }
3112     - (void) pasteboardChangedOwner:(NSPasteboard*)sender
3113     {
3114         macdrv_event* event = macdrv_create_event(LOST_PASTEBOARD_OWNERSHIP, self);
3115         [queue postEvent:event];
3116         macdrv_release_event(event);
3117     }
3120     /*
3121      * ---------- NSDraggingDestination methods ----------
3122      */
3123     - (NSDragOperation) draggingEntered:(id <NSDraggingInfo>)sender
3124     {
3125         return [self draggingUpdated:sender];
3126     }
3128     - (void) draggingExited:(id <NSDraggingInfo>)sender
3129     {
3130         // This isn't really a query.  We don't need any response.  However, it
3131         // has to be processed in a similar manner as the other drag-and-drop
3132         // queries in order to maintain the proper order of operations.
3133         macdrv_query* query = macdrv_create_query();
3134         query->type = QUERY_DRAG_EXITED;
3135         query->window = (macdrv_window)[self retain];
3137         [self.queue query:query timeout:0.1];
3138         macdrv_release_query(query);
3139     }
3141     - (NSDragOperation) draggingUpdated:(id <NSDraggingInfo>)sender
3142     {
3143         NSDragOperation ret;
3144         NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
3145         CGPoint cgpt = cgpoint_win_from_mac(NSPointToCGPoint(pt));
3146         NSPasteboard* pb = [sender draggingPasteboard];
3148         macdrv_query* query = macdrv_create_query();
3149         query->type = QUERY_DRAG_OPERATION;
3150         query->window = (macdrv_window)[self retain];
3151         query->drag_operation.x = floor(cgpt.x);
3152         query->drag_operation.y = floor(cgpt.y);
3153         query->drag_operation.offered_ops = [sender draggingSourceOperationMask];
3154         query->drag_operation.accepted_op = NSDragOperationNone;
3155         query->drag_operation.pasteboard = (CFTypeRef)[pb retain];
3157         [self.queue query:query timeout:3];
3158         ret = query->status ? query->drag_operation.accepted_op : NSDragOperationNone;
3159         macdrv_release_query(query);
3161         return ret;
3162     }
3164     - (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
3165     {
3166         BOOL ret;
3167         NSPoint pt = [[self contentView] convertPoint:[sender draggingLocation] fromView:nil];
3168         CGPoint cgpt = cgpoint_win_from_mac(NSPointToCGPoint(pt));
3169         NSPasteboard* pb = [sender draggingPasteboard];
3171         macdrv_query* query = macdrv_create_query();
3172         query->type = QUERY_DRAG_DROP;
3173         query->window = (macdrv_window)[self retain];
3174         query->drag_drop.x = floor(cgpt.x);
3175         query->drag_drop.y = floor(cgpt.y);
3176         query->drag_drop.op = [sender draggingSourceOperationMask];
3177         query->drag_drop.pasteboard = (CFTypeRef)[pb retain];
3179         [self.queue query:query timeout:3 * 60 flags:WineQueryProcessEvents];
3180         ret = query->status;
3181         macdrv_release_query(query);
3183         return ret;
3184     }
3186     - (BOOL) wantsPeriodicDraggingUpdates
3187     {
3188         return NO;
3189     }
3191 @end
3194 /***********************************************************************
3195  *              macdrv_create_cocoa_window
3197  * Create a Cocoa window with the given content frame and features (e.g.
3198  * title bar, close box, etc.).
3199  */
3200 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
3201         CGRect frame, void* hwnd, macdrv_event_queue queue)
3203     __block WineWindow* window;
3205     OnMainThread(^{
3206         window = [[WineWindow createWindowWithFeatures:wf
3207                                            windowFrame:NSRectFromCGRect(cgrect_mac_from_win(frame))
3208                                                   hwnd:hwnd
3209                                                  queue:(WineEventQueue*)queue] retain];
3210     });
3212     return (macdrv_window)window;
3215 /***********************************************************************
3216  *              macdrv_destroy_cocoa_window
3218  * Destroy a Cocoa window.
3219  */
3220 void macdrv_destroy_cocoa_window(macdrv_window w)
3222     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3223     WineWindow* window = (WineWindow*)w;
3225     OnMainThread(^{
3226         [window doOrderOut];
3227         [window close];
3228     });
3229     [window.queue discardEventsMatchingMask:-1 forWindow:window];
3230     [window release];
3232     [pool release];
3235 /***********************************************************************
3236  *              macdrv_get_window_hwnd
3238  * Get the hwnd that was set for the window at creation.
3239  */
3240 void* macdrv_get_window_hwnd(macdrv_window w)
3242     WineWindow* window = (WineWindow*)w;
3243     return window.hwnd;
3246 /***********************************************************************
3247  *              macdrv_set_cocoa_window_features
3249  * Update a Cocoa window's features.
3250  */
3251 void macdrv_set_cocoa_window_features(macdrv_window w,
3252         const struct macdrv_window_features* wf)
3254     WineWindow* window = (WineWindow*)w;
3256     OnMainThread(^{
3257         [window setWindowFeatures:wf];
3258     });
3261 /***********************************************************************
3262  *              macdrv_set_cocoa_window_state
3264  * Update a Cocoa window's state.
3265  */
3266 void macdrv_set_cocoa_window_state(macdrv_window w,
3267         const struct macdrv_window_state* state)
3269     WineWindow* window = (WineWindow*)w;
3271     OnMainThread(^{
3272         [window setMacDrvState:state];
3273     });
3276 /***********************************************************************
3277  *              macdrv_set_cocoa_window_title
3279  * Set a Cocoa window's title.
3280  */
3281 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
3282         size_t length)
3284     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3285     WineWindow* window = (WineWindow*)w;
3286     NSString* titleString;
3288     if (title)
3289         titleString = [NSString stringWithCharacters:title length:length];
3290     else
3291         titleString = @"";
3292     OnMainThreadAsync(^{
3293         [window setTitle:titleString];
3294         if ([window isOrderedIn] && ![window isExcludedFromWindowsMenu])
3295             [NSApp changeWindowsItem:window title:titleString filename:NO];
3296     });
3298     [pool release];
3301 /***********************************************************************
3302  *              macdrv_order_cocoa_window
3304  * Reorder a Cocoa window relative to other windows.  If prev is
3305  * non-NULL, it is ordered below that window.  Else, if next is non-NULL,
3306  * it is ordered above that window.  Otherwise, it is ordered to the
3307  * front.
3308  */
3309 void macdrv_order_cocoa_window(macdrv_window w, macdrv_window p,
3310         macdrv_window n, int activate)
3312     WineWindow* window = (WineWindow*)w;
3313     WineWindow* prev = (WineWindow*)p;
3314     WineWindow* next = (WineWindow*)n;
3316     OnMainThreadAsync(^{
3317         [window orderBelow:prev
3318                    orAbove:next
3319                   activate:activate];
3320     });
3321     [window.queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD)
3322                                   forWindow:window];
3323     [next.queue discardEventsMatchingMask:event_mask_for_type(WINDOW_BROUGHT_FORWARD)
3324                                 forWindow:next];
3327 /***********************************************************************
3328  *              macdrv_hide_cocoa_window
3330  * Hides a Cocoa window.
3331  */
3332 void macdrv_hide_cocoa_window(macdrv_window w)
3334     WineWindow* window = (WineWindow*)w;
3336     OnMainThread(^{
3337         [window doOrderOut];
3338     });
3341 /***********************************************************************
3342  *              macdrv_set_cocoa_window_frame
3344  * Move a Cocoa window.
3345  */
3346 void macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
3348     WineWindow* window = (WineWindow*)w;
3350     OnMainThread(^{
3351         [window setFrameFromWine:NSRectFromCGRect(cgrect_mac_from_win(*new_frame))];
3352     });
3355 /***********************************************************************
3356  *              macdrv_get_cocoa_window_frame
3358  * Gets the frame of a Cocoa window.
3359  */
3360 void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
3362     WineWindow* window = (WineWindow*)w;
3364     OnMainThread(^{
3365         NSRect frame;
3367         frame = [window contentRectForFrameRect:[window wine_fractionalFrame]];
3368         [[WineApplicationController sharedController] flipRect:&frame];
3369         *out_frame = cgrect_win_from_mac(NSRectToCGRect(frame));
3370     });
3373 /***********************************************************************
3374  *              macdrv_set_cocoa_parent_window
3376  * Sets the parent window for a Cocoa window.  If parent is NULL, clears
3377  * the parent window.
3378  */
3379 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
3381     WineWindow* window = (WineWindow*)w;
3383     OnMainThread(^{
3384         [window setMacDrvParentWindow:(WineWindow*)parent];
3385     });
3388 /***********************************************************************
3389  *              macdrv_set_window_surface
3390  */
3391 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
3393     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3394     WineWindow* window = (WineWindow*)w;
3396     OnMainThread(^{
3397         window.surface = surface;
3398         window.surface_mutex = mutex;
3399     });
3401     [pool release];
3404 /***********************************************************************
3405  *              macdrv_window_needs_display
3407  * Mark a window as needing display in a specified rect (in non-client
3408  * area coordinates).
3409  */
3410 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
3412     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3413     WineWindow* window = (WineWindow*)w;
3415     OnMainThreadAsync(^{
3416         [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(cgrect_mac_from_win(rect))];
3417     });
3419     [pool release];
3422 /***********************************************************************
3423  *              macdrv_set_window_shape
3425  * Sets the shape of a Cocoa window from an array of rectangles.  If
3426  * rects is NULL, resets the window's shape to its frame.
3427  */
3428 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
3430     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3431     WineWindow* window = (WineWindow*)w;
3433     OnMainThread(^{
3434         if (!rects || !count)
3435         {
3436             window.shape = nil;
3437             window.shapeData = nil;
3438             [window checkEmptyShaped];
3439         }
3440         else
3441         {
3442             size_t length = sizeof(*rects) * count;
3443             if (window.shapeData.length != length || memcmp(window.shapeData.bytes, rects, length))
3444             {
3445                 NSBezierPath* path;
3446                 unsigned int i;
3448                 path = [NSBezierPath bezierPath];
3449                 for (i = 0; i < count; i++)
3450                     [path appendBezierPathWithRect:NSRectFromCGRect(cgrect_mac_from_win(rects[i]))];
3451                 window.shape = path;
3452                 window.shapeData = [NSData dataWithBytes:rects length:length];
3453                 [window checkEmptyShaped];
3454             }
3455         }
3456     });
3458     [pool release];
3461 /***********************************************************************
3462  *              macdrv_set_window_alpha
3463  */
3464 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
3466     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3467     WineWindow* window = (WineWindow*)w;
3469     [window setAlphaValue:alpha];
3471     [pool release];
3474 /***********************************************************************
3475  *              macdrv_set_window_color_key
3476  */
3477 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
3478                                  CGFloat keyBlue)
3480     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3481     WineWindow* window = (WineWindow*)w;
3483     OnMainThread(^{
3484         window.colorKeyed       = TRUE;
3485         window.colorKeyRed      = keyRed;
3486         window.colorKeyGreen    = keyGreen;
3487         window.colorKeyBlue     = keyBlue;
3488         [window checkTransparency];
3489     });
3491     [pool release];
3494 /***********************************************************************
3495  *              macdrv_clear_window_color_key
3496  */
3497 void macdrv_clear_window_color_key(macdrv_window w)
3499     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3500     WineWindow* window = (WineWindow*)w;
3502     OnMainThread(^{
3503         window.colorKeyed = FALSE;
3504         [window checkTransparency];
3505     });
3507     [pool release];
3510 /***********************************************************************
3511  *              macdrv_window_use_per_pixel_alpha
3512  */
3513 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
3515     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3516     WineWindow* window = (WineWindow*)w;
3518     OnMainThread(^{
3519         window.usePerPixelAlpha = use_per_pixel_alpha;
3520         [window checkTransparency];
3521     });
3523     [pool release];
3526 /***********************************************************************
3527  *              macdrv_give_cocoa_window_focus
3529  * Makes the Cocoa window "key" (gives it keyboard focus).  This also
3530  * orders it front and, if its frame was not within the desktop bounds,
3531  * Cocoa will typically move it on-screen.
3532  */
3533 void macdrv_give_cocoa_window_focus(macdrv_window w, int activate)
3535     WineWindow* window = (WineWindow*)w;
3537     OnMainThread(^{
3538         [window makeFocused:activate];
3539     });
3542 /***********************************************************************
3543  *              macdrv_set_window_min_max_sizes
3545  * Sets the window's minimum and maximum content sizes.
3546  */
3547 void macdrv_set_window_min_max_sizes(macdrv_window w, CGSize min_size, CGSize max_size)
3549     WineWindow* window = (WineWindow*)w;
3551     OnMainThread(^{
3552         [window setWineMinSize:NSSizeFromCGSize(cgsize_mac_from_win(min_size)) maxSize:NSSizeFromCGSize(cgsize_mac_from_win(max_size))];
3553     });
3556 /***********************************************************************
3557  *              macdrv_create_view
3559  * Creates and returns a view with the specified frame rect.  The
3560  * caller is responsible for calling macdrv_dispose_view() on the view
3561  * when it is done with it.
3562  */
3563 macdrv_view macdrv_create_view(CGRect rect)
3565     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3566     __block WineContentView* view;
3568     if (CGRectIsNull(rect)) rect = CGRectZero;
3570     OnMainThread(^{
3571         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
3573         view = [[WineContentView alloc] initWithFrame:NSRectFromCGRect(cgrect_mac_from_win(rect))];
3574         [view setAutoresizesSubviews:NO];
3575         [view setAutoresizingMask:NSViewNotSizable];
3576         [view setHidden:YES];
3577         [nc addObserver:view
3578                selector:@selector(updateGLContexts)
3579                    name:NSViewGlobalFrameDidChangeNotification
3580                  object:view];
3581         [nc addObserver:view
3582                selector:@selector(updateGLContexts)
3583                    name:NSApplicationDidChangeScreenParametersNotification
3584                  object:NSApp];
3585     });
3587     [pool release];
3588     return (macdrv_view)view;
3591 /***********************************************************************
3592  *              macdrv_dispose_view
3594  * Destroys a view previously returned by macdrv_create_view.
3595  */
3596 void macdrv_dispose_view(macdrv_view v)
3598     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3599     WineContentView* view = (WineContentView*)v;
3601     OnMainThread(^{
3602         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
3603         WineWindow* window = (WineWindow*)[view window];
3605         [nc removeObserver:view
3606                       name:NSViewGlobalFrameDidChangeNotification
3607                     object:view];
3608         [nc removeObserver:view
3609                       name:NSApplicationDidChangeScreenParametersNotification
3610                     object:NSApp];
3611         [view removeFromSuperview];
3612         [view release];
3613         [window updateForGLSubviews];
3614     });
3616     [pool release];
3619 /***********************************************************************
3620  *              macdrv_set_view_frame
3621  */
3622 void macdrv_set_view_frame(macdrv_view v, CGRect rect)
3624     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3625     WineContentView* view = (WineContentView*)v;
3627     if (CGRectIsNull(rect)) rect = CGRectZero;
3629     OnMainThreadAsync(^{
3630         NSRect newFrame = NSRectFromCGRect(cgrect_mac_from_win(rect));
3631         NSRect oldFrame = [view frame];
3633         if (!NSEqualRects(oldFrame, newFrame))
3634         {
3635             [[view superview] setNeedsDisplayInRect:oldFrame];
3636             if (NSEqualPoints(oldFrame.origin, newFrame.origin))
3637                 [view setFrameSize:newFrame.size];
3638             else if (NSEqualSizes(oldFrame.size, newFrame.size))
3639                 [view setFrameOrigin:newFrame.origin];
3640             else
3641                 [view setFrame:newFrame];
3642             [view setNeedsDisplay:YES];
3644             if (retina_enabled)
3645             {
3646                 int backing_size[2] = { 0 };
3647                 [view wine_setBackingSize:backing_size];
3648             }
3649             [(WineWindow*)[view window] updateForGLSubviews];
3650         }
3651     });
3653     [pool release];
3656 /***********************************************************************
3657  *              macdrv_set_view_superview
3659  * Move a view to a new superview and position it relative to its
3660  * siblings.  If p is non-NULL, the view is ordered behind it.
3661  * Otherwise, the view is ordered above n.  If s is NULL, use the
3662  * content view of w as the new superview.
3663  */
3664 void macdrv_set_view_superview(macdrv_view v, macdrv_view s, macdrv_window w, macdrv_view p, macdrv_view n)
3666     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3667     WineContentView* view = (WineContentView*)v;
3668     WineContentView* superview = (WineContentView*)s;
3669     WineWindow* window = (WineWindow*)w;
3670     WineContentView* prev = (WineContentView*)p;
3671     WineContentView* next = (WineContentView*)n;
3673     if (!superview)
3674         superview = [window contentView];
3676     OnMainThreadAsync(^{
3677         if (superview == [view superview])
3678         {
3679             NSArray* subviews = [superview subviews];
3680             NSUInteger index = [subviews indexOfObjectIdenticalTo:view];
3681             if (!prev && !next && index == [subviews count] - 1)
3682                 return;
3683             if (prev && index + 1 < [subviews count] && [subviews objectAtIndex:index + 1] == prev)
3684                 return;
3685             if (!prev && next && index > 0 && [subviews objectAtIndex:index - 1] == next)
3686                 return;
3687         }
3689         WineWindow* oldWindow = (WineWindow*)[view window];
3690         WineWindow* newWindow = (WineWindow*)[superview window];
3692 #if !defined(MAC_OS_X_VERSION_10_10) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10
3693         if (floor(NSAppKitVersionNumber) <= 1265 /*NSAppKitVersionNumber10_9*/)
3694             [view removeFromSuperview];
3695 #endif
3696         if (prev)
3697             [superview addSubview:view positioned:NSWindowBelow relativeTo:prev];
3698         else
3699             [superview addSubview:view positioned:NSWindowAbove relativeTo:next];
3701         if (oldWindow != newWindow)
3702         {
3703             [oldWindow updateForGLSubviews];
3704             [newWindow updateForGLSubviews];
3705         }
3706     });
3708     [pool release];
3711 /***********************************************************************
3712  *              macdrv_set_view_hidden
3713  */
3714 void macdrv_set_view_hidden(macdrv_view v, int hidden)
3716     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3717     WineContentView* view = (WineContentView*)v;
3719     OnMainThreadAsync(^{
3720         [view setHidden:hidden];
3721         [(WineWindow*)view.window updateForGLSubviews];
3722     });
3724     [pool release];
3727 /***********************************************************************
3728  *              macdrv_add_view_opengl_context
3730  * Add an OpenGL context to the list being tracked for each view.
3731  */
3732 void macdrv_add_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
3734     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3735     WineContentView* view = (WineContentView*)v;
3736     WineOpenGLContext *context = (WineOpenGLContext*)c;
3738     OnMainThread(^{
3739         [view addGLContext:context];
3740     });
3742     [pool release];
3745 /***********************************************************************
3746  *              macdrv_remove_view_opengl_context
3748  * Add an OpenGL context to the list being tracked for each view.
3749  */
3750 void macdrv_remove_view_opengl_context(macdrv_view v, macdrv_opengl_context c)
3752     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3753     WineContentView* view = (WineContentView*)v;
3754     WineOpenGLContext *context = (WineOpenGLContext*)c;
3756     OnMainThreadAsync(^{
3757         [view removeGLContext:context];
3758     });
3760     [pool release];
3763 #ifdef HAVE_METAL_METAL_H
3764 macdrv_metal_device macdrv_create_metal_device(void)
3766     macdrv_metal_device ret;
3768 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11
3769     if (MTLCreateSystemDefaultDevice == NULL)
3770         return NULL;
3771 #endif
3773     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3774     ret = (macdrv_metal_device)MTLCreateSystemDefaultDevice();
3775     [pool release];
3776     return ret;
3779 void macdrv_release_metal_device(macdrv_metal_device d)
3781     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
3782     [(id<MTLDevice>)d release];
3783     [pool release];
3786 macdrv_metal_view macdrv_view_create_metal_view(macdrv_view v, macdrv_metal_device d)
3788     id<MTLDevice> device = (id<MTLDevice>)d;
3789     WineContentView* view = (WineContentView*)v;
3790     __block WineMetalView *metalView;
3792     OnMainThread(^{
3793         metalView = [view newMetalViewWithDevice:device];
3794     });
3796     return (macdrv_metal_view)metalView;
3799 macdrv_metal_layer macdrv_view_get_metal_layer(macdrv_metal_view v)
3801     WineMetalView* view = (WineMetalView*)v;
3802     __block CAMetalLayer* layer;
3804     OnMainThread(^{
3805         layer = (CAMetalLayer*)view.layer;
3806     });
3808     return (macdrv_metal_layer)layer;
3811 void macdrv_view_release_metal_view(macdrv_metal_view v)
3813     WineMetalView* view = (WineMetalView*)v;
3814     OnMainThread(^{
3815         [view removeFromSuperview];
3816         [view release];
3817     });
3819 #endif
3821 int macdrv_get_view_backing_size(macdrv_view v, int backing_size[2])
3823     WineContentView* view = (WineContentView*)v;
3825     if (![view isKindOfClass:[WineContentView class]])
3826         return FALSE;
3828     [view wine_getBackingSize:backing_size];
3829     return TRUE;
3832 void macdrv_set_view_backing_size(macdrv_view v, const int backing_size[2])
3834     WineContentView* view = (WineContentView*)v;
3836     if ([view isKindOfClass:[WineContentView class]])
3837         [view wine_setBackingSize:backing_size];
3840 /***********************************************************************
3841  *              macdrv_window_background_color
3843  * Returns the standard Mac window background color as a 32-bit value of
3844  * the form 0x00rrggbb.
3845  */
3846 uint32_t macdrv_window_background_color(void)
3848     static uint32_t result;
3849     static dispatch_once_t once;
3851     // Annoyingly, [NSColor windowBackgroundColor] refuses to convert to other
3852     // color spaces (RGB or grayscale).  So, the only way to get RGB values out
3853     // of it is to draw with it.
3854     dispatch_once(&once, ^{
3855         OnMainThread(^{
3856             unsigned char rgbx[4];
3857             unsigned char *planes = rgbx;
3858             NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&planes
3859                                                                                pixelsWide:1
3860                                                                                pixelsHigh:1
3861                                                                             bitsPerSample:8
3862                                                                           samplesPerPixel:3
3863                                                                                  hasAlpha:NO
3864                                                                                  isPlanar:NO
3865                                                                            colorSpaceName:NSCalibratedRGBColorSpace
3866                                                                              bitmapFormat:0
3867                                                                               bytesPerRow:4
3868                                                                              bitsPerPixel:32];
3869             [NSGraphicsContext saveGraphicsState];
3870             [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
3871             [[NSColor windowBackgroundColor] set];
3872             NSRectFill(NSMakeRect(0, 0, 1, 1));
3873             [NSGraphicsContext restoreGraphicsState];
3874             [bitmap release];
3875             result = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
3876         });
3877     });
3879     return result;
3882 /***********************************************************************
3883  *              macdrv_send_text_input_event
3884  */
3885 void macdrv_send_text_input_event(int pressed, unsigned int flags, int repeat, int keyc, void* data, int* done)
3887     OnMainThreadAsync(^{
3888         BOOL ret;
3889         macdrv_event* event;
3890         WineWindow* window = (WineWindow*)[NSApp keyWindow];
3891         if (![window isKindOfClass:[WineWindow class]])
3892         {
3893             window = (WineWindow*)[NSApp mainWindow];
3894             if (![window isKindOfClass:[WineWindow class]])
3895                 window = [[WineApplicationController sharedController] frontWineWindow];
3896         }
3898         if (window)
3899         {
3900             NSUInteger localFlags = flags;
3901             CGEventRef c;
3902             NSEvent* event;
3904             window.imeData = data;
3905             fix_device_modifiers_by_generic(&localFlags);
3907             // An NSEvent created with +keyEventWithType:... is internally marked
3908             // as synthetic and doesn't get sent through input methods.  But one
3909             // created from a CGEvent doesn't have that problem.
3910             c = CGEventCreateKeyboardEvent(NULL, keyc, pressed);
3911             CGEventSetFlags(c, localFlags);
3912             CGEventSetIntegerValueField(c, kCGKeyboardEventAutorepeat, repeat);
3913             event = [NSEvent eventWithCGEvent:c];
3914             CFRelease(c);
3916             window.commandDone = FALSE;
3917             ret = [[[window contentView] inputContext] handleEvent:event] && !window.commandDone;
3918         }
3919         else
3920             ret = FALSE;
3922         event = macdrv_create_event(SENT_TEXT_INPUT, window);
3923         event->sent_text_input.handled = ret;
3924         event->sent_text_input.done = done;
3925         [[window queue] postEvent:event];
3926         macdrv_release_event(event);
3927     });
3930 void macdrv_clear_ime_text(void)
3932     OnMainThreadAsync(^{
3933         WineWindow* window = (WineWindow*)[NSApp keyWindow];
3934         if (![window isKindOfClass:[WineWindow class]])
3935         {
3936             window = (WineWindow*)[NSApp mainWindow];
3937             if (![window isKindOfClass:[WineWindow class]])
3938                 window = [[WineApplicationController sharedController] frontWineWindow];
3939         }
3940         if (window)
3941             [[window contentView] clearMarkedText];
3942     });