winewayland.drv: Implement vkGetPhysicalDeviceSurfaceSupportKHR.
[wine.git] / dlls / winemac.drv / cocoa_app.m
blob4e26bf1e122d7bc9108613f80e45a01c844b96f8
1 /*
2  * MACDRV Cocoa application class
3  *
4  * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
21 #import <Carbon/Carbon.h>
23 #import "cocoa_app.h"
24 #import "cocoa_cursorclipping.h"
25 #import "cocoa_event.h"
26 #import "cocoa_window.h"
28 #pragma GCC diagnostic ignored "-Wdeclaration-after-statement"
31 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
33 // Private notifications that are reliably dispatched when a window is moved by dragging its titlebar.
34 // The object of the notification is the window being dragged.
35 // Available in macOS 10.12+
36 static NSString* const NSWindowWillStartDraggingNotification = @"NSWindowWillStartDraggingNotification";
37 static NSString* const NSWindowDidEndDraggingNotification = @"NSWindowDidEndDraggingNotification";
39 // Internal distributed notification to handle cooperative app activation in Sonoma.
40 static NSString* const WineAppWillActivateNotification = @"WineAppWillActivateNotification";
41 static NSString* const WineActivatingAppPIDKey = @"ActivatingAppPID";
42 static NSString* const WineActivatingAppPrefixKey = @"ActivatingAppPrefix";
43 static NSString* const WineActivatingAppConfigDirKey = @"ActivatingAppConfigDir";
46 int macdrv_err_on;
49 #if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
50 @interface NSWindow (WineAutoTabbingExtensions)
52     + (void) setAllowsAutomaticWindowTabbing:(BOOL)allows;
54 @end
55 #endif
58 #if !defined(MAC_OS_VERSION_14_0) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_14_0
59 @interface NSApplication (CooperativeActivationSelectorsForOldSDKs)
61     - (void)activate;
62     - (void)yieldActivationToApplication:(NSRunningApplication *)application;
63     - (void)yieldActivationToApplicationWithBundleIdentifier:(NSString *)bundleIdentifier;
65 @end
67 @interface NSRunningApplication (CooperativeActivationSelectorsForOldSDKs)
69     - (BOOL)activateFromApplication:(NSRunningApplication *)application
70                             options:(NSApplicationActivationOptions)options;
72 @end
73 #endif
76 /***********************************************************************
77  *              WineLocalizedString
78  *
79  * Look up a localized string by its ID in the dictionary.
80  */
81 static NSString* WineLocalizedString(unsigned int stringID)
83     return ((NSDictionary*)localized_strings)[@(stringID)];
87 @implementation WineApplication
89 @synthesize wineController;
91     - (void) sendEvent:(NSEvent*)anEvent
92     {
93         if (![wineController handleEvent:anEvent])
94         {
95             [super sendEvent:anEvent];
96             [wineController didSendEvent:anEvent];
97         }
98     }
100     - (void) setWineController:(WineApplicationController*)newController
101     {
102         wineController = newController;
103         [self setDelegate:wineController];
104     }
106 @end
109 @interface WineApplicationController ()
111 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
112 @property (copy, nonatomic) NSArray* cursorFrames;
113 @property (retain, nonatomic) NSTimer* cursorTimer;
114 @property (retain, nonatomic) NSCursor* cursor;
115 @property (retain, nonatomic) NSImage* applicationIcon;
116 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
117 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
119     - (void) setupObservations;
120     - (void) applicationDidBecomeActive:(NSNotification *)notification;
122     static void PerformRequest(void *info);
124 @end
127 @implementation WineApplicationController
129     @synthesize keyboardType, lastFlagsChanged;
130     @synthesize applicationIcon;
131     @synthesize cursorFrames, cursorTimer, cursor;
132     @synthesize mouseCaptureWindow;
133     @synthesize lastSetCursorPositionTime;
135     + (void) initialize
136     {
137         if (self == [WineApplicationController class])
138         {
139             NSDictionary<NSString *, id> *defaults =
140             @{
141                 @"NSQuotedKeystrokeBinding" : @"",
142                     @"NSRepeatCountBinding" : @"",
143                 @"ApplePressAndHoldEnabled" : @NO
144             };
146             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
148             if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
149                 [NSWindow setAllowsAutomaticWindowTabbing:NO];
150         }
151     }
153     + (WineApplicationController*) sharedController
154     {
155         static WineApplicationController* sharedController;
156         static dispatch_once_t once;
158         dispatch_once(&once, ^{
159             sharedController = [[self alloc] init];
160         });
162         return sharedController;
163     }
165     - (id) init
166     {
167         self = [super init];
168         if (self != nil)
169         {
170             CFRunLoopSourceContext context = { 0 };
171             context.perform = PerformRequest;
172             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
173             if (!requestSource)
174             {
175                 [self release];
176                 return nil;
177             }
178             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
179             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
181             requests =  [[NSMutableArray alloc] init];
182             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
184             eventQueues = [[NSMutableArray alloc] init];
185             eventQueuesLock = [[NSLock alloc] init];
187             keyWindows = [[NSMutableArray alloc] init];
189             originalDisplayModes = [[NSMutableDictionary alloc] init];
190             latentDisplayModes = [[NSMutableDictionary alloc] init];
192             windowsBeingDragged = [[NSMutableSet alloc] init];
194             // On macOS 10.12+, use notifications to more reliably detect when windows are being dragged.
195             if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)])
196             {
197                 NSOperatingSystemVersion requiredVersion = { 10, 12, 0 };
198                 useDragNotifications = [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:requiredVersion];
199             }
200             else
201                 useDragNotifications = NO;
203             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
204                 !keyWindows || !originalDisplayModes || !latentDisplayModes)
205             {
206                 [self release];
207                 return nil;
208             }
210             [self setupObservations];
212             keyboardType = LMGetKbdType();
214             if ([NSApp isActive])
215                 [self applicationDidBecomeActive:nil];
216         }
217         return self;
218     }
220     - (void) dealloc
221     {
222         [windowsBeingDragged release];
223         [cursor release];
224         [screenFrameCGRects release];
225         [applicationIcon release];
226         [clipCursorHandler release];
227         [cursorTimer release];
228         [cursorFrames release];
229         [latentDisplayModes release];
230         [originalDisplayModes release];
231         [keyWindows release];
232         [eventQueues release];
233         [eventQueuesLock release];
234         if (requestsManipQueue) dispatch_release(requestsManipQueue);
235         [requests release];
236         if (requestSource)
237         {
238             CFRunLoopSourceInvalidate(requestSource);
239             CFRelease(requestSource);
240         }
241         [super dealloc];
242     }
244     - (void) transformProcessToForeground:(BOOL)activateIfTransformed
245     {
246         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
247         {
248             NSMenu* mainMenu;
249             NSMenu* submenu;
250             NSString* bundleName;
251             NSString* title;
252             NSMenuItem* item;
254             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
256             if (activateIfTransformed)
257                 [self tryToActivateIgnoringOtherApps:YES];
259 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
260             if (!enable_app_nap && [NSProcessInfo instancesRespondToSelector:@selector(beginActivityWithOptions:reason:)])
261             {
262                 [[[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep
263                                                                 reason:@"Running Windows program"] retain]; // intentional leak
264             }
265 #endif
267             mainMenu = [[[NSMenu alloc] init] autorelease];
269             // Application menu
270             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
271             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
273             if ([bundleName length])
274                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
275             else
276                 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
277             item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
279             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
280                                       action:@selector(hideOtherApplications:)
281                                keyEquivalent:@"h"];
282             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
284             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
285                                       action:@selector(unhideAllApplications:)
286                                keyEquivalent:@""];
288             [submenu addItem:[NSMenuItem separatorItem]];
290             if ([bundleName length])
291                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
292             else
293                 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
294             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
295             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
296             item = [[[NSMenuItem alloc] init] autorelease];
297             [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
298             [item setSubmenu:submenu];
299             [mainMenu addItem:item];
301             // Window menu
302             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
303             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
304                                action:@selector(performMiniaturize:)
305                         keyEquivalent:@""];
306             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
307                                action:@selector(performZoom:)
308                         keyEquivalent:@""];
309             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
310                                       action:@selector(toggleFullScreen:)
311                                keyEquivalent:@"f"];
312             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand |
313                                                NSEventModifierFlagOption |
314                                                NSEventModifierFlagControl];
315             [submenu addItem:[NSMenuItem separatorItem]];
316             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
317                                action:@selector(arrangeInFront:)
318                         keyEquivalent:@""];
319             item = [[[NSMenuItem alloc] init] autorelease];
320             [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
321             [item setSubmenu:submenu];
322             [mainMenu addItem:item];
324             [NSApp setMainMenu:mainMenu];
325             [NSApp setWindowsMenu:submenu];
327             [NSApp setApplicationIconImage:self.applicationIcon];
328         }
329     }
331     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
332     {
333         PerformRequest(NULL);
335         do
336         {
337             if (processEvents)
338             {
339                 @autoreleasepool
340                 {
341                     NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
342                                                         untilDate:timeout
343                                                            inMode:NSDefaultRunLoopMode
344                                                           dequeue:YES];
345                     if (event)
346                         [NSApp sendEvent:event];
347                 }
348             }
349             else
350                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
351         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
353         return *done;
354     }
356     - (BOOL) registerEventQueue:(WineEventQueue*)queue
357     {
358         [eventQueuesLock lock];
359         [eventQueues addObject:queue];
360         [eventQueuesLock unlock];
361         return TRUE;
362     }
364     - (void) unregisterEventQueue:(WineEventQueue*)queue
365     {
366         [eventQueuesLock lock];
367         [eventQueues removeObjectIdenticalTo:queue];
368         [eventQueuesLock unlock];
369     }
371     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
372     {
373         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
374     }
376     - (double) ticksForEventTime:(NSTimeInterval)eventTime
377     {
378         return (eventTime + eventTimeAdjustment) * 1000;
379     }
381     /* Invalidate old focus offers across all queues. */
382     - (void) invalidateGotFocusEvents
383     {
384         WineEventQueue* queue;
386         windowFocusSerial++;
388         [eventQueuesLock lock];
389         for (queue in eventQueues)
390         {
391             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
392                                    forWindow:nil];
393         }
394         [eventQueuesLock unlock];
395     }
397     - (void) windowGotFocus:(WineWindow*)window
398     {
399         macdrv_event* event;
401         [self invalidateGotFocusEvents];
403         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
404         event->window_got_focus.serial = windowFocusSerial;
405         if (triedWindows)
406             event->window_got_focus.tried_windows = [triedWindows retain];
407         else
408             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
409         [window.queue postEvent:event];
410         macdrv_release_event(event);
411     }
413     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
414     {
415         if (event->window_got_focus.serial == windowFocusSerial)
416         {
417             NSMutableArray* windows = [keyWindows mutableCopy];
418             NSNumber* windowNumber;
419             WineWindow* window;
421             for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
422             {
423                 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
424                 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
425                     ![windows containsObject:window])
426                     [windows addObject:window];
427             }
429             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
430             [triedWindows addObject:(WineWindow*)event->window];
431             for (window in windows)
432             {
433                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
434                 {
435                     [window makeKeyWindow];
436                     break;
437                 }
438             }
439             triedWindows = nil;
440             [windows release];
441         }
442     }
444     static BOOL EqualInputSource(TISInputSourceRef source1, TISInputSourceRef source2)
445     {
446         if (!source1 && !source2)
447             return TRUE;
448         if (!source1 || !source2)
449             return FALSE;
450         return CFEqual(source1, source2);
451     }
453     - (void) keyboardSelectionDidChange:(BOOL)force
454     {
455         TISInputSourceRef inputSource, inputSourceLayout;
457         if (!force)
458         {
459             NSTextInputContext* context = [NSTextInputContext currentInputContext];
460             if (!context || ![context client])
461                 return;
462         }
464         inputSource = TISCopyCurrentKeyboardInputSource();
465         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
466         if (!force && EqualInputSource(inputSource, lastKeyboardInputSource) &&
467             EqualInputSource(inputSourceLayout, lastKeyboardLayoutInputSource))
468         {
469             if (inputSource) CFRelease(inputSource);
470             if (inputSourceLayout) CFRelease(inputSourceLayout);
471             return;
472         }
474         if (lastKeyboardInputSource)
475             CFRelease(lastKeyboardInputSource);
476         lastKeyboardInputSource = inputSource;
477         if (lastKeyboardLayoutInputSource)
478             CFRelease(lastKeyboardLayoutInputSource);
479         lastKeyboardLayoutInputSource = inputSourceLayout;
481         inputSourceIsInputMethodValid = FALSE;
483         if (inputSourceLayout)
484         {
485             CFDataRef uchr;
486             uchr = TISGetInputSourceProperty(inputSourceLayout,
487                     kTISPropertyUnicodeKeyLayoutData);
488             if (uchr)
489             {
490                 macdrv_event* event;
491                 WineEventQueue* queue;
493                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
494                 event->keyboard_changed.keyboard_type = self.keyboardType;
495                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
496                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
497                 event->keyboard_changed.input_source = (TISInputSourceRef)CFRetain(inputSource);
499                 if (event->keyboard_changed.uchr)
500                 {
501                     [eventQueuesLock lock];
503                     for (queue in eventQueues)
504                         [queue postEvent:event];
506                     [eventQueuesLock unlock];
507                 }
509                 macdrv_release_event(event);
510             }
511         }
512     }
514     - (void) keyboardSelectionDidChange
515     {
516         [self keyboardSelectionDidChange:NO];
517     }
519     - (void) setKeyboardType:(CGEventSourceKeyboardType)newType
520     {
521         if (newType != keyboardType)
522         {
523             keyboardType = newType;
524             [self keyboardSelectionDidChange:YES];
525         }
526     }
528     - (void) enabledKeyboardInputSourcesChanged
529     {
530         macdrv_layout_list_needs_update = TRUE;
531     }
533     - (CGFloat) primaryScreenHeight
534     {
535         if (!primaryScreenHeightValid)
536         {
537             NSArray* screens = [NSScreen screens];
538             NSUInteger count = [screens count];
539             if (count)
540             {
541                 NSUInteger size;
542                 CGRect* rect;
543                 NSScreen* screen;
545                 primaryScreenHeight = NSHeight([screens[0] frame]);
546                 primaryScreenHeightValid = TRUE;
548                 size = count * sizeof(CGRect);
549                 if (!screenFrameCGRects)
550                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
551                 else
552                     [screenFrameCGRects setLength:size];
554                 rect = [screenFrameCGRects mutableBytes];
555                 for (screen in screens)
556                 {
557                     CGRect temp = NSRectToCGRect([screen frame]);
558                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
559                     *rect++ = temp;
560                 }
561             }
562             else
563                 return 1280; /* arbitrary value */
564         }
566         return primaryScreenHeight;
567     }
569     - (NSPoint) flippedMouseLocation:(NSPoint)point
570     {
571         /* This relies on the fact that Cocoa's mouse location points are
572            actually off by one (precisely because they were flipped from
573            Quartz screen coordinates using this same technique). */
574         point.y = [self primaryScreenHeight] - point.y;
575         return point;
576     }
578     - (void) flipRect:(NSRect*)rect
579     {
580         // We don't use -primaryScreenHeight here so there's no chance of having
581         // out-of-date cached info.  This method is called infrequently enough
582         // that getting the screen height each time is not prohibitively expensive.
583         rect->origin.y = NSMaxY([[NSScreen screens][0] frame]) - NSMaxY(*rect);
584     }
586     - (WineWindow*) frontWineWindow
587     {
588         NSNumber* windowNumber;
589         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
590         {
591             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
592             if ([window isKindOfClass:[WineWindow class]] && [window screen])
593                 return (WineWindow*)window;
594         }
596         return nil;
597     }
599     - (void) adjustWindowLevels:(BOOL)active
600     {
601         NSArray* windowNumbers;
602         NSMutableArray* wineWindows;
603         NSNumber* windowNumber;
604         NSUInteger nextFloatingIndex = 0;
605         __block NSInteger maxLevel = NSIntegerMin;
606         __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
607         __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
608         __block WineWindow* prev = nil;
609         WineWindow* window;
611         if ([NSApp isHidden]) return;
613         windowNumbers = [NSWindow windowNumbersWithOptions:0];
614         wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
616         // For the most part, we rely on the window server's ordering of the windows
617         // to be authoritative.  The one exception is if the "floating" property of
618         // one of the windows has been changed, it may be in the wrong level and thus
619         // in the order.  This method is what's supposed to fix that up.  So build
620         // a list of Wine windows sorted first by floating-ness and then by order
621         // as indicated by the window server.
622         for (windowNumber in windowNumbers)
623         {
624             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
625             if ([window isKindOfClass:[WineWindow class]])
626             {
627                 if (window.floating)
628                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
629                 else
630                     [wineWindows addObject:window];
631             }
632         }
634         NSDisableScreenUpdates();
636         // Go from back to front so that all windows in front of one which is
637         // elevated for full-screen are also elevated.
638         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
639                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
640             WineWindow* window = (WineWindow*)obj;
641             NSInteger origLevel = [window level];
642             NSInteger newLevel = [window minimumLevelForActive:active];
644             if (window.floating)
645             {
646                 if (minFloatingLevel <= maxNonfloatingLevel)
647                     minFloatingLevel = maxNonfloatingLevel + 1;
648                 if (newLevel < minFloatingLevel)
649                     newLevel = minFloatingLevel;
650             }
652             if (newLevel < maxLevel)
653                 newLevel = maxLevel;
654             else
655                 maxLevel = newLevel;
657             if (!window.floating && maxNonfloatingLevel < newLevel)
658                 maxNonfloatingLevel = newLevel;
660             if (newLevel != origLevel)
661             {
662                 [window setLevel:newLevel];
664                 if (origLevel < newLevel)
665                 {
666                     // If we increased the level, the window should be toward the
667                     // back of its new level (but still ahead of the previous
668                     // windows we did this to).
669                     if (prev)
670                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
671                     else
672                         [window orderBack:nil];
673                 }
674                 else
675                 {
676                     // If we decreased the level, we want the window at the top
677                     // of its new level. -setLevel: is documented to do that on
678                     // its own, but that's buggy on Ventura. Since we're looping
679                     // back-to-front here, -orderFront: will do the right thing.
680                     [window orderFront:nil];
681                 }
682             }
684             prev = window;
685         }];
687         NSEnableScreenUpdates();
689         [wineWindows release];
691         // The above took care of the visible windows on the current space.  That
692         // leaves windows on other spaces, minimized windows, and windows which
693         // are not ordered in.  We want to leave windows on other spaces alone
694         // so the space remains just as they left it (when viewed in Exposé or
695         // Mission Control, for example).  We'll adjust the window levels again
696         // after we switch to another space, anyway.  Windows which aren't
697         // ordered in will be handled when we order them in.  Minimized windows
698         // on the current space should be set to the level they would have gotten
699         // if they were at the front of the windows with the same floating-ness,
700         // because that's where they'll go if/when they are unminimized.  Again,
701         // for good measure we'll adjust window levels again when a window is
702         // unminimized, too.
703         for (window in [NSApp windows])
704         {
705             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
706                 [window isOnActiveSpace])
707             {
708                 NSInteger origLevel = [window level];
709                 NSInteger newLevel = [window minimumLevelForActive:YES];
710                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
712                 if (newLevel < maxLevelForType)
713                     newLevel = maxLevelForType;
715                 if (newLevel != origLevel)
716                     [window setLevel:newLevel];
717             }
718         }
719     }
721     - (void) adjustWindowLevels
722     {
723         [self adjustWindowLevels:[NSApp isActive]];
724     }
726     - (void) updateFullscreenWindows
727     {
728         if (capture_displays_for_fullscreen && [NSApp isActive])
729         {
730             BOOL anyFullscreen = FALSE;
731             NSNumber* windowNumber;
732             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
733             {
734                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
735                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
736                 {
737                     anyFullscreen = TRUE;
738                     break;
739                 }
740             }
742             if (anyFullscreen)
743             {
744                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
745                     displaysCapturedForFullscreen = TRUE;
746             }
747             else if (displaysCapturedForFullscreen)
748             {
749                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
750                     displaysCapturedForFullscreen = FALSE;
751             }
752         }
753     }
755     - (void) activeSpaceDidChange
756     {
757         [self updateFullscreenWindows];
758         [self adjustWindowLevels];
759     }
761     - (void) sendDisplaysChanged:(BOOL)activating
762     {
763         macdrv_event* event;
764         WineEventQueue* queue;
766         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
767         event->displays_changed.activating = activating;
769         [eventQueuesLock lock];
771         // If we're activating, then we just need one of our threads to get the
772         // event, so it can send it directly to the desktop window.  Otherwise,
773         // we need all of the threads to get it because we don't know which owns
774         // the desktop window and only that one will do anything with it.
775         if (activating) event->deliver = 1;
777         for (queue in eventQueues)
778             [queue postEvent:event];
779         [eventQueuesLock unlock];
781         macdrv_release_event(event);
782     }
784     // We can compare two modes directly using CFEqual, but that may require that
785     // they are identical to a level that we don't need.  In particular, when the
786     // OS switches between the integrated and discrete GPUs, the set of display
787     // modes can change in subtle ways.  We're interested in whether two modes
788     // match in their most salient features, even if they aren't identical.
789     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
790     {
791         NSString *encoding1, *encoding2;
792         uint32_t ioflags1, ioflags2, different;
793         double refresh1, refresh2;
795         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
796         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
797         if (CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
798         if (CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
800         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
801         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
802         if (![encoding1 isEqualToString:encoding2]) return FALSE;
804         ioflags1 = CGDisplayModeGetIOFlags(mode1);
805         ioflags2 = CGDisplayModeGetIOFlags(mode2);
806         different = ioflags1 ^ ioflags2;
807         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
808                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
809             return FALSE;
811         refresh1 = CGDisplayModeGetRefreshRate(mode1);
812         if (refresh1 == 0) refresh1 = 60;
813         refresh2 = CGDisplayModeGetRefreshRate(mode2);
814         if (refresh2 == 0) refresh2 = 60;
815         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
817         return TRUE;
818     }
820     - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
821     {
822         NSMutableArray* ret = [NSMutableArray array];
823         NSDictionary* options = @{ (NSString*)kCGDisplayShowDuplicateLowResolutionModes: @YES };
825         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
826         for (id candidateModeObject in modes)
827         {
828             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
829             if ([self mode:candidateMode matchesMode:mode])
830                 [ret addObject:candidateModeObject];
831         }
832         return ret;
833     }
835     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
836     {
837         BOOL ret = FALSE;
838         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
839         CGDisplayModeRef originalMode;
841         originalMode = (CGDisplayModeRef)originalDisplayModes[displayIDKey];
843         if (originalMode && [self mode:mode matchesMode:originalMode])
844         {
845             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
846             {
847                 CGRestorePermanentDisplayConfiguration();
848                 if (!displaysCapturedForFullscreen)
849                     CGReleaseAllDisplays();
850                 [originalDisplayModes removeAllObjects];
851                 ret = TRUE;
852             }
853             else // ... otherwise, try to restore just the one display
854             {
855                 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
856                 {
857                     mode = (CGDisplayModeRef)modeObject;
858                     if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
859                     {
860                         [originalDisplayModes removeObjectForKey:displayIDKey];
861                         ret = TRUE;
862                         break;
863                     }
864                 }
865             }
866         }
867         else
868         {
869             CGDisplayModeRef currentMode;
870             NSArray* modes;
872             currentMode = CGDisplayModeRetain((CGDisplayModeRef)latentDisplayModes[displayIDKey]);
873             if (!currentMode)
874                 currentMode = CGDisplayCopyDisplayMode(displayID);
875             if (!currentMode) // Invalid display ID
876                 return FALSE;
878             if ([self mode:mode matchesMode:currentMode]) // Already there!
879             {
880                 CGDisplayModeRelease(currentMode);
881                 return TRUE;
882             }
884             CGDisplayModeRelease(currentMode);
885             currentMode = NULL;
887             modes = [self modesMatchingMode:mode forDisplay:displayID];
888             if (!modes.count)
889                 return FALSE;
891             [self transformProcessToForeground:YES];
893             BOOL active = [NSApp isActive];
895             if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
896                 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
897             {
898                 if (active)
899                 {
900                     // If we get here, we have the displays captured.  If we don't
901                     // know the original mode of the display, the current mode must
902                     // be the original.  We should re-query the current mode since
903                     // another process could have changed it between when we last
904                     // checked and when we captured the displays.
905                     if (!originalMode)
906                         originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
908                     if (originalMode)
909                     {
910                         for (id modeObject in modes)
911                         {
912                             mode = (CGDisplayModeRef)modeObject;
913                             if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
914                             {
915                                 ret = TRUE;
916                                 break;
917                             }
918                         }
919                     }
920                     if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
921                         [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
922                     else if (![originalDisplayModes count])
923                     {
924                         CGRestorePermanentDisplayConfiguration();
925                         if (!displaysCapturedForFullscreen)
926                             CGReleaseAllDisplays();
927                     }
929                     if (currentMode)
930                         CGDisplayModeRelease(currentMode);
931                 }
932                 else
933                 {
934                     [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
935                     ret = TRUE;
936                 }
937             }
938         }
940         if (ret)
941             [self adjustWindowLevels];
943         return ret;
944     }
946     - (BOOL) areDisplaysCaptured
947     {
948         return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
949     }
951     - (void) updateCursor:(BOOL)force
952     {
953         if (force || lastTargetWindow)
954         {
955             if (clientWantsCursorHidden && !cursorHidden)
956             {
957                 [NSCursor hide];
958                 cursorHidden = TRUE;
959             }
961             if (!cursorIsCurrent)
962             {
963                 [cursor set];
964                 cursorIsCurrent = TRUE;
965             }
967             if (!clientWantsCursorHidden && cursorHidden)
968             {
969                 [NSCursor unhide];
970                 cursorHidden = FALSE;
971             }
972         }
973         else
974         {
975             if (cursorIsCurrent)
976             {
977                 [[NSCursor arrowCursor] set];
978                 cursorIsCurrent = FALSE;
979             }
980             if (cursorHidden)
981             {
982                 [NSCursor unhide];
983                 cursorHidden = FALSE;
984             }
985         }
986     }
988     - (void) hideCursor
989     {
990         if (!clientWantsCursorHidden)
991         {
992             clientWantsCursorHidden = TRUE;
993             [self updateCursor:TRUE];
994         }
995     }
997     - (void) unhideCursor
998     {
999         if (clientWantsCursorHidden)
1000         {
1001             clientWantsCursorHidden = FALSE;
1002             [self updateCursor:FALSE];
1003         }
1004     }
1006     - (void) setCursor:(NSCursor*)newCursor
1007     {
1008         if (newCursor != cursor)
1009         {
1010             [cursor release];
1011             cursor = [newCursor retain];
1012             cursorIsCurrent = FALSE;
1013             [self updateCursor:FALSE];
1014         }
1015     }
1017     - (void) setCursor
1018     {
1019         NSDictionary* frame = cursorFrames[cursorFrame];
1020         CGImageRef cgimage = (CGImageRef)frame[@"image"];
1021         CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
1022         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
1023         CFDictionaryRef hotSpotDict = (CFDictionaryRef)frame[@"hotSpot"];
1024         CGPoint hotSpot;
1026         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
1027             hotSpot = CGPointZero;
1028         hotSpot = cgpoint_mac_from_win(hotSpot);
1029         self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1030         [image release];
1031         [self unhideCursor];
1032     }
1034     - (void) nextCursorFrame:(NSTimer*)theTimer
1035     {
1036         NSDictionary* frame;
1037         NSTimeInterval duration;
1038         NSDate* date;
1040         cursorFrame++;
1041         if (cursorFrame >= [cursorFrames count])
1042             cursorFrame = 0;
1043         [self setCursor];
1045         frame = cursorFrames[cursorFrame];
1046         duration = [frame[@"duration"] doubleValue];
1047         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1048         [cursorTimer setFireDate:date];
1049     }
1051     - (void) setCursorWithFrames:(NSArray*)frames
1052     {
1053         if (self.cursorFrames == frames || [self.cursorFrames isEqualToArray:frames])
1054             return;
1056         self.cursorFrames = frames;
1057         cursorFrame = 0;
1058         [cursorTimer invalidate];
1059         self.cursorTimer = nil;
1061         if ([frames count])
1062         {
1063             if ([frames count] > 1)
1064             {
1065                 NSDictionary* frame = frames[0];
1066                 NSTimeInterval duration = [frame[@"duration"] doubleValue];
1067                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1068                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1069                                                              interval:1000000
1070                                                                target:self
1071                                                              selector:@selector(nextCursorFrame:)
1072                                                              userInfo:nil
1073                                                               repeats:YES] autorelease];
1074                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1075             }
1077             [self setCursor];
1078         }
1079     }
1081     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1082     {
1083         NSImage* nsimage = nil;
1085         if ([images count])
1086         {
1087             NSSize bestSize = NSZeroSize;
1088             id image;
1090             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1092             for (image in images)
1093             {
1094                 CGImageRef cgimage = (CGImageRef)image;
1095                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1096                 if (imageRep)
1097                 {
1098                     NSSize size = [imageRep size];
1100                     [nsimage addRepresentation:imageRep];
1101                     [imageRep release];
1103                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1104                         bestSize = size;
1105                 }
1106             }
1108             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1109                 [nsimage setSize:bestSize];
1110             else
1111                 nsimage = nil;
1112         }
1114         self.applicationIcon = nsimage;
1115     }
1117     - (void) handleCommandTab
1118     {
1119         if ([NSApp isActive])
1120         {
1121             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1122             NSRunningApplication* app;
1123             NSRunningApplication* otherValidApp = nil;
1125             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1126             {
1127                 NSNumber* displayID;
1128                 for (displayID in originalDisplayModes)
1129                 {
1130                     CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1131                     [latentDisplayModes setObject:(id)mode forKey:displayID];
1132                     CGDisplayModeRelease(mode);
1133                 }
1135                 CGRestorePermanentDisplayConfiguration();
1136                 CGReleaseAllDisplays();
1137                 [originalDisplayModes removeAllObjects];
1138                 displaysCapturedForFullscreen = FALSE;
1139             }
1141             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1142             {
1143                 if (![app isEqual:thisApp] && !app.terminated &&
1144                     app.activationPolicy == NSApplicationActivationPolicyRegular)
1145                 {
1146                     if (!app.hidden)
1147                     {
1148                         // There's another visible app.  Just hide ourselves and let
1149                         // the system activate the other app.
1150                         [NSApp hide:self];
1151                         return;
1152                     }
1154                     if (!otherValidApp)
1155                         otherValidApp = app;
1156                 }
1157             }
1159             // Didn't find a visible GUI app.  Try the Finder or, if that's not
1160             // running, the first hidden GUI app.  If even that doesn't work, we
1161             // just fail to switch and remain the active app.
1162             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1163             if (!app) app = otherValidApp;
1164             [app unhide];
1165             [app activateWithOptions:0];
1166         }
1167     }
1169     - (BOOL) setCursorPosition:(CGPoint)pos
1170     {
1171         BOOL ret;
1173         if ([windowsBeingDragged count])
1174             ret = FALSE;
1175         else if (self.clippingCursor && [clipCursorHandler respondsToSelector:@selector(setCursorPosition:)])
1176             ret = [clipCursorHandler setCursorPosition:pos];
1177         else
1178         {
1179             if (self.clippingCursor)
1180                 [clipCursorHandler clipCursorLocation:&pos];
1182             // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1183             // the mouse from the cursor position for 0.25 seconds.  This means
1184             // that mouse movement during that interval doesn't move the cursor
1185             // and events carry a constant location (the warped-to position)
1186             // even though they have delta values.  For apps which warp the
1187             // cursor frequently (like after every mouse move), this makes
1188             // cursor movement horribly laggy and jerky, as only a fraction of
1189             // mouse move events have any effect.
1190             //
1191             // On some versions of OS X, it's sufficient to forcibly reassociate
1192             // the mouse and cursor position.  On others, it's necessary to set
1193             // the local events suppression interval to 0 for the warp.  That's
1194             // deprecated, but I'm not aware of any other way.  For good
1195             // measure, we do both.
1196             CGSetLocalEventsSuppressionInterval(0);
1197             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1198             CGSetLocalEventsSuppressionInterval(0.25);
1199             if (ret)
1200             {
1201                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1203                 CGAssociateMouseAndMouseCursorPosition(true);
1204             }
1205         }
1207         if (ret)
1208         {
1209             WineEventQueue* queue;
1211             // Discard all pending mouse move events.
1212             [eventQueuesLock lock];
1213             for (queue in eventQueues)
1214             {
1215                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED_RELATIVE) |
1216                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1217                                        forWindow:nil];
1218                 [queue resetMouseEventPositions:pos];
1219             }
1220             [eventQueuesLock unlock];
1221         }
1223         return ret;
1224     }
1226     - (void) updateWindowsForCursorClipping
1227     {
1228         WineWindow* window;
1229         for (window in [NSApp windows])
1230         {
1231             if ([window isKindOfClass:[WineWindow class]])
1232                 [window updateForCursorClipping];
1233         }
1234     }
1236     - (BOOL) startClippingCursor:(CGRect)rect
1237     {
1238         if (!clipCursorHandler) {
1239             if (use_confinement_cursor_clipping && [WineConfinementClipCursorHandler isAvailable])
1240                 clipCursorHandler = [[WineConfinementClipCursorHandler alloc] init];
1241             else
1242                 clipCursorHandler = [[WineEventTapClipCursorHandler alloc] init];
1243         }
1245         if (self.clippingCursor && CGRectEqualToRect(rect, clipCursorHandler.cursorClipRect))
1246             return TRUE;
1248         if (![clipCursorHandler startClippingCursor:rect])
1249             return FALSE;
1251         [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1253         [self updateWindowsForCursorClipping];
1255         return TRUE;
1256     }
1258     - (BOOL) stopClippingCursor
1259     {
1260         if (!self.clippingCursor)
1261             return TRUE;
1263         if (![clipCursorHandler stopClippingCursor])
1264             return FALSE;
1266         lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1268         [self updateWindowsForCursorClipping];
1270         return TRUE;
1271     }
1273     - (BOOL) clippingCursor
1274     {
1275         return clipCursorHandler.clippingCursor;
1276     }
1278     - (BOOL) isKeyPressed:(uint16_t)keyCode
1279     {
1280         int bits = sizeof(pressedKeyCodes[0]) * 8;
1281         int index = keyCode / bits;
1282         uint32_t mask = 1 << (keyCode % bits);
1283         return (pressedKeyCodes[index] & mask) != 0;
1284     }
1286     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1287     {
1288         int bits = sizeof(pressedKeyCodes[0]) * 8;
1289         int index = keyCode / bits;
1290         uint32_t mask = 1 << (keyCode % bits);
1291         if (pressed)
1292             pressedKeyCodes[index] |= mask;
1293         else
1294             pressedKeyCodes[index] &= ~mask;
1295     }
1297     - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1298     {
1299         if (dragged)
1300             [windowsBeingDragged addObject:window];
1301         else
1302             [windowsBeingDragged removeObject:window];
1303     }
1305     - (void) windowWillOrderOut:(WineWindow*)window
1306     {
1307         if ([windowsBeingDragged containsObject:window])
1308         {
1309             [self window:window isBeingDragged:NO];
1311             macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1312             [window.queue postEvent:event];
1313             macdrv_release_event(event);
1314         }
1315     }
1317     - (BOOL) isAnyWineWindowVisible
1318     {
1319         for (WineWindow* w in [NSApp windows])
1320         {
1321             if ([w isKindOfClass:[WineWindow class]] && ![w isMiniaturized] && [w isVisible])
1322                 return YES;
1323         }
1325         return NO;
1326     }
1328     - (void) handleWindowDrag:(WineWindow*)window begin:(BOOL)begin
1329     {
1330         macdrv_event* event;
1331         int eventType;
1333         if (begin)
1334         {
1335             [windowsBeingDragged addObject:window];
1336             eventType = WINDOW_DRAG_BEGIN;
1337         }
1338         else
1339         {
1340             [windowsBeingDragged removeObject:window];
1341             eventType = WINDOW_DRAG_END;
1342         }
1344         event = macdrv_create_event(eventType, window);
1345         if (eventType == WINDOW_DRAG_BEGIN)
1346             event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
1347         [window.queue postEvent:event];
1348         macdrv_release_event(event);
1349     }
1351     - (void) handleMouseMove:(NSEvent*)anEvent
1352     {
1353         WineWindow* targetWindow;
1354         BOOL drag = [anEvent type] != NSEventTypeMouseMoved;
1356         if ([windowsBeingDragged count])
1357             targetWindow = nil;
1358         else if (mouseCaptureWindow)
1359             targetWindow = mouseCaptureWindow;
1360         else if (drag)
1361             targetWindow = (WineWindow*)[anEvent window];
1362         else
1363         {
1364             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1365                event indicates its window is the main window, even if the cursor is
1366                over a different window.  Find the actual WineWindow that is under the
1367                cursor and post the event as being for that window. */
1368             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1369             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1370             NSInteger windowUnderNumber;
1372             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1373                                   belowWindowWithWindowNumber:0];
1374             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1375             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1376                 targetWindow = nil;
1377         }
1379         if ([targetWindow isKindOfClass:[WineWindow class]])
1380         {
1381             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1382             macdrv_event* event;
1383             BOOL absolute;
1385             // If we recently warped the cursor (other than in our cursor-clipping
1386             // event tap), discard mouse move events until we see an event which is
1387             // later than that time.
1388             if (lastSetCursorPositionTime)
1389             {
1390                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1391                     return;
1393                 lastSetCursorPositionTime = 0;
1394                 forceNextMouseMoveAbsolute = TRUE;
1395             }
1397             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1398             {
1399                 absolute = TRUE;
1400                 forceNextMouseMoveAbsolute = FALSE;
1401             }
1402             else
1403             {
1404                 // Send absolute move events if the cursor is in the interior of
1405                 // its range.  Only send relative moves if the cursor is pinned to
1406                 // the boundaries of where it can go.  We compute the position
1407                 // that's one additional point in the direction of movement.  If
1408                 // that is outside of the clipping rect or desktop region (the
1409                 // union of the screen frames), then we figure the cursor would
1410                 // have moved outside if it could but it was pinned.
1411                 CGPoint computedPoint = point;
1412                 CGFloat deltaX = [anEvent deltaX];
1413                 CGFloat deltaY = [anEvent deltaY];
1415                 if (deltaX > 0.001)
1416                     computedPoint.x++;
1417                 else if (deltaX < -0.001)
1418                     computedPoint.x--;
1420                 if (deltaY > 0.001)
1421                     computedPoint.y++;
1422                 else if (deltaY < -0.001)
1423                     computedPoint.y--;
1425                 // Assume cursor is pinned for now
1426                 absolute = FALSE;
1427                 if (!self.clippingCursor || CGRectContainsPoint(clipCursorHandler.cursorClipRect, computedPoint))
1428                 {
1429                     const CGRect* rects;
1430                     NSUInteger count, i;
1432                     // Caches screenFrameCGRects if necessary
1433                     [self primaryScreenHeight];
1435                     rects = [screenFrameCGRects bytes];
1436                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1438                     for (i = 0; i < count; i++)
1439                     {
1440                         if (CGRectContainsPoint(rects[i], computedPoint))
1441                         {
1442                             absolute = TRUE;
1443                             break;
1444                         }
1445                     }
1446                 }
1447             }
1449             if (absolute)
1450             {
1451                 if (self.clippingCursor)
1452                     [clipCursorHandler clipCursorLocation:&point];
1453                 point = cgpoint_win_from_mac(point);
1455                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1456                 event->mouse_moved.x = floor(point.x);
1457                 event->mouse_moved.y = floor(point.y);
1459                 mouseMoveDeltaX = 0;
1460                 mouseMoveDeltaY = 0;
1461             }
1462             else
1463             {
1464                 double scale = retina_on ? 2 : 1;
1466                 /* Add event delta to accumulated delta error */
1467                 /* deltaY is already flipped */
1468                 mouseMoveDeltaX += [anEvent deltaX];
1469                 mouseMoveDeltaY += [anEvent deltaY];
1471                 event = macdrv_create_event(MOUSE_MOVED_RELATIVE, targetWindow);
1472                 event->mouse_moved.x = mouseMoveDeltaX * scale;
1473                 event->mouse_moved.y = mouseMoveDeltaY * scale;
1475                 /* Keep the remainder after integer truncation. */
1476                 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1477                 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1478             }
1480             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1481             {
1482                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1483                 event->mouse_moved.drag = drag;
1485                 [targetWindow.queue postEvent:event];
1486             }
1488             macdrv_release_event(event);
1490             lastTargetWindow = targetWindow;
1491         }
1492         else
1493             lastTargetWindow = nil;
1495         [self updateCursor:FALSE];
1496     }
1498     - (void) handleMouseButton:(NSEvent*)theEvent
1499     {
1500         WineWindow* window = (WineWindow*)[theEvent window];
1501         NSEventType type = [theEvent type];
1502         WineWindow* windowBroughtForward = nil;
1503         BOOL process = FALSE;
1505         if ([window isKindOfClass:[WineWindow class]] &&
1506             type == NSEventTypeLeftMouseDown &&
1507             ![theEvent wine_commandKeyDown])
1508         {
1509             NSWindowButton windowButton;
1511             windowBroughtForward = window;
1513             /* Any left-click on our window anyplace other than the close or
1514                minimize buttons will bring it forward. */
1515             for (windowButton = NSWindowCloseButton;
1516                  windowButton <= NSWindowMiniaturizeButton;
1517                  windowButton++)
1518             {
1519                 NSButton* button = [window standardWindowButton:windowButton];
1520                 if (button)
1521                 {
1522                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1523                     if ([button mouse:point inRect:[button bounds]])
1524                     {
1525                         windowBroughtForward = nil;
1526                         break;
1527                     }
1528                 }
1529             }
1530         }
1532         if ([windowsBeingDragged count])
1533             window = nil;
1534         else if (mouseCaptureWindow)
1535             window = mouseCaptureWindow;
1537         if ([window isKindOfClass:[WineWindow class]])
1538         {
1539             BOOL pressed = (type == NSEventTypeLeftMouseDown ||
1540                             type == NSEventTypeRightMouseDown ||
1541                             type == NSEventTypeOtherMouseDown);
1542             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1544             if (self.clippingCursor)
1545                 [clipCursorHandler clipCursorLocation:&pt];
1547             if (pressed)
1548             {
1549                 if (mouseCaptureWindow)
1550                     process = TRUE;
1551                 else
1552                 {
1553                     // Test if the click was in the window's content area.
1554                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1555                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1556                     process = NSMouseInRect(nspoint, contentRect, NO);
1557                     if (process && [window styleMask] & NSWindowStyleMaskResizable)
1558                     {
1559                         // Ignore clicks in the grow box (resize widget).
1560                         HIPoint origin = { 0, 0 };
1561                         HIThemeGrowBoxDrawInfo info = { 0 };
1562                         HIRect bounds;
1563                         OSStatus status;
1565                         info.kind = kHIThemeGrowBoxKindNormal;
1566                         info.direction = kThemeGrowRight | kThemeGrowDown;
1567                         if ([window styleMask] & NSWindowStyleMaskUtilityWindow)
1568                             info.size = kHIThemeGrowBoxSizeSmall;
1569                         else
1570                             info.size = kHIThemeGrowBoxSizeNormal;
1572                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1573                         if (status == noErr)
1574                         {
1575                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1576                                                         NSMinY(contentRect),
1577                                                         bounds.size.width,
1578                                                         bounds.size.height);
1579                             process = !NSMouseInRect(nspoint, growBox, NO);
1580                         }
1581                     }
1582                 }
1583                 if (process)
1584                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1585             }
1586             else
1587             {
1588                 NSEventType downType = type - 1;
1589                 NSUInteger downMask = NSEventMaskFromType(downType);
1590                 process = (unmatchedMouseDowns & downMask) != 0;
1591                 unmatchedMouseDowns &= ~downMask;
1592             }
1594             if (process)
1595             {
1596                 macdrv_event* event;
1598                 pt = cgpoint_win_from_mac(pt);
1600                 event = macdrv_create_event(MOUSE_BUTTON, window);
1601                 event->mouse_button.button = [theEvent buttonNumber];
1602                 event->mouse_button.pressed = pressed;
1603                 event->mouse_button.x = floor(pt.x);
1604                 event->mouse_button.y = floor(pt.y);
1605                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1607                 [window.queue postEvent:event];
1609                 macdrv_release_event(event);
1610             }
1611         }
1613         if (windowBroughtForward)
1614         {
1615             WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1616             NSInteger ancestorNumber = [ancestor windowNumber];
1617             NSInteger ancestorLevel = [ancestor level];
1619             for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1620             {
1621                 NSInteger windowNumber = [windowNumberObject integerValue];
1622                 if (windowNumber == ancestorNumber)
1623                     break;
1624                 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1625                 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1626                     [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1627                 {
1628                     [ancestor postBroughtForwardEvent];
1629                     break;
1630                 }
1631             }
1632             if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noForeground)
1633                 [self windowGotFocus:windowBroughtForward];
1634         }
1636         // Since mouse button events deliver absolute cursor position, the
1637         // accumulating delta from move events is invalidated.  Make sure
1638         // next mouse move event starts over from an absolute baseline.
1639         // Also, it's at least possible that the title bar widgets (e.g. close
1640         // button, etc.) could enter an internal event loop on a mouse down that
1641         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1642         // dragged events and, after that, any notion of the cursor position
1643         // computed from accumulating deltas would be wrong.
1644         forceNextMouseMoveAbsolute = TRUE;
1645     }
1647     - (void) handleScrollWheel:(NSEvent*)theEvent
1648     {
1649         WineWindow* window;
1651         if (mouseCaptureWindow)
1652             window = mouseCaptureWindow;
1653         else
1654             window = (WineWindow*)[theEvent window];
1656         if ([window isKindOfClass:[WineWindow class]])
1657         {
1658             CGEventRef cgevent = [theEvent CGEvent];
1659             CGPoint pt = CGEventGetLocation(cgevent);
1660             BOOL process;
1662             if (self.clippingCursor)
1663                 [clipCursorHandler clipCursorLocation:&pt];
1665             if (mouseCaptureWindow)
1666                 process = TRUE;
1667             else
1668             {
1669                 // Only process the event if it was in the window's content area.
1670                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1671                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1672                 process = NSMouseInRect(nspoint, contentRect, NO);
1673             }
1675             if (process)
1676             {
1677                 macdrv_event* event;
1678                 double x, y;
1679                 BOOL continuous = FALSE;
1681                 pt = cgpoint_win_from_mac(pt);
1683                 event = macdrv_create_event(MOUSE_SCROLL, window);
1684                 event->mouse_scroll.x = floor(pt.x);
1685                 event->mouse_scroll.y = floor(pt.y);
1686                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1688                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1689                 {
1690                     continuous = TRUE;
1692                     /* Continuous scroll wheel events come from high-precision scrolling
1693                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1694                        For these, we can get more precise data from the CGEvent API. */
1695                     /* Axis 1 is vertical, axis 2 is horizontal. */
1696                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1697                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1698                 }
1699                 else
1700                 {
1701                     double pixelsPerLine = 10;
1702                     CGEventSourceRef source;
1704                     /* The non-continuous values are in units of "lines", not pixels. */
1705                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1706                     {
1707                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1708                         CFRelease(source);
1709                     }
1711                     x = pixelsPerLine * [theEvent deltaX];
1712                     y = pixelsPerLine * [theEvent deltaY];
1713                 }
1715                 /* Mac: negative is right or down, positive is left or up.
1716                    Win32: negative is left or down, positive is right or up.
1717                    So, negate the X scroll value to translate. */
1718                 x = -x;
1720                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1721                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1722                    6 times the pixel value. */
1723                 x *= 6;
1724                 y *= 6;
1726                 if (use_precise_scrolling)
1727                 {
1728                     event->mouse_scroll.x_scroll = x;
1729                     event->mouse_scroll.y_scroll = y;
1731                     if (!continuous)
1732                     {
1733                         /* For non-continuous "clicky" wheels, if there was any motion, make
1734                            sure there was at least WHEEL_DELTA motion.  This is so, at slow
1735                            speeds where the system's acceleration curve is actually reducing the
1736                            scroll distance, the user is sure to get some action out of each click.
1737                            For example, this is important for rotating though weapons in a
1738                            first-person shooter. */
1739                         if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1740                             event->mouse_scroll.x_scroll = 120;
1741                         else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1742                             event->mouse_scroll.x_scroll = -120;
1744                         if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1745                             event->mouse_scroll.y_scroll = 120;
1746                         else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1747                             event->mouse_scroll.y_scroll = -120;
1748                     }
1749                 }
1750                 else
1751                 {
1752                     /* If it's been a while since the last scroll event or if the scrolling has
1753                        reversed direction, reset the accumulated scroll value. */
1754                     if ([theEvent timestamp] - lastScrollTime > 1)
1755                         accumScrollX = accumScrollY = 0;
1756                     else
1757                     {
1758                         /* The accumulated scroll value is in the opposite direction/sign of the last
1759                            scroll.  That's because it's the "debt" resulting from over-scrolling in
1760                            that direction.  We accumulate by adding in the scroll amount and then, if
1761                            it has the same sign as the scroll value, we subtract any whole or partial
1762                            WHEEL_DELTAs, leaving it 0 or the opposite sign.  So, the user switched
1763                            scroll direction if the accumulated debt and the new scroll value have the
1764                            same sign. */
1765                         if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1766                             accumScrollX = 0;
1767                         if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1768                             accumScrollY = 0;
1769                     }
1770                     lastScrollTime = [theEvent timestamp];
1772                     accumScrollX += x;
1773                     accumScrollY += y;
1775                     if (accumScrollX > 0 && x > 0)
1776                         event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1777                     if (accumScrollX < 0 && x < 0)
1778                         event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1779                     if (accumScrollY > 0 && y > 0)
1780                         event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1781                     if (accumScrollY < 0 && y < 0)
1782                         event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1784                     accumScrollX -= event->mouse_scroll.x_scroll;
1785                     accumScrollY -= event->mouse_scroll.y_scroll;
1786                 }
1788                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1789                     [window.queue postEvent:event];
1791                 macdrv_release_event(event);
1793                 // Since scroll wheel events deliver absolute cursor position, the
1794                 // accumulating delta from move events is invalidated.  Make sure next
1795                 // mouse move event starts over from an absolute baseline.
1796                 forceNextMouseMoveAbsolute = TRUE;
1797             }
1798         }
1799     }
1801     // Returns TRUE if the event was handled and caller should do nothing more
1802     // with it.  Returns FALSE if the caller should process it as normal and
1803     // then call -didSendEvent:.
1804     - (BOOL) handleEvent:(NSEvent*)anEvent
1805     {
1806         BOOL ret = FALSE;
1807         NSEventType type = [anEvent type];
1809         if (type == NSEventTypeFlagsChanged)
1810             self.lastFlagsChanged = anEvent;
1811         else if (type == NSEventTypeMouseMoved || type == NSEventTypeLeftMouseDragged ||
1812                  type == NSEventTypeRightMouseDragged || type == NSEventTypeOtherMouseDragged)
1813         {
1814             [self handleMouseMove:anEvent];
1815             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1816         }
1817         else if (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp ||
1818                  type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp ||
1819                  type == NSEventTypeOtherMouseDown || type == NSEventTypeOtherMouseUp)
1820         {
1821             [self handleMouseButton:anEvent];
1822             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1823         }
1824         else if (type == NSEventTypeScrollWheel)
1825         {
1826             [self handleScrollWheel:anEvent];
1827             ret = mouseCaptureWindow != nil;
1828         }
1829         else if (type == NSEventTypeKeyDown)
1830         {
1831             // -[NSApplication sendEvent:] seems to consume presses of the Help
1832             // key (Insert key on PC keyboards), so we have to bypass it and
1833             // send the event directly to the window.
1834             if (anEvent.keyCode == kVK_Help)
1835             {
1836                 [anEvent.window sendEvent:anEvent];
1837                 ret = TRUE;
1838             }
1839         }
1840         else if (type == NSEventTypeKeyUp)
1841         {
1842             uint16_t keyCode = [anEvent keyCode];
1843             if ([self isKeyPressed:keyCode])
1844             {
1845                 WineWindow* window = (WineWindow*)[anEvent window];
1846                 [self noteKey:keyCode pressed:FALSE];
1847                 if ([window isKindOfClass:[WineWindow class]])
1848                     [window postKeyEvent:anEvent];
1849             }
1850         }
1851         else if (!useDragNotifications && type == NSEventTypeAppKitDefined)
1852         {
1853             WineWindow *window = (WineWindow *)[anEvent window];
1854             short subtype = [anEvent subtype];
1856             // These subtypes are not documented but they appear to mean
1857             // "a window is being dragged" and "a window is no longer being
1858             // dragged", respectively.
1859             if ((subtype == 20 || subtype == 21) && [window isKindOfClass:[WineWindow class]])
1860                 [self handleWindowDrag:window begin:(subtype == 20)];
1861         }
1863         return ret;
1864     }
1866     - (void) didSendEvent:(NSEvent*)anEvent
1867     {
1868         NSEventType type = [anEvent type];
1870         if (type == NSEventTypeKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1871         {
1872             NSUInteger modifiers = [anEvent modifierFlags];
1873             if ((modifiers & NSEventModifierFlagCommand) &&
1874                 !(modifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)))
1875             {
1876                 // Command-Tab and Command-Shift-Tab would normally be intercepted
1877                 // by the system to switch applications.  If we're seeing it, it's
1878                 // presumably because we've captured the displays, preventing
1879                 // normal application switching.  Do it manually.
1880                 [self handleCommandTab];
1881             }
1882         }
1883     }
1885     - (void) setupObservations
1886     {
1887         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1888         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1889         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
1891         [nc addObserverForName:NSWindowDidBecomeKeyNotification
1892                         object:nil
1893                          queue:nil
1894                     usingBlock:^(NSNotification *note){
1895             NSWindow* window = [note object];
1896             [keyWindows removeObjectIdenticalTo:window];
1897             [keyWindows insertObject:window atIndex:0];
1898         }];
1900         [nc addObserverForName:NSWindowWillCloseNotification
1901                         object:nil
1902                          queue:[NSOperationQueue mainQueue]
1903                     usingBlock:^(NSNotification *note){
1904             NSWindow* window = [note object];
1905             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
1906                 return;
1907             [keyWindows removeObjectIdenticalTo:window];
1908             if (window == lastTargetWindow)
1909                 lastTargetWindow = nil;
1910             if (window == self.mouseCaptureWindow)
1911                 self.mouseCaptureWindow = nil;
1912             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
1913             {
1914                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
1915                     [self updateFullscreenWindows];
1916                 });
1917             }
1918             [windowsBeingDragged removeObject:window];
1919         }];
1921         if (useDragNotifications) {
1922             [nc addObserverForName:NSWindowWillStartDraggingNotification
1923                             object:nil
1924                              queue:[NSOperationQueue mainQueue]
1925                         usingBlock:^(NSNotification *note){
1926                 NSWindow* window = [note object];
1927                 if ([window isKindOfClass:[WineWindow class]])
1928                     [self handleWindowDrag:(WineWindow *)window begin:YES];
1929             }];
1931             [nc addObserverForName:NSWindowDidEndDraggingNotification
1932                             object:nil
1933                              queue:[NSOperationQueue mainQueue]
1934                         usingBlock:^(NSNotification *note){
1935                 NSWindow* window = [note object];
1936                 if ([window isKindOfClass:[WineWindow class]])
1937                     [self handleWindowDrag:(WineWindow *)window begin:NO];
1938             }];
1939         }
1941         [nc addObserver:self
1942                selector:@selector(keyboardSelectionDidChange)
1943                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
1944                  object:nil];
1946         /* The above notification isn't sent unless the NSTextInputContext
1947            class has initialized itself.  Poke it. */
1948         [NSTextInputContext self];
1950         [wsnc addObserver:self
1951                  selector:@selector(activeSpaceDidChange)
1952                      name:NSWorkspaceActiveSpaceDidChangeNotification
1953                    object:nil];
1955         [nc addObserver:self
1956                selector:@selector(releaseMouseCapture)
1957                    name:NSMenuDidBeginTrackingNotification
1958                  object:nil];
1960         [dnc        addObserver:self
1961                        selector:@selector(releaseMouseCapture)
1962                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
1963                          object:nil
1964              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
1966         [dnc addObserver:self
1967                 selector:@selector(enabledKeyboardInputSourcesChanged)
1968                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
1969                   object:nil];
1971         if ([NSApplication instancesRespondToSelector:@selector(yieldActivationToApplication:)])
1972         {
1973             /* App activation cooperation, starting in macOS 14 Sonoma. */
1974             [dnc addObserver:self
1975                     selector:@selector(otherWineAppWillActivate:)
1976                         name:WineAppWillActivateNotification
1977                       object:nil
1978           suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
1979         }
1980     }
1982     - (void) otherWineAppWillActivate:(NSNotification *)note
1983     {
1984         NSProcessInfo *ourProcess;
1985         pid_t otherPID;
1986         NSString *ourConfigDir, *otherConfigDir, *ourPrefix, *otherPrefix;
1987         NSRunningApplication *otherApp;
1989         /* No point in yielding if we're not the foreground app. */
1990         if (![NSApp isActive]) return;
1992         /* Ignore requests from ourself, dead processes, and other prefixes. */
1993         ourProcess = [NSProcessInfo processInfo];
1994         otherPID = [note.userInfo[WineActivatingAppPIDKey] integerValue];
1995         if (otherPID == ourProcess.processIdentifier) return;
1997         otherApp = [NSRunningApplication runningApplicationWithProcessIdentifier:otherPID];
1998         if (!otherApp) return;
2000         ourConfigDir = ourProcess.environment[@"WINECONFIGDIR"];
2001         otherConfigDir = note.userInfo[WineActivatingAppConfigDirKey];
2002         if (ourConfigDir.length && otherConfigDir.length &&
2003             ![ourConfigDir isEqualToString:otherConfigDir])
2004         {
2005             return;
2006         }
2008         ourPrefix = ourProcess.environment[@"WINEPREFIX"];
2009         otherPrefix = note.userInfo[WineActivatingAppPrefixKey];
2010         if (ourPrefix.length && otherPrefix.length &&
2011             ![ourPrefix isEqualToString:otherPrefix])
2012         {
2013             return;
2014         }
2016         /* There's a race condition here. The requesting app sends out
2017            WineAppWillActivateNotification and then activates itself, but since
2018            distributed notifications are asynchronous, we may not have yielded
2019            in time. So we call activateFromApplication: on the other app here,
2020            which will work around that race if it happened. If we didn't hit the
2021            race, the activateFromApplication: call will be a no-op. */
2023         /* We only add this observer if NSApplication responds to the yield
2024            methods, so they're safe to call without checking here. */
2025         [NSApp yieldActivationToApplication:otherApp];
2026         [otherApp activateFromApplication:[NSRunningApplication currentApplication]
2027                                   options:0];
2028     }
2030     - (void) tryToActivateIgnoringOtherApps:(BOOL)ignore
2031     {
2032         NSProcessInfo *processInfo;
2033         NSString *configDir, *prefix;
2034         NSDictionary *userInfo;
2036         if ([NSApp isActive]) return;  /* Nothing to do. */
2038         if (!ignore ||
2039             ![NSApplication instancesRespondToSelector:@selector(yieldActivationToApplication:)])
2040         {
2041             /* Either we don't need to force activation, or the OS is old enough
2042                that this is our only option. */
2043             [NSApp activateIgnoringOtherApps:ignore];
2044             return;
2045         }
2047         /* Ask other Wine apps to yield activation to us. */
2048         processInfo = [NSProcessInfo processInfo];
2049         configDir = processInfo.environment[@"WINECONFIGDIR"];
2050         prefix = processInfo.environment[@"WINEPREFIX"];
2051         userInfo = @{
2052             WineActivatingAppPIDKey: @(processInfo.processIdentifier),
2053             WineActivatingAppPrefixKey: prefix ? prefix : @"",
2054             WineActivatingAppConfigDirKey: configDir ? configDir : @""
2055         };
2057         [[NSDistributedNotificationCenter defaultCenter]
2058             postNotificationName:WineAppWillActivateNotification
2059                           object:nil
2060                         userInfo:userInfo
2061               deliverImmediately:YES];
2063         /* This is racy. See the note in otherWineAppWillActivate:. */
2064         [NSApp activate];
2065      }
2067     - (BOOL) inputSourceIsInputMethod
2068     {
2069         if (!inputSourceIsInputMethodValid)
2070         {
2071             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2072             if (inputSource)
2073             {
2074                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2075                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2076                 CFRelease(inputSource);
2077             }
2078             else
2079                 inputSourceIsInputMethod = FALSE;
2080             inputSourceIsInputMethodValid = TRUE;
2081         }
2083         return inputSourceIsInputMethod;
2084     }
2086     - (void) releaseMouseCapture
2087     {
2088         // This might be invoked on a background thread by the distributed
2089         // notification center.  Shunt it to the main thread.
2090         if (![NSThread isMainThread])
2091         {
2092             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2093             return;
2094         }
2096         if (mouseCaptureWindow)
2097         {
2098             macdrv_event* event;
2100             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2101             [mouseCaptureWindow.queue postEvent:event];
2102             macdrv_release_event(event);
2103         }
2104     }
2106     - (void) unminimizeWindowIfNoneVisible
2107     {
2108         if (![self frontWineWindow])
2109         {
2110             for (WineWindow* window in [NSApp windows])
2111             {
2112                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2113                 {
2114                     [window deminiaturize:self];
2115                     break;
2116                 }
2117             }
2118         }
2119     }
2121     - (void) setRetinaMode:(int)mode
2122     {
2123         retina_on = mode;
2125         [clipCursorHandler setRetinaMode:mode];
2127         for (WineWindow* window in [NSApp windows])
2128         {
2129             if ([window isKindOfClass:[WineWindow class]])
2130                 [window setRetinaMode:mode];
2131         }
2132     }
2135     /*
2136      * ---------- NSApplicationDelegate methods ----------
2137      */
2138     - (void)applicationDidBecomeActive:(NSNotification *)notification
2139     {
2140         NSNumber* displayID;
2141         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2143         latentDisplayModes = [[NSMutableDictionary alloc] init];
2144         for (displayID in modesToRealize)
2145         {
2146             CGDisplayModeRef mode = (CGDisplayModeRef)modesToRealize[displayID];
2147             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2148         }
2150         [self updateFullscreenWindows];
2151         [self adjustWindowLevels:YES];
2153         if (beenActive)
2154             [self unminimizeWindowIfNoneVisible];
2155         beenActive = TRUE;
2157         // If a Wine process terminates abruptly while it has the display captured
2158         // and switched to a different resolution, Mac OS X will uncapture the
2159         // displays and switch their resolutions back.  However, the other Wine
2160         // processes won't have their notion of the desktop rect changed back.
2161         // This can lead them to refuse to draw or acknowledge clicks in certain
2162         // portions of their windows.
2163         //
2164         // To solve this, we synthesize a displays-changed event whenever we're
2165         // activated.  This will provoke a re-synchronization of Wine's notion of
2166         // the desktop rect with the actual state.
2167         [self sendDisplaysChanged:TRUE];
2169         // The cursor probably moved while we were inactive.  Accumulated mouse
2170         // movement deltas are invalidated.  Make sure the next mouse move event
2171         // starts over from an absolute baseline.
2172         forceNextMouseMoveAbsolute = TRUE;
2173     }
2175     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2176     {
2177         primaryScreenHeightValid = FALSE;
2178         [self sendDisplaysChanged:FALSE];
2179         [self adjustWindowLevels];
2181         // When the display configuration changes, the cursor position may jump.
2182         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2183         // mouse move event starts over from an absolute baseline.
2184         forceNextMouseMoveAbsolute = TRUE;
2185     }
2187     - (void)applicationDidResignActive:(NSNotification *)notification
2188     {
2189         macdrv_event* event;
2190         WineEventQueue* queue;
2192         [self invalidateGotFocusEvents];
2194         event = macdrv_create_event(APP_DEACTIVATED, nil);
2196         [eventQueuesLock lock];
2197         for (queue in eventQueues)
2198             [queue postEvent:event];
2199         [eventQueuesLock unlock];
2201         macdrv_release_event(event);
2203         [self releaseMouseCapture];
2204     }
2206     - (void) applicationDidUnhide:(NSNotification*)aNotification
2207     {
2208         [self adjustWindowLevels];
2209     }
2211     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2212     {
2213         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2214         // don't count as "visible windows" for this purpose.
2215         [self unminimizeWindowIfNoneVisible];
2216         return YES;
2217     }
2219     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2220     {
2221         NSApplicationTerminateReply ret = NSTerminateNow;
2222         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2223         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2224         macdrv_event* event;
2225         WineEventQueue* queue;
2227         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2228         event->deliver = 1;
2229         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2230         {
2231             case kAELogOut:
2232             case kAEReallyLogOut:
2233                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2234                 break;
2235             case kAEShowRestartDialog:
2236                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2237                 break;
2238             case kAEShowShutdownDialog:
2239                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2240                 break;
2241             default:
2242                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2243                 break;
2244         }
2246         [eventQueuesLock lock];
2248         if ([eventQueues count])
2249         {
2250             for (queue in eventQueues)
2251                 [queue postEvent:event];
2252             ret = NSTerminateLater;
2253         }
2255         [eventQueuesLock unlock];
2257         macdrv_release_event(event);
2259         return ret;
2260     }
2262     - (void)applicationWillBecomeActive:(NSNotification *)notification
2263     {
2264         macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2265         event->deliver = 1;
2267         [eventQueuesLock lock];
2268         for (WineEventQueue* queue in eventQueues)
2269             [queue postEvent:event];
2270         [eventQueuesLock unlock];
2272         macdrv_release_event(event);
2273     }
2275     - (void)applicationWillResignActive:(NSNotification *)notification
2276     {
2277         [self adjustWindowLevels:NO];
2278     }
2280 /***********************************************************************
2281  *              PerformRequest
2283  * Run-loop-source perform callback.  Pull request blocks from the
2284  * array of queued requests and invoke them.
2285  */
2286 static void PerformRequest(void *info)
2288 @autoreleasepool
2290     WineApplicationController* controller = [WineApplicationController sharedController];
2292     for (;;)
2293     {
2294         @autoreleasepool
2295         {
2296             __block dispatch_block_t block;
2298             dispatch_sync(controller->requestsManipQueue, ^{
2299                 if ([controller->requests count])
2300                 {
2301                     block = (dispatch_block_t)[controller->requests[0] retain];
2302                     [controller->requests removeObjectAtIndex:0];
2303                 }
2304                 else
2305                     block = nil;
2306             });
2308             if (!block)
2309                 break;
2311             block();
2312             [block release];
2313         }
2314     }
2318 /***********************************************************************
2319  *              OnMainThreadAsync
2321  * Run a block on the main thread asynchronously.
2322  */
2323 void OnMainThreadAsync(dispatch_block_t block)
2325     WineApplicationController* controller = [WineApplicationController sharedController];
2327     block = [block copy];
2328     dispatch_sync(controller->requestsManipQueue, ^{
2329         [controller->requests addObject:block];
2330     });
2331     [block release];
2332     CFRunLoopSourceSignal(controller->requestSource);
2333     CFRunLoopWakeUp(CFRunLoopGetMain());
2336 @end
2338 /***********************************************************************
2339  *              LogError
2340  */
2341 void LogError(const char* func, NSString* format, ...)
2343     va_list args;
2344     va_start(args, format);
2345     LogErrorv(func, format, args);
2346     va_end(args);
2349 /***********************************************************************
2350  *              LogErrorv
2351  */
2352 void LogErrorv(const char* func, NSString* format, va_list args)
2354 @autoreleasepool
2356     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2357     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2358     [message release];
2362 /***********************************************************************
2363  *              macdrv_window_rejected_focus
2365  * Pass focus to the next window that hasn't already rejected this same
2366  * WINDOW_GOT_FOCUS event.
2367  */
2368 void macdrv_window_rejected_focus(const macdrv_event *event)
2370     OnMainThread(^{
2371         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2372     });
2375 /***********************************************************************
2376  *              macdrv_get_input_source_info
2378  * Returns the keyboard layout uchr data, keyboard type and input source.
2379  */
2380 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2382     OnMainThread(^{
2383         TISInputSourceRef inputSourceLayout;
2385         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2386         if (inputSourceLayout)
2387         {
2388             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2389                                 kTISPropertyUnicodeKeyLayoutData);
2390             *uchr = CFDataCreateCopy(NULL, data);
2391             CFRelease(inputSourceLayout);
2393             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2394             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2395             if (input_source)
2396                 *input_source = TISCopyCurrentKeyboardInputSource();
2397         }
2398     });
2401 /***********************************************************************
2402  *              macdrv_beep
2404  * Play the beep sound configured by the user in System Preferences.
2405  */
2406 void macdrv_beep(void)
2408     OnMainThreadAsync(^{
2409         NSBeep();
2410     });
2413 /***********************************************************************
2414  *              macdrv_set_display_mode
2415  */
2416 int macdrv_set_display_mode(const struct macdrv_display* display,
2417                             CGDisplayModeRef display_mode)
2419     __block int ret;
2421     OnMainThread(^{
2422         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2423     });
2425     return ret;
2428 /***********************************************************************
2429  *              macdrv_set_cursor
2431  * Set the cursor.
2433  * If name is non-NULL, it is a selector for a class method on NSCursor
2434  * identifying the cursor to set.  In that case, frames is ignored.  If
2435  * name is NULL, then frames is used.
2437  * frames is an array of dictionaries.  Each dictionary is a frame of
2438  * an animated cursor.  Under the key "image" is a CGImage for the
2439  * frame.  Under the key "duration" is a CFNumber time interval, in
2440  * seconds, for how long that frame is presented before proceeding to
2441  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2442  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2443  * This is the hot spot, measured in pixels down and to the right of the
2444  * top-left corner of the image.
2446  * If the array has exactly 1 element, the cursor is static, not
2447  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2448  */
2449 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2451     SEL sel;
2453     sel = NSSelectorFromString((NSString*)name);
2454     if (sel)
2455     {
2456         OnMainThreadAsync(^{
2457             WineApplicationController* controller = [WineApplicationController sharedController];
2458             [controller setCursorWithFrames:nil];
2459             controller.cursor = [NSCursor performSelector:sel];
2460             [controller unhideCursor];
2461         });
2462     }
2463     else
2464     {
2465         NSArray* nsframes = (NSArray*)frames;
2466         if ([nsframes count])
2467         {
2468             OnMainThreadAsync(^{
2469                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2470             });
2471         }
2472         else
2473         {
2474             OnMainThreadAsync(^{
2475                 WineApplicationController* controller = [WineApplicationController sharedController];
2476                 [controller setCursorWithFrames:nil];
2477                 [controller hideCursor];
2478             });
2479         }
2480     }
2483 /***********************************************************************
2484  *              macdrv_get_cursor_position
2486  * Obtains the current cursor position.  Returns zero on failure,
2487  * non-zero on success.
2488  */
2489 int macdrv_get_cursor_position(CGPoint *pos)
2491     OnMainThread(^{
2492         NSPoint location = [NSEvent mouseLocation];
2493         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2494         *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2495     });
2497     return TRUE;
2500 /***********************************************************************
2501  *              macdrv_set_cursor_position
2503  * Sets the cursor position without generating events.  Returns zero on
2504  * failure, non-zero on success.
2505  */
2506 int macdrv_set_cursor_position(CGPoint pos)
2508     __block int ret;
2510     OnMainThread(^{
2511         ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2512     });
2514     return ret;
2517 /***********************************************************************
2518  *              macdrv_clip_cursor
2520  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2521  * to or larger than the whole desktop region, the cursor is unclipped.
2522  * Returns zero on failure, non-zero on success.
2523  */
2524 int macdrv_clip_cursor(CGRect r)
2526     __block int ret;
2528     OnMainThread(^{
2529         WineApplicationController* controller = [WineApplicationController sharedController];
2530         BOOL clipping = FALSE;
2531         CGRect rect = r;
2533         if (!CGRectIsInfinite(rect))
2534             rect = cgrect_mac_from_win(rect);
2536         if (!CGRectIsInfinite(rect))
2537         {
2538             NSRect nsrect = NSRectFromCGRect(rect);
2539             NSScreen* screen;
2541             /* Convert the rectangle from top-down coords to bottom-up. */
2542             [controller flipRect:&nsrect];
2544             clipping = FALSE;
2545             for (screen in [NSScreen screens])
2546             {
2547                 if (!NSContainsRect(nsrect, [screen frame]))
2548                 {
2549                     clipping = TRUE;
2550                     break;
2551                 }
2552             }
2553         }
2555         if (clipping)
2556             ret = [controller startClippingCursor:rect];
2557         else
2558             ret = [controller stopClippingCursor];
2559     });
2561     return ret;
2564 /***********************************************************************
2565  *              macdrv_set_application_icon
2567  * Set the application icon.  The images array contains CGImages.  If
2568  * there are more than one, then they represent different sizes or
2569  * color depths from the icon resource.  If images is NULL or empty,
2570  * restores the default application image.
2571  */
2572 void macdrv_set_application_icon(CFArrayRef images)
2574     NSArray* imageArray = (NSArray*)images;
2576     OnMainThreadAsync(^{
2577         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2578     });
2581 /***********************************************************************
2582  *              macdrv_quit_reply
2583  */
2584 void macdrv_quit_reply(int reply)
2586     OnMainThread(^{
2587         [NSApp replyToApplicationShouldTerminate:reply];
2588     });
2591 /***********************************************************************
2592  *              macdrv_using_input_method
2593  */
2594 int macdrv_using_input_method(void)
2596     __block BOOL ret;
2598     OnMainThread(^{
2599         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2600     });
2602     return ret;
2605 /***********************************************************************
2606  *              macdrv_set_mouse_capture_window
2607  */
2608 void macdrv_set_mouse_capture_window(macdrv_window window)
2610     WineWindow* w = (WineWindow*)window;
2612     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2614     OnMainThread(^{
2615         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2616     });
2619 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2620 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2621 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2623 /***********************************************************************
2624  *              macdrv_create_input_source_list
2625  */
2626 CFArrayRef macdrv_create_input_source_list(void)
2628     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2630     OnMainThread(^{
2631         CFArrayRef input_list;
2632         CFDictionaryRef filter_dict;
2633         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2634         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2635         int i;
2637         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2638                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2639         input_list = TISCreateInputSourceList(filter_dict, false);
2641         for (i = 0; i < CFArrayGetCount(input_list); i++)
2642         {
2643             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2644             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2645             CFDictionaryRef entry;
2646             const void *input_keys[3] = { macdrv_input_source_input_key,
2647                                           macdrv_input_source_type_key,
2648                                           macdrv_input_source_lang_key };
2649             const void *input_values[3];
2651             input_values[0] = input;
2652             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2653             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2655             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2656                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2658             CFArrayAppendValue(ret, entry);
2659             CFRelease(entry);
2660         }
2661         CFRelease(input_list);
2662         CFRelease(filter_dict);
2663     });
2665     return ret;
2668 int macdrv_select_input_source(TISInputSourceRef input_source)
2670     __block int ret = FALSE;
2672     OnMainThread(^{
2673         ret = (TISSelectInputSource(input_source) == noErr);
2674     });
2676     return ret;
2679 void macdrv_set_cocoa_retina_mode(int new_mode)
2681     OnMainThread(^{
2682         [[WineApplicationController sharedController] setRetinaMode:new_mode];
2683     });
2686 int macdrv_is_any_wine_window_visible(void)
2688     __block int ret = FALSE;
2690     OnMainThread(^{
2691         ret = [[WineApplicationController sharedController] isAnyWineWindowVisible];
2692     });
2694     return ret;