dmusic: Avoid swallowing collection Load failures.
[wine.git] / dlls / winemac.drv / cocoa_app.m
blob5a6a3e0100579df2ad8f20430d72255b033e5f62
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"
29 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
31 // Private notifications that are reliably dispatched when a window is moved by dragging its titlebar.
32 // The object of the notification is the window being dragged.
33 // Available in macOS 10.12+
34 static NSString* const NSWindowWillStartDraggingNotification = @"NSWindowWillStartDraggingNotification";
35 static NSString* const NSWindowDidEndDraggingNotification = @"NSWindowDidEndDraggingNotification";
37 // Internal distributed notification to handle cooperative app activation in Sonoma.
38 static NSString* const WineAppWillActivateNotification = @"WineAppWillActivateNotification";
39 static NSString* const WineActivatingAppPIDKey = @"ActivatingAppPID";
40 static NSString* const WineActivatingAppPrefixKey = @"ActivatingAppPrefix";
41 static NSString* const WineActivatingAppConfigDirKey = @"ActivatingAppConfigDir";
44 int macdrv_err_on;
47 #if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
48 @interface NSWindow (WineAutoTabbingExtensions)
50     + (void) setAllowsAutomaticWindowTabbing:(BOOL)allows;
52 @end
53 #endif
56 #if !defined(MAC_OS_VERSION_14_0) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_14_0
57 @interface NSApplication (CooperativeActivationSelectorsForOldSDKs)
59     - (void)activate;
60     - (void)yieldActivationToApplication:(NSRunningApplication *)application;
61     - (void)yieldActivationToApplicationWithBundleIdentifier:(NSString *)bundleIdentifier;
63 @end
65 @interface NSRunningApplication (CooperativeActivationSelectorsForOldSDKs)
67     - (BOOL)activateFromApplication:(NSRunningApplication *)application
68                             options:(NSApplicationActivationOptions)options;
70 @end
71 #endif
74 /***********************************************************************
75  *              WineLocalizedString
76  *
77  * Look up a localized string by its ID in the dictionary.
78  */
79 static NSString* WineLocalizedString(unsigned int stringID)
81     NSNumber* key = [NSNumber numberWithUnsignedInt:stringID];
82     return [(NSDictionary*)localized_strings objectForKey:key];
86 @implementation WineApplication
88 @synthesize wineController;
90     - (void) sendEvent:(NSEvent*)anEvent
91     {
92         if (![wineController handleEvent:anEvent])
93         {
94             [super sendEvent:anEvent];
95             [wineController didSendEvent:anEvent];
96         }
97     }
99     - (void) setWineController:(WineApplicationController*)newController
100     {
101         wineController = newController;
102         [self setDelegate:wineController];
103     }
105 @end
108 @interface WineApplicationController ()
110 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
111 @property (copy, nonatomic) NSArray* cursorFrames;
112 @property (retain, nonatomic) NSTimer* cursorTimer;
113 @property (retain, nonatomic) NSCursor* cursor;
114 @property (retain, nonatomic) NSImage* applicationIcon;
115 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
116 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
118     - (void) setupObservations;
119     - (void) applicationDidBecomeActive:(NSNotification *)notification;
121     static void PerformRequest(void *info);
123 @end
126 @implementation WineApplicationController
128     @synthesize keyboardType, lastFlagsChanged;
129     @synthesize applicationIcon;
130     @synthesize cursorFrames, cursorTimer, cursor;
131     @synthesize mouseCaptureWindow;
132     @synthesize lastSetCursorPositionTime;
134     + (void) initialize
135     {
136         if (self == [WineApplicationController class])
137         {
138             NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
139                                       @"", @"NSQuotedKeystrokeBinding",
140                                       @"", @"NSRepeatCountBinding",
141                                       [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
142                                       nil];
143             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
145             if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
146                 [NSWindow setAllowsAutomaticWindowTabbing:NO];
147         }
148     }
150     + (WineApplicationController*) sharedController
151     {
152         static WineApplicationController* sharedController;
153         static dispatch_once_t once;
155         dispatch_once(&once, ^{
156             sharedController = [[self alloc] init];
157         });
159         return sharedController;
160     }
162     - (id) init
163     {
164         self = [super init];
165         if (self != nil)
166         {
167             CFRunLoopSourceContext context = { 0 };
168             context.perform = PerformRequest;
169             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
170             if (!requestSource)
171             {
172                 [self release];
173                 return nil;
174             }
175             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
176             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
178             requests =  [[NSMutableArray alloc] init];
179             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
181             eventQueues = [[NSMutableArray alloc] init];
182             eventQueuesLock = [[NSLock alloc] init];
184             keyWindows = [[NSMutableArray alloc] init];
186             originalDisplayModes = [[NSMutableDictionary alloc] init];
187             latentDisplayModes = [[NSMutableDictionary alloc] init];
189             windowsBeingDragged = [[NSMutableSet alloc] init];
191             // On macOS 10.12+, use notifications to more reliably detect when windows are being dragged.
192             if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)])
193             {
194                 NSOperatingSystemVersion requiredVersion = { 10, 12, 0 };
195                 useDragNotifications = [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:requiredVersion];
196             }
197             else
198                 useDragNotifications = NO;
200             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
201                 !keyWindows || !originalDisplayModes || !latentDisplayModes)
202             {
203                 [self release];
204                 return nil;
205             }
207             [self setupObservations];
209             keyboardType = LMGetKbdType();
211             if ([NSApp isActive])
212                 [self applicationDidBecomeActive:nil];
213         }
214         return self;
215     }
217     - (void) dealloc
218     {
219         [windowsBeingDragged release];
220         [cursor release];
221         [screenFrameCGRects release];
222         [applicationIcon release];
223         [clipCursorHandler release];
224         [cursorTimer release];
225         [cursorFrames release];
226         [latentDisplayModes release];
227         [originalDisplayModes release];
228         [keyWindows release];
229         [eventQueues release];
230         [eventQueuesLock release];
231         if (requestsManipQueue) dispatch_release(requestsManipQueue);
232         [requests release];
233         if (requestSource)
234         {
235             CFRunLoopSourceInvalidate(requestSource);
236             CFRelease(requestSource);
237         }
238         [super dealloc];
239     }
241     - (void) transformProcessToForeground:(BOOL)activateIfTransformed
242     {
243         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
244         {
245             NSMenu* mainMenu;
246             NSMenu* submenu;
247             NSString* bundleName;
248             NSString* title;
249             NSMenuItem* item;
251             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
253             if (activateIfTransformed)
254                 [self tryToActivateIgnoringOtherApps:YES];
256 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
257             if (!enable_app_nap && [NSProcessInfo instancesRespondToSelector:@selector(beginActivityWithOptions:reason:)])
258             {
259                 [[[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep
260                                                                 reason:@"Running Windows program"] retain]; // intentional leak
261             }
262 #endif
264             mainMenu = [[[NSMenu alloc] init] autorelease];
266             // Application menu
267             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
268             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
270             if ([bundleName length])
271                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
272             else
273                 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
274             item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
276             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
277                                       action:@selector(hideOtherApplications:)
278                                keyEquivalent:@"h"];
279             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
281             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
282                                       action:@selector(unhideAllApplications:)
283                                keyEquivalent:@""];
285             [submenu addItem:[NSMenuItem separatorItem]];
287             if ([bundleName length])
288                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
289             else
290                 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
291             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
292             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
293             item = [[[NSMenuItem alloc] init] autorelease];
294             [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
295             [item setSubmenu:submenu];
296             [mainMenu addItem:item];
298             // Window menu
299             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
300             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
301                                action:@selector(performMiniaturize:)
302                         keyEquivalent:@""];
303             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
304                                action:@selector(performZoom:)
305                         keyEquivalent:@""];
306             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
307                                       action:@selector(toggleFullScreen:)
308                                keyEquivalent:@"f"];
309             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand |
310                                                NSEventModifierFlagOption |
311                                                NSEventModifierFlagControl];
312             [submenu addItem:[NSMenuItem separatorItem]];
313             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
314                                action:@selector(arrangeInFront:)
315                         keyEquivalent:@""];
316             item = [[[NSMenuItem alloc] init] autorelease];
317             [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
318             [item setSubmenu:submenu];
319             [mainMenu addItem:item];
321             [NSApp setMainMenu:mainMenu];
322             [NSApp setWindowsMenu:submenu];
324             [NSApp setApplicationIconImage:self.applicationIcon];
325         }
326     }
328     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
329     {
330         PerformRequest(NULL);
332         do
333         {
334             if (processEvents)
335             {
336                 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
337                 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
338                                                     untilDate:timeout
339                                                        inMode:NSDefaultRunLoopMode
340                                                       dequeue:YES];
341                 if (event)
342                     [NSApp sendEvent:event];
343                 [pool release];
344             }
345             else
346                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
347         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
349         return *done;
350     }
352     - (BOOL) registerEventQueue:(WineEventQueue*)queue
353     {
354         [eventQueuesLock lock];
355         [eventQueues addObject:queue];
356         [eventQueuesLock unlock];
357         return TRUE;
358     }
360     - (void) unregisterEventQueue:(WineEventQueue*)queue
361     {
362         [eventQueuesLock lock];
363         [eventQueues removeObjectIdenticalTo:queue];
364         [eventQueuesLock unlock];
365     }
367     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
368     {
369         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
370     }
372     - (double) ticksForEventTime:(NSTimeInterval)eventTime
373     {
374         return (eventTime + eventTimeAdjustment) * 1000;
375     }
377     /* Invalidate old focus offers across all queues. */
378     - (void) invalidateGotFocusEvents
379     {
380         WineEventQueue* queue;
382         windowFocusSerial++;
384         [eventQueuesLock lock];
385         for (queue in eventQueues)
386         {
387             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
388                                    forWindow:nil];
389         }
390         [eventQueuesLock unlock];
391     }
393     - (void) windowGotFocus:(WineWindow*)window
394     {
395         macdrv_event* event;
397         [self invalidateGotFocusEvents];
399         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
400         event->window_got_focus.serial = windowFocusSerial;
401         if (triedWindows)
402             event->window_got_focus.tried_windows = [triedWindows retain];
403         else
404             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
405         [window.queue postEvent:event];
406         macdrv_release_event(event);
407     }
409     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
410     {
411         if (event->window_got_focus.serial == windowFocusSerial)
412         {
413             NSMutableArray* windows = [keyWindows mutableCopy];
414             NSNumber* windowNumber;
415             WineWindow* window;
417             for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
418             {
419                 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
420                 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
421                     ![windows containsObject:window])
422                     [windows addObject:window];
423             }
425             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
426             [triedWindows addObject:(WineWindow*)event->window];
427             for (window in windows)
428             {
429                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
430                 {
431                     [window makeKeyWindow];
432                     break;
433                 }
434             }
435             triedWindows = nil;
436             [windows release];
437         }
438     }
440     static BOOL EqualInputSource(TISInputSourceRef source1, TISInputSourceRef source2)
441     {
442         if (!source1 && !source2)
443             return TRUE;
444         if (!source1 || !source2)
445             return FALSE;
446         return CFEqual(source1, source2);
447     }
449     - (void) keyboardSelectionDidChange:(BOOL)force
450     {
451         TISInputSourceRef inputSource, inputSourceLayout;
453         if (!force)
454         {
455             NSTextInputContext* context = [NSTextInputContext currentInputContext];
456             if (!context || ![context client])
457                 return;
458         }
460         inputSource = TISCopyCurrentKeyboardInputSource();
461         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
462         if (!force && EqualInputSource(inputSource, lastKeyboardInputSource) &&
463             EqualInputSource(inputSourceLayout, lastKeyboardLayoutInputSource))
464         {
465             if (inputSource) CFRelease(inputSource);
466             if (inputSourceLayout) CFRelease(inputSourceLayout);
467             return;
468         }
470         if (lastKeyboardInputSource)
471             CFRelease(lastKeyboardInputSource);
472         lastKeyboardInputSource = inputSource;
473         if (lastKeyboardLayoutInputSource)
474             CFRelease(lastKeyboardLayoutInputSource);
475         lastKeyboardLayoutInputSource = inputSourceLayout;
477         inputSourceIsInputMethodValid = FALSE;
479         if (inputSourceLayout)
480         {
481             CFDataRef uchr;
482             uchr = TISGetInputSourceProperty(inputSourceLayout,
483                     kTISPropertyUnicodeKeyLayoutData);
484             if (uchr)
485             {
486                 macdrv_event* event;
487                 WineEventQueue* queue;
489                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
490                 event->keyboard_changed.keyboard_type = self.keyboardType;
491                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
492                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
493                 event->keyboard_changed.input_source = (TISInputSourceRef)CFRetain(inputSource);
495                 if (event->keyboard_changed.uchr)
496                 {
497                     [eventQueuesLock lock];
499                     for (queue in eventQueues)
500                         [queue postEvent:event];
502                     [eventQueuesLock unlock];
503                 }
505                 macdrv_release_event(event);
506             }
507         }
508     }
510     - (void) keyboardSelectionDidChange
511     {
512         [self keyboardSelectionDidChange:NO];
513     }
515     - (void) setKeyboardType:(CGEventSourceKeyboardType)newType
516     {
517         if (newType != keyboardType)
518         {
519             keyboardType = newType;
520             [self keyboardSelectionDidChange:YES];
521         }
522     }
524     - (void) enabledKeyboardInputSourcesChanged
525     {
526         macdrv_layout_list_needs_update = TRUE;
527     }
529     - (CGFloat) primaryScreenHeight
530     {
531         if (!primaryScreenHeightValid)
532         {
533             NSArray* screens = [NSScreen screens];
534             NSUInteger count = [screens count];
535             if (count)
536             {
537                 NSUInteger size;
538                 CGRect* rect;
539                 NSScreen* screen;
541                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
542                 primaryScreenHeightValid = TRUE;
544                 size = count * sizeof(CGRect);
545                 if (!screenFrameCGRects)
546                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
547                 else
548                     [screenFrameCGRects setLength:size];
550                 rect = [screenFrameCGRects mutableBytes];
551                 for (screen in screens)
552                 {
553                     CGRect temp = NSRectToCGRect([screen frame]);
554                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
555                     *rect++ = temp;
556                 }
557             }
558             else
559                 return 1280; /* arbitrary value */
560         }
562         return primaryScreenHeight;
563     }
565     - (NSPoint) flippedMouseLocation:(NSPoint)point
566     {
567         /* This relies on the fact that Cocoa's mouse location points are
568            actually off by one (precisely because they were flipped from
569            Quartz screen coordinates using this same technique). */
570         point.y = [self primaryScreenHeight] - point.y;
571         return point;
572     }
574     - (void) flipRect:(NSRect*)rect
575     {
576         // We don't use -primaryScreenHeight here so there's no chance of having
577         // out-of-date cached info.  This method is called infrequently enough
578         // that getting the screen height each time is not prohibitively expensive.
579         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
580     }
582     - (WineWindow*) frontWineWindow
583     {
584         NSNumber* windowNumber;
585         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
586         {
587             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
588             if ([window isKindOfClass:[WineWindow class]] && [window screen])
589                 return (WineWindow*)window;
590         }
592         return nil;
593     }
595     - (void) adjustWindowLevels:(BOOL)active
596     {
597         NSArray* windowNumbers;
598         NSMutableArray* wineWindows;
599         NSNumber* windowNumber;
600         NSUInteger nextFloatingIndex = 0;
601         __block NSInteger maxLevel = NSIntegerMin;
602         __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
603         __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
604         __block WineWindow* prev = nil;
605         WineWindow* window;
607         if ([NSApp isHidden]) return;
609         windowNumbers = [NSWindow windowNumbersWithOptions:0];
610         wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
612         // For the most part, we rely on the window server's ordering of the windows
613         // to be authoritative.  The one exception is if the "floating" property of
614         // one of the windows has been changed, it may be in the wrong level and thus
615         // in the order.  This method is what's supposed to fix that up.  So build
616         // a list of Wine windows sorted first by floating-ness and then by order
617         // as indicated by the window server.
618         for (windowNumber in windowNumbers)
619         {
620             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
621             if ([window isKindOfClass:[WineWindow class]])
622             {
623                 if (window.floating)
624                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
625                 else
626                     [wineWindows addObject:window];
627             }
628         }
630         NSDisableScreenUpdates();
632         // Go from back to front so that all windows in front of one which is
633         // elevated for full-screen are also elevated.
634         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
635                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
636             WineWindow* window = (WineWindow*)obj;
637             NSInteger origLevel = [window level];
638             NSInteger newLevel = [window minimumLevelForActive:active];
640             if (window.floating)
641             {
642                 if (minFloatingLevel <= maxNonfloatingLevel)
643                     minFloatingLevel = maxNonfloatingLevel + 1;
644                 if (newLevel < minFloatingLevel)
645                     newLevel = minFloatingLevel;
646             }
648             if (newLevel < maxLevel)
649                 newLevel = maxLevel;
650             else
651                 maxLevel = newLevel;
653             if (!window.floating && maxNonfloatingLevel < newLevel)
654                 maxNonfloatingLevel = newLevel;
656             if (newLevel != origLevel)
657             {
658                 [window setLevel:newLevel];
660                 if (origLevel < newLevel)
661                 {
662                     // If we increased the level, the window should be toward the
663                     // back of its new level (but still ahead of the previous
664                     // windows we did this to).
665                     if (prev)
666                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
667                     else
668                         [window orderBack:nil];
669                 }
670                 else
671                 {
672                     // If we decreased the level, we want the window at the top
673                     // of its new level. -setLevel: is documented to do that on
674                     // its own, but that's buggy on Ventura. Since we're looping
675                     // back-to-front here, -orderFront: will do the right thing.
676                     [window orderFront:nil];
677                 }
678             }
680             prev = window;
681         }];
683         NSEnableScreenUpdates();
685         [wineWindows release];
687         // The above took care of the visible windows on the current space.  That
688         // leaves windows on other spaces, minimized windows, and windows which
689         // are not ordered in.  We want to leave windows on other spaces alone
690         // so the space remains just as they left it (when viewed in Exposé or
691         // Mission Control, for example).  We'll adjust the window levels again
692         // after we switch to another space, anyway.  Windows which aren't
693         // ordered in will be handled when we order them in.  Minimized windows
694         // on the current space should be set to the level they would have gotten
695         // if they were at the front of the windows with the same floating-ness,
696         // because that's where they'll go if/when they are unminimized.  Again,
697         // for good measure we'll adjust window levels again when a window is
698         // unminimized, too.
699         for (window in [NSApp windows])
700         {
701             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
702                 [window isOnActiveSpace])
703             {
704                 NSInteger origLevel = [window level];
705                 NSInteger newLevel = [window minimumLevelForActive:YES];
706                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
708                 if (newLevel < maxLevelForType)
709                     newLevel = maxLevelForType;
711                 if (newLevel != origLevel)
712                     [window setLevel:newLevel];
713             }
714         }
715     }
717     - (void) adjustWindowLevels
718     {
719         [self adjustWindowLevels:[NSApp isActive]];
720     }
722     - (void) updateFullscreenWindows
723     {
724         if (capture_displays_for_fullscreen && [NSApp isActive])
725         {
726             BOOL anyFullscreen = FALSE;
727             NSNumber* windowNumber;
728             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
729             {
730                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
731                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
732                 {
733                     anyFullscreen = TRUE;
734                     break;
735                 }
736             }
738             if (anyFullscreen)
739             {
740                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
741                     displaysCapturedForFullscreen = TRUE;
742             }
743             else if (displaysCapturedForFullscreen)
744             {
745                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
746                     displaysCapturedForFullscreen = FALSE;
747             }
748         }
749     }
751     - (void) activeSpaceDidChange
752     {
753         [self updateFullscreenWindows];
754         [self adjustWindowLevels];
755     }
757     - (void) sendDisplaysChanged:(BOOL)activating
758     {
759         macdrv_event* event;
760         WineEventQueue* queue;
762         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
763         event->displays_changed.activating = activating;
765         [eventQueuesLock lock];
767         // If we're activating, then we just need one of our threads to get the
768         // event, so it can send it directly to the desktop window.  Otherwise,
769         // we need all of the threads to get it because we don't know which owns
770         // the desktop window and only that one will do anything with it.
771         if (activating) event->deliver = 1;
773         for (queue in eventQueues)
774             [queue postEvent:event];
775         [eventQueuesLock unlock];
777         macdrv_release_event(event);
778     }
780     // We can compare two modes directly using CFEqual, but that may require that
781     // they are identical to a level that we don't need.  In particular, when the
782     // OS switches between the integrated and discrete GPUs, the set of display
783     // modes can change in subtle ways.  We're interested in whether two modes
784     // match in their most salient features, even if they aren't identical.
785     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
786     {
787         NSString *encoding1, *encoding2;
788         uint32_t ioflags1, ioflags2, different;
789         double refresh1, refresh2;
791         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
792         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
793         if (CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
794         if (CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
796         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
797         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
798         if (![encoding1 isEqualToString:encoding2]) return FALSE;
800         ioflags1 = CGDisplayModeGetIOFlags(mode1);
801         ioflags2 = CGDisplayModeGetIOFlags(mode2);
802         different = ioflags1 ^ ioflags2;
803         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
804                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
805             return FALSE;
807         refresh1 = CGDisplayModeGetRefreshRate(mode1);
808         if (refresh1 == 0) refresh1 = 60;
809         refresh2 = CGDisplayModeGetRefreshRate(mode2);
810         if (refresh2 == 0) refresh2 = 60;
811         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
813         return TRUE;
814     }
816     - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
817     {
818         NSMutableArray* ret = [NSMutableArray array];
819         NSDictionary* options = @{ (NSString*)kCGDisplayShowDuplicateLowResolutionModes: @YES };
821         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
822         for (id candidateModeObject in modes)
823         {
824             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
825             if ([self mode:candidateMode matchesMode:mode])
826                 [ret addObject:candidateModeObject];
827         }
828         return ret;
829     }
831     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
832     {
833         BOOL ret = FALSE;
834         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
835         CGDisplayModeRef originalMode;
837         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
839         if (originalMode && [self mode:mode matchesMode:originalMode])
840         {
841             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
842             {
843                 CGRestorePermanentDisplayConfiguration();
844                 if (!displaysCapturedForFullscreen)
845                     CGReleaseAllDisplays();
846                 [originalDisplayModes removeAllObjects];
847                 ret = TRUE;
848             }
849             else // ... otherwise, try to restore just the one display
850             {
851                 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
852                 {
853                     mode = (CGDisplayModeRef)modeObject;
854                     if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
855                     {
856                         [originalDisplayModes removeObjectForKey:displayIDKey];
857                         ret = TRUE;
858                         break;
859                     }
860                 }
861             }
862         }
863         else
864         {
865             CGDisplayModeRef currentMode;
866             NSArray* modes;
868             currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
869             if (!currentMode)
870                 currentMode = CGDisplayCopyDisplayMode(displayID);
871             if (!currentMode) // Invalid display ID
872                 return FALSE;
874             if ([self mode:mode matchesMode:currentMode]) // Already there!
875             {
876                 CGDisplayModeRelease(currentMode);
877                 return TRUE;
878             }
880             CGDisplayModeRelease(currentMode);
881             currentMode = NULL;
883             modes = [self modesMatchingMode:mode forDisplay:displayID];
884             if (!modes.count)
885                 return FALSE;
887             [self transformProcessToForeground:YES];
889             BOOL active = [NSApp isActive];
891             if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
892                 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
893             {
894                 if (active)
895                 {
896                     // If we get here, we have the displays captured.  If we don't
897                     // know the original mode of the display, the current mode must
898                     // be the original.  We should re-query the current mode since
899                     // another process could have changed it between when we last
900                     // checked and when we captured the displays.
901                     if (!originalMode)
902                         originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
904                     if (originalMode)
905                     {
906                         for (id modeObject in modes)
907                         {
908                             mode = (CGDisplayModeRef)modeObject;
909                             if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
910                             {
911                                 ret = TRUE;
912                                 break;
913                             }
914                         }
915                     }
916                     if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
917                         [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
918                     else if (![originalDisplayModes count])
919                     {
920                         CGRestorePermanentDisplayConfiguration();
921                         if (!displaysCapturedForFullscreen)
922                             CGReleaseAllDisplays();
923                     }
925                     if (currentMode)
926                         CGDisplayModeRelease(currentMode);
927                 }
928                 else
929                 {
930                     [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
931                     ret = TRUE;
932                 }
933             }
934         }
936         if (ret)
937             [self adjustWindowLevels];
939         return ret;
940     }
942     - (BOOL) areDisplaysCaptured
943     {
944         return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
945     }
947     - (void) updateCursor:(BOOL)force
948     {
949         if (force || lastTargetWindow)
950         {
951             if (clientWantsCursorHidden && !cursorHidden)
952             {
953                 [NSCursor hide];
954                 cursorHidden = TRUE;
955             }
957             if (!cursorIsCurrent)
958             {
959                 [cursor set];
960                 cursorIsCurrent = TRUE;
961             }
963             if (!clientWantsCursorHidden && cursorHidden)
964             {
965                 [NSCursor unhide];
966                 cursorHidden = FALSE;
967             }
968         }
969         else
970         {
971             if (cursorIsCurrent)
972             {
973                 [[NSCursor arrowCursor] set];
974                 cursorIsCurrent = FALSE;
975             }
976             if (cursorHidden)
977             {
978                 [NSCursor unhide];
979                 cursorHidden = FALSE;
980             }
981         }
982     }
984     - (void) hideCursor
985     {
986         if (!clientWantsCursorHidden)
987         {
988             clientWantsCursorHidden = TRUE;
989             [self updateCursor:TRUE];
990         }
991     }
993     - (void) unhideCursor
994     {
995         if (clientWantsCursorHidden)
996         {
997             clientWantsCursorHidden = FALSE;
998             [self updateCursor:FALSE];
999         }
1000     }
1002     - (void) setCursor:(NSCursor*)newCursor
1003     {
1004         if (newCursor != cursor)
1005         {
1006             [cursor release];
1007             cursor = [newCursor retain];
1008             cursorIsCurrent = FALSE;
1009             [self updateCursor:FALSE];
1010         }
1011     }
1013     - (void) setCursor
1014     {
1015         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
1016         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
1017         CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
1018         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
1019         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
1020         CGPoint hotSpot;
1022         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
1023             hotSpot = CGPointZero;
1024         hotSpot = cgpoint_mac_from_win(hotSpot);
1025         self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1026         [image release];
1027         [self unhideCursor];
1028     }
1030     - (void) nextCursorFrame:(NSTimer*)theTimer
1031     {
1032         NSDictionary* frame;
1033         NSTimeInterval duration;
1034         NSDate* date;
1036         cursorFrame++;
1037         if (cursorFrame >= [cursorFrames count])
1038             cursorFrame = 0;
1039         [self setCursor];
1041         frame = [cursorFrames objectAtIndex:cursorFrame];
1042         duration = [[frame objectForKey:@"duration"] doubleValue];
1043         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1044         [cursorTimer setFireDate:date];
1045     }
1047     - (void) setCursorWithFrames:(NSArray*)frames
1048     {
1049         if (self.cursorFrames == frames)
1050             return;
1052         self.cursorFrames = frames;
1053         cursorFrame = 0;
1054         [cursorTimer invalidate];
1055         self.cursorTimer = nil;
1057         if ([frames count])
1058         {
1059             if ([frames count] > 1)
1060             {
1061                 NSDictionary* frame = [frames objectAtIndex:0];
1062                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
1063                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1064                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1065                                                              interval:1000000
1066                                                                target:self
1067                                                              selector:@selector(nextCursorFrame:)
1068                                                              userInfo:nil
1069                                                               repeats:YES] autorelease];
1070                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1071             }
1073             [self setCursor];
1074         }
1075     }
1077     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1078     {
1079         NSImage* nsimage = nil;
1081         if ([images count])
1082         {
1083             NSSize bestSize = NSZeroSize;
1084             id image;
1086             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1088             for (image in images)
1089             {
1090                 CGImageRef cgimage = (CGImageRef)image;
1091                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1092                 if (imageRep)
1093                 {
1094                     NSSize size = [imageRep size];
1096                     [nsimage addRepresentation:imageRep];
1097                     [imageRep release];
1099                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1100                         bestSize = size;
1101                 }
1102             }
1104             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1105                 [nsimage setSize:bestSize];
1106             else
1107                 nsimage = nil;
1108         }
1110         self.applicationIcon = nsimage;
1111     }
1113     - (void) handleCommandTab
1114     {
1115         if ([NSApp isActive])
1116         {
1117             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1118             NSRunningApplication* app;
1119             NSRunningApplication* otherValidApp = nil;
1121             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1122             {
1123                 NSNumber* displayID;
1124                 for (displayID in originalDisplayModes)
1125                 {
1126                     CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1127                     [latentDisplayModes setObject:(id)mode forKey:displayID];
1128                     CGDisplayModeRelease(mode);
1129                 }
1131                 CGRestorePermanentDisplayConfiguration();
1132                 CGReleaseAllDisplays();
1133                 [originalDisplayModes removeAllObjects];
1134                 displaysCapturedForFullscreen = FALSE;
1135             }
1137             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1138             {
1139                 if (![app isEqual:thisApp] && !app.terminated &&
1140                     app.activationPolicy == NSApplicationActivationPolicyRegular)
1141                 {
1142                     if (!app.hidden)
1143                     {
1144                         // There's another visible app.  Just hide ourselves and let
1145                         // the system activate the other app.
1146                         [NSApp hide:self];
1147                         return;
1148                     }
1150                     if (!otherValidApp)
1151                         otherValidApp = app;
1152                 }
1153             }
1155             // Didn't find a visible GUI app.  Try the Finder or, if that's not
1156             // running, the first hidden GUI app.  If even that doesn't work, we
1157             // just fail to switch and remain the active app.
1158             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1159             if (!app) app = otherValidApp;
1160             [app unhide];
1161             [app activateWithOptions:0];
1162         }
1163     }
1165     - (BOOL) setCursorPosition:(CGPoint)pos
1166     {
1167         BOOL ret;
1169         if ([windowsBeingDragged count])
1170             ret = FALSE;
1171         else if (self.clippingCursor && [clipCursorHandler respondsToSelector:@selector(setCursorPosition:)])
1172             ret = [clipCursorHandler setCursorPosition:pos];
1173         else
1174         {
1175             if (self.clippingCursor)
1176                 [clipCursorHandler clipCursorLocation:&pos];
1178             // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1179             // the mouse from the cursor position for 0.25 seconds.  This means
1180             // that mouse movement during that interval doesn't move the cursor
1181             // and events carry a constant location (the warped-to position)
1182             // even though they have delta values.  For apps which warp the
1183             // cursor frequently (like after every mouse move), this makes
1184             // cursor movement horribly laggy and jerky, as only a fraction of
1185             // mouse move events have any effect.
1186             //
1187             // On some versions of OS X, it's sufficient to forcibly reassociate
1188             // the mouse and cursor position.  On others, it's necessary to set
1189             // the local events suppression interval to 0 for the warp.  That's
1190             // deprecated, but I'm not aware of any other way.  For good
1191             // measure, we do both.
1192             CGSetLocalEventsSuppressionInterval(0);
1193             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1194             CGSetLocalEventsSuppressionInterval(0.25);
1195             if (ret)
1196             {
1197                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1199                 CGAssociateMouseAndMouseCursorPosition(true);
1200             }
1201         }
1203         if (ret)
1204         {
1205             WineEventQueue* queue;
1207             // Discard all pending mouse move events.
1208             [eventQueuesLock lock];
1209             for (queue in eventQueues)
1210             {
1211                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED_RELATIVE) |
1212                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1213                                        forWindow:nil];
1214                 [queue resetMouseEventPositions:pos];
1215             }
1216             [eventQueuesLock unlock];
1217         }
1219         return ret;
1220     }
1222     - (void) updateWindowsForCursorClipping
1223     {
1224         WineWindow* window;
1225         for (window in [NSApp windows])
1226         {
1227             if ([window isKindOfClass:[WineWindow class]])
1228                 [window updateForCursorClipping];
1229         }
1230     }
1232     - (BOOL) startClippingCursor:(CGRect)rect
1233     {
1234         if (!clipCursorHandler) {
1235             if (use_confinement_cursor_clipping && [WineConfinementClipCursorHandler isAvailable])
1236                 clipCursorHandler = [[WineConfinementClipCursorHandler alloc] init];
1237             else
1238                 clipCursorHandler = [[WineEventTapClipCursorHandler alloc] init];
1239         }
1241         if (self.clippingCursor && CGRectEqualToRect(rect, clipCursorHandler.cursorClipRect))
1242             return TRUE;
1244         if (![clipCursorHandler startClippingCursor:rect])
1245             return FALSE;
1247         [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1249         [self updateWindowsForCursorClipping];
1251         return TRUE;
1252     }
1254     - (BOOL) stopClippingCursor
1255     {
1256         if (!self.clippingCursor)
1257             return TRUE;
1259         if (![clipCursorHandler stopClippingCursor])
1260             return FALSE;
1262         lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1264         [self updateWindowsForCursorClipping];
1266         return TRUE;
1267     }
1269     - (BOOL) clippingCursor
1270     {
1271         return clipCursorHandler.clippingCursor;
1272     }
1274     - (BOOL) isKeyPressed:(uint16_t)keyCode
1275     {
1276         int bits = sizeof(pressedKeyCodes[0]) * 8;
1277         int index = keyCode / bits;
1278         uint32_t mask = 1 << (keyCode % bits);
1279         return (pressedKeyCodes[index] & mask) != 0;
1280     }
1282     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1283     {
1284         int bits = sizeof(pressedKeyCodes[0]) * 8;
1285         int index = keyCode / bits;
1286         uint32_t mask = 1 << (keyCode % bits);
1287         if (pressed)
1288             pressedKeyCodes[index] |= mask;
1289         else
1290             pressedKeyCodes[index] &= ~mask;
1291     }
1293     - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1294     {
1295         if (dragged)
1296             [windowsBeingDragged addObject:window];
1297         else
1298             [windowsBeingDragged removeObject:window];
1299     }
1301     - (void) windowWillOrderOut:(WineWindow*)window
1302     {
1303         if ([windowsBeingDragged containsObject:window])
1304         {
1305             [self window:window isBeingDragged:NO];
1307             macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1308             [window.queue postEvent:event];
1309             macdrv_release_event(event);
1310         }
1311     }
1313     - (BOOL) isAnyWineWindowVisible
1314     {
1315         for (WineWindow* w in [NSApp windows])
1316         {
1317             if ([w isKindOfClass:[WineWindow class]] && ![w isMiniaturized] && [w isVisible])
1318                 return YES;
1319         }
1321         return NO;
1322     }
1324     - (void) handleWindowDrag:(WineWindow*)window begin:(BOOL)begin
1325     {
1326         macdrv_event* event;
1327         int eventType;
1329         if (begin)
1330         {
1331             [windowsBeingDragged addObject:window];
1332             eventType = WINDOW_DRAG_BEGIN;
1333         }
1334         else
1335         {
1336             [windowsBeingDragged removeObject:window];
1337             eventType = WINDOW_DRAG_END;
1338         }
1340         event = macdrv_create_event(eventType, window);
1341         if (eventType == WINDOW_DRAG_BEGIN)
1342             event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
1343         [window.queue postEvent:event];
1344         macdrv_release_event(event);
1345     }
1347     - (void) handleMouseMove:(NSEvent*)anEvent
1348     {
1349         WineWindow* targetWindow;
1350         BOOL drag = [anEvent type] != NSEventTypeMouseMoved;
1352         if ([windowsBeingDragged count])
1353             targetWindow = nil;
1354         else if (mouseCaptureWindow)
1355             targetWindow = mouseCaptureWindow;
1356         else if (drag)
1357             targetWindow = (WineWindow*)[anEvent window];
1358         else
1359         {
1360             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1361                event indicates its window is the main window, even if the cursor is
1362                over a different window.  Find the actual WineWindow that is under the
1363                cursor and post the event as being for that window. */
1364             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1365             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1366             NSInteger windowUnderNumber;
1368             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1369                                   belowWindowWithWindowNumber:0];
1370             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1371             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1372                 targetWindow = nil;
1373         }
1375         if ([targetWindow isKindOfClass:[WineWindow class]])
1376         {
1377             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1378             macdrv_event* event;
1379             BOOL absolute;
1381             // If we recently warped the cursor (other than in our cursor-clipping
1382             // event tap), discard mouse move events until we see an event which is
1383             // later than that time.
1384             if (lastSetCursorPositionTime)
1385             {
1386                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1387                     return;
1389                 lastSetCursorPositionTime = 0;
1390                 forceNextMouseMoveAbsolute = TRUE;
1391             }
1393             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1394             {
1395                 absolute = TRUE;
1396                 forceNextMouseMoveAbsolute = FALSE;
1397             }
1398             else
1399             {
1400                 // Send absolute move events if the cursor is in the interior of
1401                 // its range.  Only send relative moves if the cursor is pinned to
1402                 // the boundaries of where it can go.  We compute the position
1403                 // that's one additional point in the direction of movement.  If
1404                 // that is outside of the clipping rect or desktop region (the
1405                 // union of the screen frames), then we figure the cursor would
1406                 // have moved outside if it could but it was pinned.
1407                 CGPoint computedPoint = point;
1408                 CGFloat deltaX = [anEvent deltaX];
1409                 CGFloat deltaY = [anEvent deltaY];
1411                 if (deltaX > 0.001)
1412                     computedPoint.x++;
1413                 else if (deltaX < -0.001)
1414                     computedPoint.x--;
1416                 if (deltaY > 0.001)
1417                     computedPoint.y++;
1418                 else if (deltaY < -0.001)
1419                     computedPoint.y--;
1421                 // Assume cursor is pinned for now
1422                 absolute = FALSE;
1423                 if (!self.clippingCursor || CGRectContainsPoint(clipCursorHandler.cursorClipRect, computedPoint))
1424                 {
1425                     const CGRect* rects;
1426                     NSUInteger count, i;
1428                     // Caches screenFrameCGRects if necessary
1429                     [self primaryScreenHeight];
1431                     rects = [screenFrameCGRects bytes];
1432                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1434                     for (i = 0; i < count; i++)
1435                     {
1436                         if (CGRectContainsPoint(rects[i], computedPoint))
1437                         {
1438                             absolute = TRUE;
1439                             break;
1440                         }
1441                     }
1442                 }
1443             }
1445             if (absolute)
1446             {
1447                 if (self.clippingCursor)
1448                     [clipCursorHandler clipCursorLocation:&point];
1449                 point = cgpoint_win_from_mac(point);
1451                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1452                 event->mouse_moved.x = floor(point.x);
1453                 event->mouse_moved.y = floor(point.y);
1455                 mouseMoveDeltaX = 0;
1456                 mouseMoveDeltaY = 0;
1457             }
1458             else
1459             {
1460                 double scale = retina_on ? 2 : 1;
1462                 /* Add event delta to accumulated delta error */
1463                 /* deltaY is already flipped */
1464                 mouseMoveDeltaX += [anEvent deltaX];
1465                 mouseMoveDeltaY += [anEvent deltaY];
1467                 event = macdrv_create_event(MOUSE_MOVED_RELATIVE, targetWindow);
1468                 event->mouse_moved.x = mouseMoveDeltaX * scale;
1469                 event->mouse_moved.y = mouseMoveDeltaY * scale;
1471                 /* Keep the remainder after integer truncation. */
1472                 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1473                 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1474             }
1476             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1477             {
1478                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1479                 event->mouse_moved.drag = drag;
1481                 [targetWindow.queue postEvent:event];
1482             }
1484             macdrv_release_event(event);
1486             lastTargetWindow = targetWindow;
1487         }
1488         else
1489             lastTargetWindow = nil;
1491         [self updateCursor:FALSE];
1492     }
1494     - (void) handleMouseButton:(NSEvent*)theEvent
1495     {
1496         WineWindow* window = (WineWindow*)[theEvent window];
1497         NSEventType type = [theEvent type];
1498         WineWindow* windowBroughtForward = nil;
1499         BOOL process = FALSE;
1501         if ([window isKindOfClass:[WineWindow class]] &&
1502             type == NSEventTypeLeftMouseDown &&
1503             ![theEvent wine_commandKeyDown])
1504         {
1505             NSWindowButton windowButton;
1507             windowBroughtForward = window;
1509             /* Any left-click on our window anyplace other than the close or
1510                minimize buttons will bring it forward. */
1511             for (windowButton = NSWindowCloseButton;
1512                  windowButton <= NSWindowMiniaturizeButton;
1513                  windowButton++)
1514             {
1515                 NSButton* button = [window standardWindowButton:windowButton];
1516                 if (button)
1517                 {
1518                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1519                     if ([button mouse:point inRect:[button bounds]])
1520                     {
1521                         windowBroughtForward = nil;
1522                         break;
1523                     }
1524                 }
1525             }
1526         }
1528         if ([windowsBeingDragged count])
1529             window = nil;
1530         else if (mouseCaptureWindow)
1531             window = mouseCaptureWindow;
1533         if ([window isKindOfClass:[WineWindow class]])
1534         {
1535             BOOL pressed = (type == NSEventTypeLeftMouseDown ||
1536                             type == NSEventTypeRightMouseDown ||
1537                             type == NSEventTypeOtherMouseDown);
1538             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1540             if (self.clippingCursor)
1541                 [clipCursorHandler clipCursorLocation:&pt];
1543             if (pressed)
1544             {
1545                 if (mouseCaptureWindow)
1546                     process = TRUE;
1547                 else
1548                 {
1549                     // Test if the click was in the window's content area.
1550                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1551                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1552                     process = NSMouseInRect(nspoint, contentRect, NO);
1553                     if (process && [window styleMask] & NSWindowStyleMaskResizable)
1554                     {
1555                         // Ignore clicks in the grow box (resize widget).
1556                         HIPoint origin = { 0, 0 };
1557                         HIThemeGrowBoxDrawInfo info = { 0 };
1558                         HIRect bounds;
1559                         OSStatus status;
1561                         info.kind = kHIThemeGrowBoxKindNormal;
1562                         info.direction = kThemeGrowRight | kThemeGrowDown;
1563                         if ([window styleMask] & NSWindowStyleMaskUtilityWindow)
1564                             info.size = kHIThemeGrowBoxSizeSmall;
1565                         else
1566                             info.size = kHIThemeGrowBoxSizeNormal;
1568                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1569                         if (status == noErr)
1570                         {
1571                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1572                                                         NSMinY(contentRect),
1573                                                         bounds.size.width,
1574                                                         bounds.size.height);
1575                             process = !NSMouseInRect(nspoint, growBox, NO);
1576                         }
1577                     }
1578                 }
1579                 if (process)
1580                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1581             }
1582             else
1583             {
1584                 NSEventType downType = type - 1;
1585                 NSUInteger downMask = NSEventMaskFromType(downType);
1586                 process = (unmatchedMouseDowns & downMask) != 0;
1587                 unmatchedMouseDowns &= ~downMask;
1588             }
1590             if (process)
1591             {
1592                 macdrv_event* event;
1594                 pt = cgpoint_win_from_mac(pt);
1596                 event = macdrv_create_event(MOUSE_BUTTON, window);
1597                 event->mouse_button.button = [theEvent buttonNumber];
1598                 event->mouse_button.pressed = pressed;
1599                 event->mouse_button.x = floor(pt.x);
1600                 event->mouse_button.y = floor(pt.y);
1601                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1603                 [window.queue postEvent:event];
1605                 macdrv_release_event(event);
1606             }
1607         }
1609         if (windowBroughtForward)
1610         {
1611             WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1612             NSInteger ancestorNumber = [ancestor windowNumber];
1613             NSInteger ancestorLevel = [ancestor level];
1615             for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1616             {
1617                 NSInteger windowNumber = [windowNumberObject integerValue];
1618                 if (windowNumber == ancestorNumber)
1619                     break;
1620                 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1621                 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1622                     [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1623                 {
1624                     [ancestor postBroughtForwardEvent];
1625                     break;
1626                 }
1627             }
1628             if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noForeground)
1629                 [self windowGotFocus:windowBroughtForward];
1630         }
1632         // Since mouse button events deliver absolute cursor position, the
1633         // accumulating delta from move events is invalidated.  Make sure
1634         // next mouse move event starts over from an absolute baseline.
1635         // Also, it's at least possible that the title bar widgets (e.g. close
1636         // button, etc.) could enter an internal event loop on a mouse down that
1637         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1638         // dragged events and, after that, any notion of the cursor position
1639         // computed from accumulating deltas would be wrong.
1640         forceNextMouseMoveAbsolute = TRUE;
1641     }
1643     - (void) handleScrollWheel:(NSEvent*)theEvent
1644     {
1645         WineWindow* window;
1647         if (mouseCaptureWindow)
1648             window = mouseCaptureWindow;
1649         else
1650             window = (WineWindow*)[theEvent window];
1652         if ([window isKindOfClass:[WineWindow class]])
1653         {
1654             CGEventRef cgevent = [theEvent CGEvent];
1655             CGPoint pt = CGEventGetLocation(cgevent);
1656             BOOL process;
1658             if (self.clippingCursor)
1659                 [clipCursorHandler clipCursorLocation:&pt];
1661             if (mouseCaptureWindow)
1662                 process = TRUE;
1663             else
1664             {
1665                 // Only process the event if it was in the window's content area.
1666                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1667                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1668                 process = NSMouseInRect(nspoint, contentRect, NO);
1669             }
1671             if (process)
1672             {
1673                 macdrv_event* event;
1674                 double x, y;
1675                 BOOL continuous = FALSE;
1677                 pt = cgpoint_win_from_mac(pt);
1679                 event = macdrv_create_event(MOUSE_SCROLL, window);
1680                 event->mouse_scroll.x = floor(pt.x);
1681                 event->mouse_scroll.y = floor(pt.y);
1682                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1684                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1685                 {
1686                     continuous = TRUE;
1688                     /* Continuous scroll wheel events come from high-precision scrolling
1689                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1690                        For these, we can get more precise data from the CGEvent API. */
1691                     /* Axis 1 is vertical, axis 2 is horizontal. */
1692                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1693                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1694                 }
1695                 else
1696                 {
1697                     double pixelsPerLine = 10;
1698                     CGEventSourceRef source;
1700                     /* The non-continuous values are in units of "lines", not pixels. */
1701                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1702                     {
1703                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1704                         CFRelease(source);
1705                     }
1707                     x = pixelsPerLine * [theEvent deltaX];
1708                     y = pixelsPerLine * [theEvent deltaY];
1709                 }
1711                 /* Mac: negative is right or down, positive is left or up.
1712                    Win32: negative is left or down, positive is right or up.
1713                    So, negate the X scroll value to translate. */
1714                 x = -x;
1716                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1717                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1718                    6 times the pixel value. */
1719                 x *= 6;
1720                 y *= 6;
1722                 if (use_precise_scrolling)
1723                 {
1724                     event->mouse_scroll.x_scroll = x;
1725                     event->mouse_scroll.y_scroll = y;
1727                     if (!continuous)
1728                     {
1729                         /* For non-continuous "clicky" wheels, if there was any motion, make
1730                            sure there was at least WHEEL_DELTA motion.  This is so, at slow
1731                            speeds where the system's acceleration curve is actually reducing the
1732                            scroll distance, the user is sure to get some action out of each click.
1733                            For example, this is important for rotating though weapons in a
1734                            first-person shooter. */
1735                         if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1736                             event->mouse_scroll.x_scroll = 120;
1737                         else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1738                             event->mouse_scroll.x_scroll = -120;
1740                         if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1741                             event->mouse_scroll.y_scroll = 120;
1742                         else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1743                             event->mouse_scroll.y_scroll = -120;
1744                     }
1745                 }
1746                 else
1747                 {
1748                     /* If it's been a while since the last scroll event or if the scrolling has
1749                        reversed direction, reset the accumulated scroll value. */
1750                     if ([theEvent timestamp] - lastScrollTime > 1)
1751                         accumScrollX = accumScrollY = 0;
1752                     else
1753                     {
1754                         /* The accumulated scroll value is in the opposite direction/sign of the last
1755                            scroll.  That's because it's the "debt" resulting from over-scrolling in
1756                            that direction.  We accumulate by adding in the scroll amount and then, if
1757                            it has the same sign as the scroll value, we subtract any whole or partial
1758                            WHEEL_DELTAs, leaving it 0 or the opposite sign.  So, the user switched
1759                            scroll direction if the accumulated debt and the new scroll value have the
1760                            same sign. */
1761                         if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1762                             accumScrollX = 0;
1763                         if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1764                             accumScrollY = 0;
1765                     }
1766                     lastScrollTime = [theEvent timestamp];
1768                     accumScrollX += x;
1769                     accumScrollY += y;
1771                     if (accumScrollX > 0 && x > 0)
1772                         event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1773                     if (accumScrollX < 0 && x < 0)
1774                         event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1775                     if (accumScrollY > 0 && y > 0)
1776                         event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1777                     if (accumScrollY < 0 && y < 0)
1778                         event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1780                     accumScrollX -= event->mouse_scroll.x_scroll;
1781                     accumScrollY -= event->mouse_scroll.y_scroll;
1782                 }
1784                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1785                     [window.queue postEvent:event];
1787                 macdrv_release_event(event);
1789                 // Since scroll wheel events deliver absolute cursor position, the
1790                 // accumulating delta from move events is invalidated.  Make sure next
1791                 // mouse move event starts over from an absolute baseline.
1792                 forceNextMouseMoveAbsolute = TRUE;
1793             }
1794         }
1795     }
1797     // Returns TRUE if the event was handled and caller should do nothing more
1798     // with it.  Returns FALSE if the caller should process it as normal and
1799     // then call -didSendEvent:.
1800     - (BOOL) handleEvent:(NSEvent*)anEvent
1801     {
1802         BOOL ret = FALSE;
1803         NSEventType type = [anEvent type];
1805         if (type == NSEventTypeFlagsChanged)
1806             self.lastFlagsChanged = anEvent;
1807         else if (type == NSEventTypeMouseMoved || type == NSEventTypeLeftMouseDragged ||
1808                  type == NSEventTypeRightMouseDragged || type == NSEventTypeOtherMouseDragged)
1809         {
1810             [self handleMouseMove:anEvent];
1811             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1812         }
1813         else if (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp ||
1814                  type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp ||
1815                  type == NSEventTypeOtherMouseDown || type == NSEventTypeOtherMouseUp)
1816         {
1817             [self handleMouseButton:anEvent];
1818             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1819         }
1820         else if (type == NSEventTypeScrollWheel)
1821         {
1822             [self handleScrollWheel:anEvent];
1823             ret = mouseCaptureWindow != nil;
1824         }
1825         else if (type == NSEventTypeKeyDown)
1826         {
1827             // -[NSApplication sendEvent:] seems to consume presses of the Help
1828             // key (Insert key on PC keyboards), so we have to bypass it and
1829             // send the event directly to the window.
1830             if (anEvent.keyCode == kVK_Help)
1831             {
1832                 [anEvent.window sendEvent:anEvent];
1833                 ret = TRUE;
1834             }
1835         }
1836         else if (type == NSEventTypeKeyUp)
1837         {
1838             uint16_t keyCode = [anEvent keyCode];
1839             if ([self isKeyPressed:keyCode])
1840             {
1841                 WineWindow* window = (WineWindow*)[anEvent window];
1842                 [self noteKey:keyCode pressed:FALSE];
1843                 if ([window isKindOfClass:[WineWindow class]])
1844                     [window postKeyEvent:anEvent];
1845             }
1846         }
1847         else if (!useDragNotifications && type == NSEventTypeAppKitDefined)
1848         {
1849             WineWindow *window = (WineWindow *)[anEvent window];
1850             short subtype = [anEvent subtype];
1852             // These subtypes are not documented but they appear to mean
1853             // "a window is being dragged" and "a window is no longer being
1854             // dragged", respectively.
1855             if ((subtype == 20 || subtype == 21) && [window isKindOfClass:[WineWindow class]])
1856                 [self handleWindowDrag:window begin:(subtype == 20)];
1857         }
1859         return ret;
1860     }
1862     - (void) didSendEvent:(NSEvent*)anEvent
1863     {
1864         NSEventType type = [anEvent type];
1866         if (type == NSEventTypeKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1867         {
1868             NSUInteger modifiers = [anEvent modifierFlags];
1869             if ((modifiers & NSEventModifierFlagCommand) &&
1870                 !(modifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)))
1871             {
1872                 // Command-Tab and Command-Shift-Tab would normally be intercepted
1873                 // by the system to switch applications.  If we're seeing it, it's
1874                 // presumably because we've captured the displays, preventing
1875                 // normal application switching.  Do it manually.
1876                 [self handleCommandTab];
1877             }
1878         }
1879     }
1881     - (void) setupObservations
1882     {
1883         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1884         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1885         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
1887         [nc addObserverForName:NSWindowDidBecomeKeyNotification
1888                         object:nil
1889                          queue:nil
1890                     usingBlock:^(NSNotification *note){
1891             NSWindow* window = [note object];
1892             [keyWindows removeObjectIdenticalTo:window];
1893             [keyWindows insertObject:window atIndex:0];
1894         }];
1896         [nc addObserverForName:NSWindowWillCloseNotification
1897                         object:nil
1898                          queue:[NSOperationQueue mainQueue]
1899                     usingBlock:^(NSNotification *note){
1900             NSWindow* window = [note object];
1901             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
1902                 return;
1903             [keyWindows removeObjectIdenticalTo:window];
1904             if (window == lastTargetWindow)
1905                 lastTargetWindow = nil;
1906             if (window == self.mouseCaptureWindow)
1907                 self.mouseCaptureWindow = nil;
1908             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
1909             {
1910                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
1911                     [self updateFullscreenWindows];
1912                 });
1913             }
1914             [windowsBeingDragged removeObject:window];
1915         }];
1917         if (useDragNotifications) {
1918             [nc addObserverForName:NSWindowWillStartDraggingNotification
1919                             object:nil
1920                              queue:[NSOperationQueue mainQueue]
1921                         usingBlock:^(NSNotification *note){
1922                 NSWindow* window = [note object];
1923                 if ([window isKindOfClass:[WineWindow class]])
1924                     [self handleWindowDrag:(WineWindow *)window begin:YES];
1925             }];
1927             [nc addObserverForName:NSWindowDidEndDraggingNotification
1928                             object:nil
1929                              queue:[NSOperationQueue mainQueue]
1930                         usingBlock:^(NSNotification *note){
1931                 NSWindow* window = [note object];
1932                 if ([window isKindOfClass:[WineWindow class]])
1933                     [self handleWindowDrag:(WineWindow *)window begin:NO];
1934             }];
1935         }
1937         [nc addObserver:self
1938                selector:@selector(keyboardSelectionDidChange)
1939                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
1940                  object:nil];
1942         /* The above notification isn't sent unless the NSTextInputContext
1943            class has initialized itself.  Poke it. */
1944         [NSTextInputContext self];
1946         [wsnc addObserver:self
1947                  selector:@selector(activeSpaceDidChange)
1948                      name:NSWorkspaceActiveSpaceDidChangeNotification
1949                    object:nil];
1951         [nc addObserver:self
1952                selector:@selector(releaseMouseCapture)
1953                    name:NSMenuDidBeginTrackingNotification
1954                  object:nil];
1956         [dnc        addObserver:self
1957                        selector:@selector(releaseMouseCapture)
1958                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
1959                          object:nil
1960              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
1962         [dnc addObserver:self
1963                 selector:@selector(enabledKeyboardInputSourcesChanged)
1964                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
1965                   object:nil];
1967         if ([NSApplication instancesRespondToSelector:@selector(yieldActivationToApplication:)])
1968         {
1969             /* App activation cooperation, starting in macOS 14 Sonoma. */
1970             [dnc addObserver:self
1971                     selector:@selector(otherWineAppWillActivate:)
1972                         name:WineAppWillActivateNotification
1973                       object:nil
1974           suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
1975         }
1976     }
1978     - (void) otherWineAppWillActivate:(NSNotification *)note
1979     {
1980         NSProcessInfo *ourProcess;
1981         pid_t otherPID;
1982         NSString *ourConfigDir, *otherConfigDir, *ourPrefix, *otherPrefix;
1983         NSRunningApplication *otherApp;
1985         /* No point in yielding if we're not the foreground app. */
1986         if (![NSApp isActive]) return;
1988         /* Ignore requests from ourself, dead processes, and other prefixes. */
1989         ourProcess = [NSProcessInfo processInfo];
1990         otherPID = [note.userInfo[WineActivatingAppPIDKey] integerValue];
1991         if (otherPID == ourProcess.processIdentifier) return;
1993         otherApp = [NSRunningApplication runningApplicationWithProcessIdentifier:otherPID];
1994         if (!otherApp) return;
1996         ourConfigDir = ourProcess.environment[@"WINECONFIGDIR"];
1997         otherConfigDir = note.userInfo[WineActivatingAppConfigDirKey];
1998         if (ourConfigDir.length && otherConfigDir.length &&
1999             ![ourConfigDir isEqualToString:otherConfigDir])
2000         {
2001             return;
2002         }
2004         ourPrefix = ourProcess.environment[@"WINEPREFIX"];
2005         otherPrefix = note.userInfo[WineActivatingAppPrefixKey];
2006         if (ourPrefix.length && otherPrefix.length &&
2007             ![ourPrefix isEqualToString:otherPrefix])
2008         {
2009             return;
2010         }
2012         /* There's a race condition here. The requesting app sends out
2013            WineAppWillActivateNotification and then activates itself, but since
2014            distributed notifications are asynchronous, we may not have yielded
2015            in time. So we call activateFromApplication: on the other app here,
2016            which will work around that race if it happened. If we didn't hit the
2017            race, the activateFromApplication: call will be a no-op. */
2019         /* We only add this observer if NSApplication responds to the yield
2020            methods, so they're safe to call without checking here. */
2021         [NSApp yieldActivationToApplication:otherApp];
2022         [otherApp activateFromApplication:[NSRunningApplication currentApplication]
2023                                   options:0];
2024     }
2026     - (void) tryToActivateIgnoringOtherApps:(BOOL)ignore
2027     {
2028         NSProcessInfo *processInfo;
2029         NSString *configDir, *prefix;
2030         NSDictionary *userInfo;
2032         if ([NSApp isActive]) return;  /* Nothing to do. */
2034         if (!ignore ||
2035             ![NSApplication instancesRespondToSelector:@selector(yieldActivationToApplication:)])
2036         {
2037             /* Either we don't need to force activation, or the OS is old enough
2038                that this is our only option. */
2039             [NSApp activateIgnoringOtherApps:ignore];
2040             return;
2041         }
2043         /* Ask other Wine apps to yield activation to us. */
2044         processInfo = [NSProcessInfo processInfo];
2045         configDir = processInfo.environment[@"WINECONFIGDIR"];
2046         prefix = processInfo.environment[@"WINEPREFIX"];
2047         userInfo = @{
2048             WineActivatingAppPIDKey: @(processInfo.processIdentifier),
2049             WineActivatingAppPrefixKey: prefix ? prefix : @"",
2050             WineActivatingAppConfigDirKey: configDir ? configDir : @""
2051         };
2053         [[NSDistributedNotificationCenter defaultCenter]
2054             postNotificationName:WineAppWillActivateNotification
2055                           object:nil
2056                         userInfo:userInfo
2057               deliverImmediately:YES];
2059         /* This is racy. See the note in otherWineAppWillActivate:. */
2060         [NSApp activate];
2061      }
2063     - (BOOL) inputSourceIsInputMethod
2064     {
2065         if (!inputSourceIsInputMethodValid)
2066         {
2067             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2068             if (inputSource)
2069             {
2070                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2071                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2072                 CFRelease(inputSource);
2073             }
2074             else
2075                 inputSourceIsInputMethod = FALSE;
2076             inputSourceIsInputMethodValid = TRUE;
2077         }
2079         return inputSourceIsInputMethod;
2080     }
2082     - (void) releaseMouseCapture
2083     {
2084         // This might be invoked on a background thread by the distributed
2085         // notification center.  Shunt it to the main thread.
2086         if (![NSThread isMainThread])
2087         {
2088             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2089             return;
2090         }
2092         if (mouseCaptureWindow)
2093         {
2094             macdrv_event* event;
2096             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2097             [mouseCaptureWindow.queue postEvent:event];
2098             macdrv_release_event(event);
2099         }
2100     }
2102     - (void) unminimizeWindowIfNoneVisible
2103     {
2104         if (![self frontWineWindow])
2105         {
2106             for (WineWindow* window in [NSApp windows])
2107             {
2108                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2109                 {
2110                     [window deminiaturize:self];
2111                     break;
2112                 }
2113             }
2114         }
2115     }
2117     - (void) setRetinaMode:(int)mode
2118     {
2119         retina_on = mode;
2121         [clipCursorHandler setRetinaMode:mode];
2123         for (WineWindow* window in [NSApp windows])
2124         {
2125             if ([window isKindOfClass:[WineWindow class]])
2126                 [window setRetinaMode:mode];
2127         }
2128     }
2131     /*
2132      * ---------- NSApplicationDelegate methods ----------
2133      */
2134     - (void)applicationDidBecomeActive:(NSNotification *)notification
2135     {
2136         NSNumber* displayID;
2137         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2139         latentDisplayModes = [[NSMutableDictionary alloc] init];
2140         for (displayID in modesToRealize)
2141         {
2142             CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2143             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2144         }
2146         [self updateFullscreenWindows];
2147         [self adjustWindowLevels:YES];
2149         if (beenActive)
2150             [self unminimizeWindowIfNoneVisible];
2151         beenActive = TRUE;
2153         // If a Wine process terminates abruptly while it has the display captured
2154         // and switched to a different resolution, Mac OS X will uncapture the
2155         // displays and switch their resolutions back.  However, the other Wine
2156         // processes won't have their notion of the desktop rect changed back.
2157         // This can lead them to refuse to draw or acknowledge clicks in certain
2158         // portions of their windows.
2159         //
2160         // To solve this, we synthesize a displays-changed event whenever we're
2161         // activated.  This will provoke a re-synchronization of Wine's notion of
2162         // the desktop rect with the actual state.
2163         [self sendDisplaysChanged:TRUE];
2165         // The cursor probably moved while we were inactive.  Accumulated mouse
2166         // movement deltas are invalidated.  Make sure the next mouse move event
2167         // starts over from an absolute baseline.
2168         forceNextMouseMoveAbsolute = TRUE;
2169     }
2171     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2172     {
2173         primaryScreenHeightValid = FALSE;
2174         [self sendDisplaysChanged:FALSE];
2175         [self adjustWindowLevels];
2177         // When the display configuration changes, the cursor position may jump.
2178         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2179         // mouse move event starts over from an absolute baseline.
2180         forceNextMouseMoveAbsolute = TRUE;
2181     }
2183     - (void)applicationDidResignActive:(NSNotification *)notification
2184     {
2185         macdrv_event* event;
2186         WineEventQueue* queue;
2188         [self invalidateGotFocusEvents];
2190         event = macdrv_create_event(APP_DEACTIVATED, nil);
2192         [eventQueuesLock lock];
2193         for (queue in eventQueues)
2194             [queue postEvent:event];
2195         [eventQueuesLock unlock];
2197         macdrv_release_event(event);
2199         [self releaseMouseCapture];
2200     }
2202     - (void) applicationDidUnhide:(NSNotification*)aNotification
2203     {
2204         [self adjustWindowLevels];
2205     }
2207     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2208     {
2209         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2210         // don't count as "visible windows" for this purpose.
2211         [self unminimizeWindowIfNoneVisible];
2212         return YES;
2213     }
2215     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2216     {
2217         NSApplicationTerminateReply ret = NSTerminateNow;
2218         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2219         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2220         macdrv_event* event;
2221         WineEventQueue* queue;
2223         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2224         event->deliver = 1;
2225         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2226         {
2227             case kAELogOut:
2228             case kAEReallyLogOut:
2229                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2230                 break;
2231             case kAEShowRestartDialog:
2232                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2233                 break;
2234             case kAEShowShutdownDialog:
2235                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2236                 break;
2237             default:
2238                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2239                 break;
2240         }
2242         [eventQueuesLock lock];
2244         if ([eventQueues count])
2245         {
2246             for (queue in eventQueues)
2247                 [queue postEvent:event];
2248             ret = NSTerminateLater;
2249         }
2251         [eventQueuesLock unlock];
2253         macdrv_release_event(event);
2255         return ret;
2256     }
2258     - (void)applicationWillBecomeActive:(NSNotification *)notification
2259     {
2260         macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2261         event->deliver = 1;
2263         [eventQueuesLock lock];
2264         for (WineEventQueue* queue in eventQueues)
2265             [queue postEvent:event];
2266         [eventQueuesLock unlock];
2268         macdrv_release_event(event);
2269     }
2271     - (void)applicationWillResignActive:(NSNotification *)notification
2272     {
2273         [self adjustWindowLevels:NO];
2274     }
2276 /***********************************************************************
2277  *              PerformRequest
2279  * Run-loop-source perform callback.  Pull request blocks from the
2280  * array of queued requests and invoke them.
2281  */
2282 static void PerformRequest(void *info)
2284     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2285     WineApplicationController* controller = [WineApplicationController sharedController];
2287     for (;;)
2288     {
2289         __block dispatch_block_t block;
2291         dispatch_sync(controller->requestsManipQueue, ^{
2292             if ([controller->requests count])
2293             {
2294                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2295                 [controller->requests removeObjectAtIndex:0];
2296             }
2297             else
2298                 block = nil;
2299         });
2301         if (!block)
2302             break;
2304         block();
2305         [block release];
2307         [pool release];
2308         pool = [[NSAutoreleasePool alloc] init];
2309     }
2311     [pool release];
2314 /***********************************************************************
2315  *              OnMainThreadAsync
2317  * Run a block on the main thread asynchronously.
2318  */
2319 void OnMainThreadAsync(dispatch_block_t block)
2321     WineApplicationController* controller = [WineApplicationController sharedController];
2323     block = [block copy];
2324     dispatch_sync(controller->requestsManipQueue, ^{
2325         [controller->requests addObject:block];
2326     });
2327     [block release];
2328     CFRunLoopSourceSignal(controller->requestSource);
2329     CFRunLoopWakeUp(CFRunLoopGetMain());
2332 @end
2334 /***********************************************************************
2335  *              LogError
2336  */
2337 void LogError(const char* func, NSString* format, ...)
2339     va_list args;
2340     va_start(args, format);
2341     LogErrorv(func, format, args);
2342     va_end(args);
2345 /***********************************************************************
2346  *              LogErrorv
2347  */
2348 void LogErrorv(const char* func, NSString* format, va_list args)
2350     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2352     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2353     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2354     [message release];
2356     [pool release];
2359 /***********************************************************************
2360  *              macdrv_window_rejected_focus
2362  * Pass focus to the next window that hasn't already rejected this same
2363  * WINDOW_GOT_FOCUS event.
2364  */
2365 void macdrv_window_rejected_focus(const macdrv_event *event)
2367     OnMainThread(^{
2368         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2369     });
2372 /***********************************************************************
2373  *              macdrv_get_input_source_info
2375  * Returns the keyboard layout uchr data, keyboard type and input source.
2376  */
2377 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2379     OnMainThread(^{
2380         TISInputSourceRef inputSourceLayout;
2382         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2383         if (inputSourceLayout)
2384         {
2385             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2386                                 kTISPropertyUnicodeKeyLayoutData);
2387             *uchr = CFDataCreateCopy(NULL, data);
2388             CFRelease(inputSourceLayout);
2390             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2391             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2392             if (input_source)
2393                 *input_source = TISCopyCurrentKeyboardInputSource();
2394         }
2395     });
2398 /***********************************************************************
2399  *              macdrv_beep
2401  * Play the beep sound configured by the user in System Preferences.
2402  */
2403 void macdrv_beep(void)
2405     OnMainThreadAsync(^{
2406         NSBeep();
2407     });
2410 /***********************************************************************
2411  *              macdrv_set_display_mode
2412  */
2413 int macdrv_set_display_mode(const struct macdrv_display* display,
2414                             CGDisplayModeRef display_mode)
2416     __block int ret;
2418     OnMainThread(^{
2419         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2420     });
2422     return ret;
2425 /***********************************************************************
2426  *              macdrv_set_cursor
2428  * Set the cursor.
2430  * If name is non-NULL, it is a selector for a class method on NSCursor
2431  * identifying the cursor to set.  In that case, frames is ignored.  If
2432  * name is NULL, then frames is used.
2434  * frames is an array of dictionaries.  Each dictionary is a frame of
2435  * an animated cursor.  Under the key "image" is a CGImage for the
2436  * frame.  Under the key "duration" is a CFNumber time interval, in
2437  * seconds, for how long that frame is presented before proceeding to
2438  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2439  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2440  * This is the hot spot, measured in pixels down and to the right of the
2441  * top-left corner of the image.
2443  * If the array has exactly 1 element, the cursor is static, not
2444  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2445  */
2446 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2448     SEL sel;
2450     sel = NSSelectorFromString((NSString*)name);
2451     if (sel)
2452     {
2453         OnMainThreadAsync(^{
2454             WineApplicationController* controller = [WineApplicationController sharedController];
2455             [controller setCursorWithFrames:nil];
2456             controller.cursor = [NSCursor performSelector:sel];
2457             [controller unhideCursor];
2458         });
2459     }
2460     else
2461     {
2462         NSArray* nsframes = (NSArray*)frames;
2463         if ([nsframes count])
2464         {
2465             OnMainThreadAsync(^{
2466                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2467             });
2468         }
2469         else
2470         {
2471             OnMainThreadAsync(^{
2472                 WineApplicationController* controller = [WineApplicationController sharedController];
2473                 [controller setCursorWithFrames:nil];
2474                 [controller hideCursor];
2475             });
2476         }
2477     }
2480 /***********************************************************************
2481  *              macdrv_get_cursor_position
2483  * Obtains the current cursor position.  Returns zero on failure,
2484  * non-zero on success.
2485  */
2486 int macdrv_get_cursor_position(CGPoint *pos)
2488     OnMainThread(^{
2489         NSPoint location = [NSEvent mouseLocation];
2490         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2491         *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2492     });
2494     return TRUE;
2497 /***********************************************************************
2498  *              macdrv_set_cursor_position
2500  * Sets the cursor position without generating events.  Returns zero on
2501  * failure, non-zero on success.
2502  */
2503 int macdrv_set_cursor_position(CGPoint pos)
2505     __block int ret;
2507     OnMainThread(^{
2508         ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2509     });
2511     return ret;
2514 /***********************************************************************
2515  *              macdrv_clip_cursor
2517  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2518  * to or larger than the whole desktop region, the cursor is unclipped.
2519  * Returns zero on failure, non-zero on success.
2520  */
2521 int macdrv_clip_cursor(CGRect r)
2523     __block int ret;
2525     OnMainThread(^{
2526         WineApplicationController* controller = [WineApplicationController sharedController];
2527         BOOL clipping = FALSE;
2528         CGRect rect = r;
2530         if (!CGRectIsInfinite(rect))
2531             rect = cgrect_mac_from_win(rect);
2533         if (!CGRectIsInfinite(rect))
2534         {
2535             NSRect nsrect = NSRectFromCGRect(rect);
2536             NSScreen* screen;
2538             /* Convert the rectangle from top-down coords to bottom-up. */
2539             [controller flipRect:&nsrect];
2541             clipping = FALSE;
2542             for (screen in [NSScreen screens])
2543             {
2544                 if (!NSContainsRect(nsrect, [screen frame]))
2545                 {
2546                     clipping = TRUE;
2547                     break;
2548                 }
2549             }
2550         }
2552         if (clipping)
2553             ret = [controller startClippingCursor:rect];
2554         else
2555             ret = [controller stopClippingCursor];
2556     });
2558     return ret;
2561 /***********************************************************************
2562  *              macdrv_set_application_icon
2564  * Set the application icon.  The images array contains CGImages.  If
2565  * there are more than one, then they represent different sizes or
2566  * color depths from the icon resource.  If images is NULL or empty,
2567  * restores the default application image.
2568  */
2569 void macdrv_set_application_icon(CFArrayRef images)
2571     NSArray* imageArray = (NSArray*)images;
2573     OnMainThreadAsync(^{
2574         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2575     });
2578 /***********************************************************************
2579  *              macdrv_quit_reply
2580  */
2581 void macdrv_quit_reply(int reply)
2583     OnMainThread(^{
2584         [NSApp replyToApplicationShouldTerminate:reply];
2585     });
2588 /***********************************************************************
2589  *              macdrv_using_input_method
2590  */
2591 int macdrv_using_input_method(void)
2593     __block BOOL ret;
2595     OnMainThread(^{
2596         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2597     });
2599     return ret;
2602 /***********************************************************************
2603  *              macdrv_set_mouse_capture_window
2604  */
2605 void macdrv_set_mouse_capture_window(macdrv_window window)
2607     WineWindow* w = (WineWindow*)window;
2609     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2611     OnMainThread(^{
2612         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2613     });
2616 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2617 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2618 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2620 /***********************************************************************
2621  *              macdrv_create_input_source_list
2622  */
2623 CFArrayRef macdrv_create_input_source_list(void)
2625     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2627     OnMainThread(^{
2628         CFArrayRef input_list;
2629         CFDictionaryRef filter_dict;
2630         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2631         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2632         int i;
2634         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2635                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2636         input_list = TISCreateInputSourceList(filter_dict, false);
2638         for (i = 0; i < CFArrayGetCount(input_list); i++)
2639         {
2640             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2641             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2642             CFDictionaryRef entry;
2643             const void *input_keys[3] = { macdrv_input_source_input_key,
2644                                           macdrv_input_source_type_key,
2645                                           macdrv_input_source_lang_key };
2646             const void *input_values[3];
2648             input_values[0] = input;
2649             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2650             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2652             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2653                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2655             CFArrayAppendValue(ret, entry);
2656             CFRelease(entry);
2657         }
2658         CFRelease(input_list);
2659         CFRelease(filter_dict);
2660     });
2662     return ret;
2665 int macdrv_select_input_source(TISInputSourceRef input_source)
2667     __block int ret = FALSE;
2669     OnMainThread(^{
2670         ret = (TISSelectInputSource(input_source) == noErr);
2671     });
2673     return ret;
2676 void macdrv_set_cocoa_retina_mode(int new_mode)
2678     OnMainThread(^{
2679         [[WineApplicationController sharedController] setRetinaMode:new_mode];
2680     });
2683 int macdrv_is_any_wine_window_visible(void)
2685     __block int ret = FALSE;
2687     OnMainThread(^{
2688         ret = [[WineApplicationController sharedController] isAnyWineWindowVisible];
2689     });
2691     return ret;