dmime: Include dmobject.h in dmime_private.h.
[wine.git] / dlls / winemac.drv / cocoa_app.m
blob881f0a50e021a7761f47065beaf70285e8e10b3f
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     return ((NSDictionary*)localized_strings)[@(stringID)];
85 @implementation WineApplication
87 @synthesize wineController;
89     - (void) sendEvent:(NSEvent*)anEvent
90     {
91         if (![wineController handleEvent:anEvent])
92         {
93             [super sendEvent:anEvent];
94             [wineController didSendEvent:anEvent];
95         }
96     }
98     - (void) setWineController:(WineApplicationController*)newController
99     {
100         wineController = newController;
101         [self setDelegate:wineController];
102     }
104 @end
107 @interface WineApplicationController ()
109 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
110 @property (copy, nonatomic) NSArray* cursorFrames;
111 @property (retain, nonatomic) NSTimer* cursorTimer;
112 @property (retain, nonatomic) NSCursor* cursor;
113 @property (retain, nonatomic) NSImage* applicationIcon;
114 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
115 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
117     - (void) setupObservations;
118     - (void) applicationDidBecomeActive:(NSNotification *)notification;
120     static void PerformRequest(void *info);
122 @end
125 @implementation WineApplicationController
127     @synthesize keyboardType, lastFlagsChanged;
128     @synthesize applicationIcon;
129     @synthesize cursorFrames, cursorTimer, cursor;
130     @synthesize mouseCaptureWindow;
131     @synthesize lastSetCursorPositionTime;
133     + (void) initialize
134     {
135         if (self == [WineApplicationController class])
136         {
137             NSDictionary<NSString *, id> *defaults =
138             @{
139                 @"NSQuotedKeystrokeBinding" : @"",
140                     @"NSRepeatCountBinding" : @"",
141                 @"ApplePressAndHoldEnabled" : @NO
142             };
144             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
146             if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
147                 [NSWindow setAllowsAutomaticWindowTabbing:NO];
148         }
149     }
151     + (WineApplicationController*) sharedController
152     {
153         static WineApplicationController* sharedController;
154         static dispatch_once_t once;
156         dispatch_once(&once, ^{
157             sharedController = [[self alloc] init];
158         });
160         return sharedController;
161     }
163     - (id) init
164     {
165         self = [super init];
166         if (self != nil)
167         {
168             CFRunLoopSourceContext context = { 0 };
169             context.perform = PerformRequest;
170             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
171             if (!requestSource)
172             {
173                 [self release];
174                 return nil;
175             }
176             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
177             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
179             requests =  [[NSMutableArray alloc] init];
180             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
182             eventQueues = [[NSMutableArray alloc] init];
183             eventQueuesLock = [[NSLock alloc] init];
185             keyWindows = [[NSMutableArray alloc] init];
187             originalDisplayModes = [[NSMutableDictionary alloc] init];
188             latentDisplayModes = [[NSMutableDictionary alloc] init];
190             windowsBeingDragged = [[NSMutableSet alloc] init];
192             // On macOS 10.12+, use notifications to more reliably detect when windows are being dragged.
193             if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)])
194             {
195                 NSOperatingSystemVersion requiredVersion = { 10, 12, 0 };
196                 useDragNotifications = [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:requiredVersion];
197             }
198             else
199                 useDragNotifications = NO;
201             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
202                 !keyWindows || !originalDisplayModes || !latentDisplayModes)
203             {
204                 [self release];
205                 return nil;
206             }
208             [self setupObservations];
210             keyboardType = LMGetKbdType();
212             if ([NSApp isActive])
213                 [self applicationDidBecomeActive:nil];
214         }
215         return self;
216     }
218     - (void) dealloc
219     {
220         [windowsBeingDragged release];
221         [cursor release];
222         [screenFrameCGRects release];
223         [applicationIcon release];
224         [clipCursorHandler release];
225         [cursorTimer release];
226         [cursorFrames release];
227         [latentDisplayModes release];
228         [originalDisplayModes release];
229         [keyWindows release];
230         [eventQueues release];
231         [eventQueuesLock release];
232         if (requestsManipQueue) dispatch_release(requestsManipQueue);
233         [requests release];
234         if (requestSource)
235         {
236             CFRunLoopSourceInvalidate(requestSource);
237             CFRelease(requestSource);
238         }
239         [super dealloc];
240     }
242     - (void) transformProcessToForeground:(BOOL)activateIfTransformed
243     {
244         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
245         {
246             NSMenu* mainMenu;
247             NSMenu* submenu;
248             NSString* bundleName;
249             NSString* title;
250             NSMenuItem* item;
252             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
254             if (activateIfTransformed)
255                 [self tryToActivateIgnoringOtherApps:YES];
257 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
258             if (!enable_app_nap && [NSProcessInfo instancesRespondToSelector:@selector(beginActivityWithOptions:reason:)])
259             {
260                 [[[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep
261                                                                 reason:@"Running Windows program"] retain]; // intentional leak
262             }
263 #endif
265             mainMenu = [[[NSMenu alloc] init] autorelease];
267             // Application menu
268             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
269             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
271             if ([bundleName length])
272                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
273             else
274                 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
275             item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
277             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
278                                       action:@selector(hideOtherApplications:)
279                                keyEquivalent:@"h"];
280             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
282             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
283                                       action:@selector(unhideAllApplications:)
284                                keyEquivalent:@""];
286             [submenu addItem:[NSMenuItem separatorItem]];
288             if ([bundleName length])
289                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
290             else
291                 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
292             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
293             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
294             item = [[[NSMenuItem alloc] init] autorelease];
295             [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
296             [item setSubmenu:submenu];
297             [mainMenu addItem:item];
299             // Window menu
300             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
301             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
302                                action:@selector(performMiniaturize:)
303                         keyEquivalent:@""];
304             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
305                                action:@selector(performZoom:)
306                         keyEquivalent:@""];
307             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
308                                       action:@selector(toggleFullScreen:)
309                                keyEquivalent:@"f"];
310             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand |
311                                                NSEventModifierFlagOption |
312                                                NSEventModifierFlagControl];
313             [submenu addItem:[NSMenuItem separatorItem]];
314             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
315                                action:@selector(arrangeInFront:)
316                         keyEquivalent:@""];
317             item = [[[NSMenuItem alloc] init] autorelease];
318             [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
319             [item setSubmenu:submenu];
320             [mainMenu addItem:item];
322             [NSApp setMainMenu:mainMenu];
323             [NSApp setWindowsMenu:submenu];
325             [NSApp setApplicationIconImage:self.applicationIcon];
326         }
327     }
329     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
330     {
331         PerformRequest(NULL);
333         do
334         {
335             if (processEvents)
336             {
337                 @autoreleasepool
338                 {
339                     NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
340                                                         untilDate:timeout
341                                                            inMode:NSDefaultRunLoopMode
342                                                           dequeue:YES];
343                     if (event)
344                         [NSApp sendEvent:event];
345                 }
346             }
347             else
348                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
349         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
351         return *done;
352     }
354     - (BOOL) registerEventQueue:(WineEventQueue*)queue
355     {
356         [eventQueuesLock lock];
357         [eventQueues addObject:queue];
358         [eventQueuesLock unlock];
359         return TRUE;
360     }
362     - (void) unregisterEventQueue:(WineEventQueue*)queue
363     {
364         [eventQueuesLock lock];
365         [eventQueues removeObjectIdenticalTo:queue];
366         [eventQueuesLock unlock];
367     }
369     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
370     {
371         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
372     }
374     - (double) ticksForEventTime:(NSTimeInterval)eventTime
375     {
376         return (eventTime + eventTimeAdjustment) * 1000;
377     }
379     /* Invalidate old focus offers across all queues. */
380     - (void) invalidateGotFocusEvents
381     {
382         WineEventQueue* queue;
384         windowFocusSerial++;
386         [eventQueuesLock lock];
387         for (queue in eventQueues)
388         {
389             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
390                                    forWindow:nil];
391         }
392         [eventQueuesLock unlock];
393     }
395     - (void) windowGotFocus:(WineWindow*)window
396     {
397         macdrv_event* event;
399         [self invalidateGotFocusEvents];
401         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
402         event->window_got_focus.serial = windowFocusSerial;
403         if (triedWindows)
404             event->window_got_focus.tried_windows = [triedWindows retain];
405         else
406             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
407         [window.queue postEvent:event];
408         macdrv_release_event(event);
409     }
411     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
412     {
413         if (event->window_got_focus.serial == windowFocusSerial)
414         {
415             NSMutableArray* windows = [keyWindows mutableCopy];
416             NSNumber* windowNumber;
417             WineWindow* window;
419             for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
420             {
421                 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
422                 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
423                     ![windows containsObject:window])
424                     [windows addObject:window];
425             }
427             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
428             [triedWindows addObject:(WineWindow*)event->window];
429             for (window in windows)
430             {
431                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
432                 {
433                     [window makeKeyWindow];
434                     break;
435                 }
436             }
437             triedWindows = nil;
438             [windows release];
439         }
440     }
442     static BOOL EqualInputSource(TISInputSourceRef source1, TISInputSourceRef source2)
443     {
444         if (!source1 && !source2)
445             return TRUE;
446         if (!source1 || !source2)
447             return FALSE;
448         return CFEqual(source1, source2);
449     }
451     - (void) keyboardSelectionDidChange:(BOOL)force
452     {
453         TISInputSourceRef inputSource, inputSourceLayout;
455         if (!force)
456         {
457             NSTextInputContext* context = [NSTextInputContext currentInputContext];
458             if (!context || ![context client])
459                 return;
460         }
462         inputSource = TISCopyCurrentKeyboardInputSource();
463         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
464         if (!force && EqualInputSource(inputSource, lastKeyboardInputSource) &&
465             EqualInputSource(inputSourceLayout, lastKeyboardLayoutInputSource))
466         {
467             if (inputSource) CFRelease(inputSource);
468             if (inputSourceLayout) CFRelease(inputSourceLayout);
469             return;
470         }
472         if (lastKeyboardInputSource)
473             CFRelease(lastKeyboardInputSource);
474         lastKeyboardInputSource = inputSource;
475         if (lastKeyboardLayoutInputSource)
476             CFRelease(lastKeyboardLayoutInputSource);
477         lastKeyboardLayoutInputSource = inputSourceLayout;
479         inputSourceIsInputMethodValid = FALSE;
481         if (inputSourceLayout)
482         {
483             CFDataRef uchr;
484             uchr = TISGetInputSourceProperty(inputSourceLayout,
485                     kTISPropertyUnicodeKeyLayoutData);
486             if (uchr)
487             {
488                 macdrv_event* event;
489                 WineEventQueue* queue;
491                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
492                 event->keyboard_changed.keyboard_type = self.keyboardType;
493                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
494                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
495                 event->keyboard_changed.input_source = (TISInputSourceRef)CFRetain(inputSource);
497                 if (event->keyboard_changed.uchr)
498                 {
499                     [eventQueuesLock lock];
501                     for (queue in eventQueues)
502                         [queue postEvent:event];
504                     [eventQueuesLock unlock];
505                 }
507                 macdrv_release_event(event);
508             }
509         }
510     }
512     - (void) keyboardSelectionDidChange
513     {
514         [self keyboardSelectionDidChange:NO];
515     }
517     - (void) setKeyboardType:(CGEventSourceKeyboardType)newType
518     {
519         if (newType != keyboardType)
520         {
521             keyboardType = newType;
522             [self keyboardSelectionDidChange:YES];
523         }
524     }
526     - (void) enabledKeyboardInputSourcesChanged
527     {
528         macdrv_layout_list_needs_update = TRUE;
529     }
531     - (CGFloat) primaryScreenHeight
532     {
533         if (!primaryScreenHeightValid)
534         {
535             NSArray* screens = [NSScreen screens];
536             NSUInteger count = [screens count];
537             if (count)
538             {
539                 NSUInteger size;
540                 CGRect* rect;
541                 NSScreen* screen;
543                 primaryScreenHeight = NSHeight([screens[0] frame]);
544                 primaryScreenHeightValid = TRUE;
546                 size = count * sizeof(CGRect);
547                 if (!screenFrameCGRects)
548                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
549                 else
550                     [screenFrameCGRects setLength:size];
552                 rect = [screenFrameCGRects mutableBytes];
553                 for (screen in screens)
554                 {
555                     CGRect temp = NSRectToCGRect([screen frame]);
556                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
557                     *rect++ = temp;
558                 }
559             }
560             else
561                 return 1280; /* arbitrary value */
562         }
564         return primaryScreenHeight;
565     }
567     - (NSPoint) flippedMouseLocation:(NSPoint)point
568     {
569         /* This relies on the fact that Cocoa's mouse location points are
570            actually off by one (precisely because they were flipped from
571            Quartz screen coordinates using this same technique). */
572         point.y = [self primaryScreenHeight] - point.y;
573         return point;
574     }
576     - (void) flipRect:(NSRect*)rect
577     {
578         // We don't use -primaryScreenHeight here so there's no chance of having
579         // out-of-date cached info.  This method is called infrequently enough
580         // that getting the screen height each time is not prohibitively expensive.
581         rect->origin.y = NSMaxY([[NSScreen screens][0] frame]) - NSMaxY(*rect);
582     }
584     - (WineWindow*) frontWineWindow
585     {
586         NSNumber* windowNumber;
587         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
588         {
589             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
590             if ([window isKindOfClass:[WineWindow class]] && [window screen])
591                 return (WineWindow*)window;
592         }
594         return nil;
595     }
597     - (void) adjustWindowLevels:(BOOL)active
598     {
599         NSArray* windowNumbers;
600         NSMutableArray* wineWindows;
601         NSNumber* windowNumber;
602         NSUInteger nextFloatingIndex = 0;
603         __block NSInteger maxLevel = NSIntegerMin;
604         __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
605         __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
606         __block WineWindow* prev = nil;
607         WineWindow* window;
609         if ([NSApp isHidden]) return;
611         windowNumbers = [NSWindow windowNumbersWithOptions:0];
612         wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
614         // For the most part, we rely on the window server's ordering of the windows
615         // to be authoritative.  The one exception is if the "floating" property of
616         // one of the windows has been changed, it may be in the wrong level and thus
617         // in the order.  This method is what's supposed to fix that up.  So build
618         // a list of Wine windows sorted first by floating-ness and then by order
619         // as indicated by the window server.
620         for (windowNumber in windowNumbers)
621         {
622             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
623             if ([window isKindOfClass:[WineWindow class]])
624             {
625                 if (window.floating)
626                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
627                 else
628                     [wineWindows addObject:window];
629             }
630         }
632         NSDisableScreenUpdates();
634         // Go from back to front so that all windows in front of one which is
635         // elevated for full-screen are also elevated.
636         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
637                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
638             WineWindow* window = (WineWindow*)obj;
639             NSInteger origLevel = [window level];
640             NSInteger newLevel = [window minimumLevelForActive:active];
642             if (window.floating)
643             {
644                 if (minFloatingLevel <= maxNonfloatingLevel)
645                     minFloatingLevel = maxNonfloatingLevel + 1;
646                 if (newLevel < minFloatingLevel)
647                     newLevel = minFloatingLevel;
648             }
650             if (newLevel < maxLevel)
651                 newLevel = maxLevel;
652             else
653                 maxLevel = newLevel;
655             if (!window.floating && maxNonfloatingLevel < newLevel)
656                 maxNonfloatingLevel = newLevel;
658             if (newLevel != origLevel)
659             {
660                 [window setLevel:newLevel];
662                 if (origLevel < newLevel)
663                 {
664                     // If we increased the level, the window should be toward the
665                     // back of its new level (but still ahead of the previous
666                     // windows we did this to).
667                     if (prev)
668                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
669                     else
670                         [window orderBack:nil];
671                 }
672                 else
673                 {
674                     // If we decreased the level, we want the window at the top
675                     // of its new level. -setLevel: is documented to do that on
676                     // its own, but that's buggy on Ventura. Since we're looping
677                     // back-to-front here, -orderFront: will do the right thing.
678                     [window orderFront:nil];
679                 }
680             }
682             prev = window;
683         }];
685         NSEnableScreenUpdates();
687         [wineWindows release];
689         // The above took care of the visible windows on the current space.  That
690         // leaves windows on other spaces, minimized windows, and windows which
691         // are not ordered in.  We want to leave windows on other spaces alone
692         // so the space remains just as they left it (when viewed in Exposé or
693         // Mission Control, for example).  We'll adjust the window levels again
694         // after we switch to another space, anyway.  Windows which aren't
695         // ordered in will be handled when we order them in.  Minimized windows
696         // on the current space should be set to the level they would have gotten
697         // if they were at the front of the windows with the same floating-ness,
698         // because that's where they'll go if/when they are unminimized.  Again,
699         // for good measure we'll adjust window levels again when a window is
700         // unminimized, too.
701         for (window in [NSApp windows])
702         {
703             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
704                 [window isOnActiveSpace])
705             {
706                 NSInteger origLevel = [window level];
707                 NSInteger newLevel = [window minimumLevelForActive:YES];
708                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
710                 if (newLevel < maxLevelForType)
711                     newLevel = maxLevelForType;
713                 if (newLevel != origLevel)
714                     [window setLevel:newLevel];
715             }
716         }
717     }
719     - (void) adjustWindowLevels
720     {
721         [self adjustWindowLevels:[NSApp isActive]];
722     }
724     - (void) updateFullscreenWindows
725     {
726         if (capture_displays_for_fullscreen && [NSApp isActive])
727         {
728             BOOL anyFullscreen = FALSE;
729             NSNumber* windowNumber;
730             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
731             {
732                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
733                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
734                 {
735                     anyFullscreen = TRUE;
736                     break;
737                 }
738             }
740             if (anyFullscreen)
741             {
742                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
743                     displaysCapturedForFullscreen = TRUE;
744             }
745             else if (displaysCapturedForFullscreen)
746             {
747                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
748                     displaysCapturedForFullscreen = FALSE;
749             }
750         }
751     }
753     - (void) activeSpaceDidChange
754     {
755         [self updateFullscreenWindows];
756         [self adjustWindowLevels];
757     }
759     - (void) sendDisplaysChanged:(BOOL)activating
760     {
761         macdrv_event* event;
762         WineEventQueue* queue;
764         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
765         event->displays_changed.activating = activating;
767         [eventQueuesLock lock];
769         // If we're activating, then we just need one of our threads to get the
770         // event, so it can send it directly to the desktop window.  Otherwise,
771         // we need all of the threads to get it because we don't know which owns
772         // the desktop window and only that one will do anything with it.
773         if (activating) event->deliver = 1;
775         for (queue in eventQueues)
776             [queue postEvent:event];
777         [eventQueuesLock unlock];
779         macdrv_release_event(event);
780     }
782     // We can compare two modes directly using CFEqual, but that may require that
783     // they are identical to a level that we don't need.  In particular, when the
784     // OS switches between the integrated and discrete GPUs, the set of display
785     // modes can change in subtle ways.  We're interested in whether two modes
786     // match in their most salient features, even if they aren't identical.
787     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
788     {
789         NSString *encoding1, *encoding2;
790         uint32_t ioflags1, ioflags2, different;
791         double refresh1, refresh2;
793         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
794         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
795         if (CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
796         if (CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
798         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
799         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
800         if (![encoding1 isEqualToString:encoding2]) return FALSE;
802         ioflags1 = CGDisplayModeGetIOFlags(mode1);
803         ioflags2 = CGDisplayModeGetIOFlags(mode2);
804         different = ioflags1 ^ ioflags2;
805         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
806                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
807             return FALSE;
809         refresh1 = CGDisplayModeGetRefreshRate(mode1);
810         if (refresh1 == 0) refresh1 = 60;
811         refresh2 = CGDisplayModeGetRefreshRate(mode2);
812         if (refresh2 == 0) refresh2 = 60;
813         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
815         return TRUE;
816     }
818     - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
819     {
820         NSMutableArray* ret = [NSMutableArray array];
821         NSDictionary* options = @{ (NSString*)kCGDisplayShowDuplicateLowResolutionModes: @YES };
823         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
824         for (id candidateModeObject in modes)
825         {
826             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
827             if ([self mode:candidateMode matchesMode:mode])
828                 [ret addObject:candidateModeObject];
829         }
830         return ret;
831     }
833     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
834     {
835         BOOL ret = FALSE;
836         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
837         CGDisplayModeRef originalMode;
839         originalMode = (CGDisplayModeRef)originalDisplayModes[displayIDKey];
841         if (originalMode && [self mode:mode matchesMode:originalMode])
842         {
843             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
844             {
845                 CGRestorePermanentDisplayConfiguration();
846                 if (!displaysCapturedForFullscreen)
847                     CGReleaseAllDisplays();
848                 [originalDisplayModes removeAllObjects];
849                 ret = TRUE;
850             }
851             else // ... otherwise, try to restore just the one display
852             {
853                 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
854                 {
855                     mode = (CGDisplayModeRef)modeObject;
856                     if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
857                     {
858                         [originalDisplayModes removeObjectForKey:displayIDKey];
859                         ret = TRUE;
860                         break;
861                     }
862                 }
863             }
864         }
865         else
866         {
867             CGDisplayModeRef currentMode;
868             NSArray* modes;
870             currentMode = CGDisplayModeRetain((CGDisplayModeRef)latentDisplayModes[displayIDKey]);
871             if (!currentMode)
872                 currentMode = CGDisplayCopyDisplayMode(displayID);
873             if (!currentMode) // Invalid display ID
874                 return FALSE;
876             if ([self mode:mode matchesMode:currentMode]) // Already there!
877             {
878                 CGDisplayModeRelease(currentMode);
879                 return TRUE;
880             }
882             CGDisplayModeRelease(currentMode);
883             currentMode = NULL;
885             modes = [self modesMatchingMode:mode forDisplay:displayID];
886             if (!modes.count)
887                 return FALSE;
889             [self transformProcessToForeground:YES];
891             BOOL active = [NSApp isActive];
893             if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
894                 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
895             {
896                 if (active)
897                 {
898                     // If we get here, we have the displays captured.  If we don't
899                     // know the original mode of the display, the current mode must
900                     // be the original.  We should re-query the current mode since
901                     // another process could have changed it between when we last
902                     // checked and when we captured the displays.
903                     if (!originalMode)
904                         originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
906                     if (originalMode)
907                     {
908                         for (id modeObject in modes)
909                         {
910                             mode = (CGDisplayModeRef)modeObject;
911                             if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
912                             {
913                                 ret = TRUE;
914                                 break;
915                             }
916                         }
917                     }
918                     if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
919                         [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
920                     else if (![originalDisplayModes count])
921                     {
922                         CGRestorePermanentDisplayConfiguration();
923                         if (!displaysCapturedForFullscreen)
924                             CGReleaseAllDisplays();
925                     }
927                     if (currentMode)
928                         CGDisplayModeRelease(currentMode);
929                 }
930                 else
931                 {
932                     [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
933                     ret = TRUE;
934                 }
935             }
936         }
938         if (ret)
939             [self adjustWindowLevels];
941         return ret;
942     }
944     - (BOOL) areDisplaysCaptured
945     {
946         return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
947     }
949     - (void) updateCursor:(BOOL)force
950     {
951         if (force || lastTargetWindow)
952         {
953             if (clientWantsCursorHidden && !cursorHidden)
954             {
955                 [NSCursor hide];
956                 cursorHidden = TRUE;
957             }
959             if (!cursorIsCurrent)
960             {
961                 [cursor set];
962                 cursorIsCurrent = TRUE;
963             }
965             if (!clientWantsCursorHidden && cursorHidden)
966             {
967                 [NSCursor unhide];
968                 cursorHidden = FALSE;
969             }
970         }
971         else
972         {
973             if (cursorIsCurrent)
974             {
975                 [[NSCursor arrowCursor] set];
976                 cursorIsCurrent = FALSE;
977             }
978             if (cursorHidden)
979             {
980                 [NSCursor unhide];
981                 cursorHidden = FALSE;
982             }
983         }
984     }
986     - (void) hideCursor
987     {
988         if (!clientWantsCursorHidden)
989         {
990             clientWantsCursorHidden = TRUE;
991             [self updateCursor:TRUE];
992         }
993     }
995     - (void) unhideCursor
996     {
997         if (clientWantsCursorHidden)
998         {
999             clientWantsCursorHidden = FALSE;
1000             [self updateCursor:FALSE];
1001         }
1002     }
1004     - (void) setCursor:(NSCursor*)newCursor
1005     {
1006         if (newCursor != cursor)
1007         {
1008             [cursor release];
1009             cursor = [newCursor retain];
1010             cursorIsCurrent = FALSE;
1011             [self updateCursor:FALSE];
1012         }
1013     }
1015     - (void) setCursor
1016     {
1017         NSDictionary* frame = cursorFrames[cursorFrame];
1018         CGImageRef cgimage = (CGImageRef)frame[@"image"];
1019         CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
1020         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
1021         CFDictionaryRef hotSpotDict = (CFDictionaryRef)frame[@"hotSpot"];
1022         CGPoint hotSpot;
1024         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
1025             hotSpot = CGPointZero;
1026         hotSpot = cgpoint_mac_from_win(hotSpot);
1027         self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1028         [image release];
1029         [self unhideCursor];
1030     }
1032     - (void) nextCursorFrame:(NSTimer*)theTimer
1033     {
1034         NSDictionary* frame;
1035         NSTimeInterval duration;
1036         NSDate* date;
1038         cursorFrame++;
1039         if (cursorFrame >= [cursorFrames count])
1040             cursorFrame = 0;
1041         [self setCursor];
1043         frame = cursorFrames[cursorFrame];
1044         duration = [frame[@"duration"] doubleValue];
1045         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1046         [cursorTimer setFireDate:date];
1047     }
1049     - (void) setCursorWithFrames:(NSArray*)frames
1050     {
1051         if (self.cursorFrames == frames)
1052             return;
1054         self.cursorFrames = frames;
1055         cursorFrame = 0;
1056         [cursorTimer invalidate];
1057         self.cursorTimer = nil;
1059         if ([frames count])
1060         {
1061             if ([frames count] > 1)
1062             {
1063                 NSDictionary* frame = frames[0];
1064                 NSTimeInterval duration = [frame[@"duration"] doubleValue];
1065                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1066                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1067                                                              interval:1000000
1068                                                                target:self
1069                                                              selector:@selector(nextCursorFrame:)
1070                                                              userInfo:nil
1071                                                               repeats:YES] autorelease];
1072                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1073             }
1075             [self setCursor];
1076         }
1077     }
1079     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1080     {
1081         NSImage* nsimage = nil;
1083         if ([images count])
1084         {
1085             NSSize bestSize = NSZeroSize;
1086             id image;
1088             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1090             for (image in images)
1091             {
1092                 CGImageRef cgimage = (CGImageRef)image;
1093                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1094                 if (imageRep)
1095                 {
1096                     NSSize size = [imageRep size];
1098                     [nsimage addRepresentation:imageRep];
1099                     [imageRep release];
1101                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1102                         bestSize = size;
1103                 }
1104             }
1106             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1107                 [nsimage setSize:bestSize];
1108             else
1109                 nsimage = nil;
1110         }
1112         self.applicationIcon = nsimage;
1113     }
1115     - (void) handleCommandTab
1116     {
1117         if ([NSApp isActive])
1118         {
1119             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1120             NSRunningApplication* app;
1121             NSRunningApplication* otherValidApp = nil;
1123             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1124             {
1125                 NSNumber* displayID;
1126                 for (displayID in originalDisplayModes)
1127                 {
1128                     CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1129                     [latentDisplayModes setObject:(id)mode forKey:displayID];
1130                     CGDisplayModeRelease(mode);
1131                 }
1133                 CGRestorePermanentDisplayConfiguration();
1134                 CGReleaseAllDisplays();
1135                 [originalDisplayModes removeAllObjects];
1136                 displaysCapturedForFullscreen = FALSE;
1137             }
1139             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1140             {
1141                 if (![app isEqual:thisApp] && !app.terminated &&
1142                     app.activationPolicy == NSApplicationActivationPolicyRegular)
1143                 {
1144                     if (!app.hidden)
1145                     {
1146                         // There's another visible app.  Just hide ourselves and let
1147                         // the system activate the other app.
1148                         [NSApp hide:self];
1149                         return;
1150                     }
1152                     if (!otherValidApp)
1153                         otherValidApp = app;
1154                 }
1155             }
1157             // Didn't find a visible GUI app.  Try the Finder or, if that's not
1158             // running, the first hidden GUI app.  If even that doesn't work, we
1159             // just fail to switch and remain the active app.
1160             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1161             if (!app) app = otherValidApp;
1162             [app unhide];
1163             [app activateWithOptions:0];
1164         }
1165     }
1167     - (BOOL) setCursorPosition:(CGPoint)pos
1168     {
1169         BOOL ret;
1171         if ([windowsBeingDragged count])
1172             ret = FALSE;
1173         else if (self.clippingCursor && [clipCursorHandler respondsToSelector:@selector(setCursorPosition:)])
1174             ret = [clipCursorHandler setCursorPosition:pos];
1175         else
1176         {
1177             if (self.clippingCursor)
1178                 [clipCursorHandler clipCursorLocation:&pos];
1180             // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1181             // the mouse from the cursor position for 0.25 seconds.  This means
1182             // that mouse movement during that interval doesn't move the cursor
1183             // and events carry a constant location (the warped-to position)
1184             // even though they have delta values.  For apps which warp the
1185             // cursor frequently (like after every mouse move), this makes
1186             // cursor movement horribly laggy and jerky, as only a fraction of
1187             // mouse move events have any effect.
1188             //
1189             // On some versions of OS X, it's sufficient to forcibly reassociate
1190             // the mouse and cursor position.  On others, it's necessary to set
1191             // the local events suppression interval to 0 for the warp.  That's
1192             // deprecated, but I'm not aware of any other way.  For good
1193             // measure, we do both.
1194             CGSetLocalEventsSuppressionInterval(0);
1195             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1196             CGSetLocalEventsSuppressionInterval(0.25);
1197             if (ret)
1198             {
1199                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1201                 CGAssociateMouseAndMouseCursorPosition(true);
1202             }
1203         }
1205         if (ret)
1206         {
1207             WineEventQueue* queue;
1209             // Discard all pending mouse move events.
1210             [eventQueuesLock lock];
1211             for (queue in eventQueues)
1212             {
1213                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED_RELATIVE) |
1214                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1215                                        forWindow:nil];
1216                 [queue resetMouseEventPositions:pos];
1217             }
1218             [eventQueuesLock unlock];
1219         }
1221         return ret;
1222     }
1224     - (void) updateWindowsForCursorClipping
1225     {
1226         WineWindow* window;
1227         for (window in [NSApp windows])
1228         {
1229             if ([window isKindOfClass:[WineWindow class]])
1230                 [window updateForCursorClipping];
1231         }
1232     }
1234     - (BOOL) startClippingCursor:(CGRect)rect
1235     {
1236         if (!clipCursorHandler) {
1237             if (use_confinement_cursor_clipping && [WineConfinementClipCursorHandler isAvailable])
1238                 clipCursorHandler = [[WineConfinementClipCursorHandler alloc] init];
1239             else
1240                 clipCursorHandler = [[WineEventTapClipCursorHandler alloc] init];
1241         }
1243         if (self.clippingCursor && CGRectEqualToRect(rect, clipCursorHandler.cursorClipRect))
1244             return TRUE;
1246         if (![clipCursorHandler startClippingCursor:rect])
1247             return FALSE;
1249         [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1251         [self updateWindowsForCursorClipping];
1253         return TRUE;
1254     }
1256     - (BOOL) stopClippingCursor
1257     {
1258         if (!self.clippingCursor)
1259             return TRUE;
1261         if (![clipCursorHandler stopClippingCursor])
1262             return FALSE;
1264         lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1266         [self updateWindowsForCursorClipping];
1268         return TRUE;
1269     }
1271     - (BOOL) clippingCursor
1272     {
1273         return clipCursorHandler.clippingCursor;
1274     }
1276     - (BOOL) isKeyPressed:(uint16_t)keyCode
1277     {
1278         int bits = sizeof(pressedKeyCodes[0]) * 8;
1279         int index = keyCode / bits;
1280         uint32_t mask = 1 << (keyCode % bits);
1281         return (pressedKeyCodes[index] & mask) != 0;
1282     }
1284     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1285     {
1286         int bits = sizeof(pressedKeyCodes[0]) * 8;
1287         int index = keyCode / bits;
1288         uint32_t mask = 1 << (keyCode % bits);
1289         if (pressed)
1290             pressedKeyCodes[index] |= mask;
1291         else
1292             pressedKeyCodes[index] &= ~mask;
1293     }
1295     - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1296     {
1297         if (dragged)
1298             [windowsBeingDragged addObject:window];
1299         else
1300             [windowsBeingDragged removeObject:window];
1301     }
1303     - (void) windowWillOrderOut:(WineWindow*)window
1304     {
1305         if ([windowsBeingDragged containsObject:window])
1306         {
1307             [self window:window isBeingDragged:NO];
1309             macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1310             [window.queue postEvent:event];
1311             macdrv_release_event(event);
1312         }
1313     }
1315     - (BOOL) isAnyWineWindowVisible
1316     {
1317         for (WineWindow* w in [NSApp windows])
1318         {
1319             if ([w isKindOfClass:[WineWindow class]] && ![w isMiniaturized] && [w isVisible])
1320                 return YES;
1321         }
1323         return NO;
1324     }
1326     - (void) handleWindowDrag:(WineWindow*)window begin:(BOOL)begin
1327     {
1328         macdrv_event* event;
1329         int eventType;
1331         if (begin)
1332         {
1333             [windowsBeingDragged addObject:window];
1334             eventType = WINDOW_DRAG_BEGIN;
1335         }
1336         else
1337         {
1338             [windowsBeingDragged removeObject:window];
1339             eventType = WINDOW_DRAG_END;
1340         }
1342         event = macdrv_create_event(eventType, window);
1343         if (eventType == WINDOW_DRAG_BEGIN)
1344             event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
1345         [window.queue postEvent:event];
1346         macdrv_release_event(event);
1347     }
1349     - (void) handleMouseMove:(NSEvent*)anEvent
1350     {
1351         WineWindow* targetWindow;
1352         BOOL drag = [anEvent type] != NSEventTypeMouseMoved;
1354         if ([windowsBeingDragged count])
1355             targetWindow = nil;
1356         else if (mouseCaptureWindow)
1357             targetWindow = mouseCaptureWindow;
1358         else if (drag)
1359             targetWindow = (WineWindow*)[anEvent window];
1360         else
1361         {
1362             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1363                event indicates its window is the main window, even if the cursor is
1364                over a different window.  Find the actual WineWindow that is under the
1365                cursor and post the event as being for that window. */
1366             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1367             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1368             NSInteger windowUnderNumber;
1370             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1371                                   belowWindowWithWindowNumber:0];
1372             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1373             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1374                 targetWindow = nil;
1375         }
1377         if ([targetWindow isKindOfClass:[WineWindow class]])
1378         {
1379             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1380             macdrv_event* event;
1381             BOOL absolute;
1383             // If we recently warped the cursor (other than in our cursor-clipping
1384             // event tap), discard mouse move events until we see an event which is
1385             // later than that time.
1386             if (lastSetCursorPositionTime)
1387             {
1388                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1389                     return;
1391                 lastSetCursorPositionTime = 0;
1392                 forceNextMouseMoveAbsolute = TRUE;
1393             }
1395             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1396             {
1397                 absolute = TRUE;
1398                 forceNextMouseMoveAbsolute = FALSE;
1399             }
1400             else
1401             {
1402                 // Send absolute move events if the cursor is in the interior of
1403                 // its range.  Only send relative moves if the cursor is pinned to
1404                 // the boundaries of where it can go.  We compute the position
1405                 // that's one additional point in the direction of movement.  If
1406                 // that is outside of the clipping rect or desktop region (the
1407                 // union of the screen frames), then we figure the cursor would
1408                 // have moved outside if it could but it was pinned.
1409                 CGPoint computedPoint = point;
1410                 CGFloat deltaX = [anEvent deltaX];
1411                 CGFloat deltaY = [anEvent deltaY];
1413                 if (deltaX > 0.001)
1414                     computedPoint.x++;
1415                 else if (deltaX < -0.001)
1416                     computedPoint.x--;
1418                 if (deltaY > 0.001)
1419                     computedPoint.y++;
1420                 else if (deltaY < -0.001)
1421                     computedPoint.y--;
1423                 // Assume cursor is pinned for now
1424                 absolute = FALSE;
1425                 if (!self.clippingCursor || CGRectContainsPoint(clipCursorHandler.cursorClipRect, computedPoint))
1426                 {
1427                     const CGRect* rects;
1428                     NSUInteger count, i;
1430                     // Caches screenFrameCGRects if necessary
1431                     [self primaryScreenHeight];
1433                     rects = [screenFrameCGRects bytes];
1434                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1436                     for (i = 0; i < count; i++)
1437                     {
1438                         if (CGRectContainsPoint(rects[i], computedPoint))
1439                         {
1440                             absolute = TRUE;
1441                             break;
1442                         }
1443                     }
1444                 }
1445             }
1447             if (absolute)
1448             {
1449                 if (self.clippingCursor)
1450                     [clipCursorHandler clipCursorLocation:&point];
1451                 point = cgpoint_win_from_mac(point);
1453                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1454                 event->mouse_moved.x = floor(point.x);
1455                 event->mouse_moved.y = floor(point.y);
1457                 mouseMoveDeltaX = 0;
1458                 mouseMoveDeltaY = 0;
1459             }
1460             else
1461             {
1462                 double scale = retina_on ? 2 : 1;
1464                 /* Add event delta to accumulated delta error */
1465                 /* deltaY is already flipped */
1466                 mouseMoveDeltaX += [anEvent deltaX];
1467                 mouseMoveDeltaY += [anEvent deltaY];
1469                 event = macdrv_create_event(MOUSE_MOVED_RELATIVE, targetWindow);
1470                 event->mouse_moved.x = mouseMoveDeltaX * scale;
1471                 event->mouse_moved.y = mouseMoveDeltaY * scale;
1473                 /* Keep the remainder after integer truncation. */
1474                 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1475                 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1476             }
1478             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1479             {
1480                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1481                 event->mouse_moved.drag = drag;
1483                 [targetWindow.queue postEvent:event];
1484             }
1486             macdrv_release_event(event);
1488             lastTargetWindow = targetWindow;
1489         }
1490         else
1491             lastTargetWindow = nil;
1493         [self updateCursor:FALSE];
1494     }
1496     - (void) handleMouseButton:(NSEvent*)theEvent
1497     {
1498         WineWindow* window = (WineWindow*)[theEvent window];
1499         NSEventType type = [theEvent type];
1500         WineWindow* windowBroughtForward = nil;
1501         BOOL process = FALSE;
1503         if ([window isKindOfClass:[WineWindow class]] &&
1504             type == NSEventTypeLeftMouseDown &&
1505             ![theEvent wine_commandKeyDown])
1506         {
1507             NSWindowButton windowButton;
1509             windowBroughtForward = window;
1511             /* Any left-click on our window anyplace other than the close or
1512                minimize buttons will bring it forward. */
1513             for (windowButton = NSWindowCloseButton;
1514                  windowButton <= NSWindowMiniaturizeButton;
1515                  windowButton++)
1516             {
1517                 NSButton* button = [window standardWindowButton:windowButton];
1518                 if (button)
1519                 {
1520                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1521                     if ([button mouse:point inRect:[button bounds]])
1522                     {
1523                         windowBroughtForward = nil;
1524                         break;
1525                     }
1526                 }
1527             }
1528         }
1530         if ([windowsBeingDragged count])
1531             window = nil;
1532         else if (mouseCaptureWindow)
1533             window = mouseCaptureWindow;
1535         if ([window isKindOfClass:[WineWindow class]])
1536         {
1537             BOOL pressed = (type == NSEventTypeLeftMouseDown ||
1538                             type == NSEventTypeRightMouseDown ||
1539                             type == NSEventTypeOtherMouseDown);
1540             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1542             if (self.clippingCursor)
1543                 [clipCursorHandler clipCursorLocation:&pt];
1545             if (pressed)
1546             {
1547                 if (mouseCaptureWindow)
1548                     process = TRUE;
1549                 else
1550                 {
1551                     // Test if the click was in the window's content area.
1552                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1553                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1554                     process = NSMouseInRect(nspoint, contentRect, NO);
1555                     if (process && [window styleMask] & NSWindowStyleMaskResizable)
1556                     {
1557                         // Ignore clicks in the grow box (resize widget).
1558                         HIPoint origin = { 0, 0 };
1559                         HIThemeGrowBoxDrawInfo info = { 0 };
1560                         HIRect bounds;
1561                         OSStatus status;
1563                         info.kind = kHIThemeGrowBoxKindNormal;
1564                         info.direction = kThemeGrowRight | kThemeGrowDown;
1565                         if ([window styleMask] & NSWindowStyleMaskUtilityWindow)
1566                             info.size = kHIThemeGrowBoxSizeSmall;
1567                         else
1568                             info.size = kHIThemeGrowBoxSizeNormal;
1570                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1571                         if (status == noErr)
1572                         {
1573                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1574                                                         NSMinY(contentRect),
1575                                                         bounds.size.width,
1576                                                         bounds.size.height);
1577                             process = !NSMouseInRect(nspoint, growBox, NO);
1578                         }
1579                     }
1580                 }
1581                 if (process)
1582                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1583             }
1584             else
1585             {
1586                 NSEventType downType = type - 1;
1587                 NSUInteger downMask = NSEventMaskFromType(downType);
1588                 process = (unmatchedMouseDowns & downMask) != 0;
1589                 unmatchedMouseDowns &= ~downMask;
1590             }
1592             if (process)
1593             {
1594                 macdrv_event* event;
1596                 pt = cgpoint_win_from_mac(pt);
1598                 event = macdrv_create_event(MOUSE_BUTTON, window);
1599                 event->mouse_button.button = [theEvent buttonNumber];
1600                 event->mouse_button.pressed = pressed;
1601                 event->mouse_button.x = floor(pt.x);
1602                 event->mouse_button.y = floor(pt.y);
1603                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1605                 [window.queue postEvent:event];
1607                 macdrv_release_event(event);
1608             }
1609         }
1611         if (windowBroughtForward)
1612         {
1613             WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1614             NSInteger ancestorNumber = [ancestor windowNumber];
1615             NSInteger ancestorLevel = [ancestor level];
1617             for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1618             {
1619                 NSInteger windowNumber = [windowNumberObject integerValue];
1620                 if (windowNumber == ancestorNumber)
1621                     break;
1622                 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1623                 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1624                     [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1625                 {
1626                     [ancestor postBroughtForwardEvent];
1627                     break;
1628                 }
1629             }
1630             if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noForeground)
1631                 [self windowGotFocus:windowBroughtForward];
1632         }
1634         // Since mouse button events deliver absolute cursor position, the
1635         // accumulating delta from move events is invalidated.  Make sure
1636         // next mouse move event starts over from an absolute baseline.
1637         // Also, it's at least possible that the title bar widgets (e.g. close
1638         // button, etc.) could enter an internal event loop on a mouse down that
1639         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1640         // dragged events and, after that, any notion of the cursor position
1641         // computed from accumulating deltas would be wrong.
1642         forceNextMouseMoveAbsolute = TRUE;
1643     }
1645     - (void) handleScrollWheel:(NSEvent*)theEvent
1646     {
1647         WineWindow* window;
1649         if (mouseCaptureWindow)
1650             window = mouseCaptureWindow;
1651         else
1652             window = (WineWindow*)[theEvent window];
1654         if ([window isKindOfClass:[WineWindow class]])
1655         {
1656             CGEventRef cgevent = [theEvent CGEvent];
1657             CGPoint pt = CGEventGetLocation(cgevent);
1658             BOOL process;
1660             if (self.clippingCursor)
1661                 [clipCursorHandler clipCursorLocation:&pt];
1663             if (mouseCaptureWindow)
1664                 process = TRUE;
1665             else
1666             {
1667                 // Only process the event if it was in the window's content area.
1668                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1669                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1670                 process = NSMouseInRect(nspoint, contentRect, NO);
1671             }
1673             if (process)
1674             {
1675                 macdrv_event* event;
1676                 double x, y;
1677                 BOOL continuous = FALSE;
1679                 pt = cgpoint_win_from_mac(pt);
1681                 event = macdrv_create_event(MOUSE_SCROLL, window);
1682                 event->mouse_scroll.x = floor(pt.x);
1683                 event->mouse_scroll.y = floor(pt.y);
1684                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1686                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1687                 {
1688                     continuous = TRUE;
1690                     /* Continuous scroll wheel events come from high-precision scrolling
1691                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1692                        For these, we can get more precise data from the CGEvent API. */
1693                     /* Axis 1 is vertical, axis 2 is horizontal. */
1694                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1695                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1696                 }
1697                 else
1698                 {
1699                     double pixelsPerLine = 10;
1700                     CGEventSourceRef source;
1702                     /* The non-continuous values are in units of "lines", not pixels. */
1703                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1704                     {
1705                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1706                         CFRelease(source);
1707                     }
1709                     x = pixelsPerLine * [theEvent deltaX];
1710                     y = pixelsPerLine * [theEvent deltaY];
1711                 }
1713                 /* Mac: negative is right or down, positive is left or up.
1714                    Win32: negative is left or down, positive is right or up.
1715                    So, negate the X scroll value to translate. */
1716                 x = -x;
1718                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1719                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1720                    6 times the pixel value. */
1721                 x *= 6;
1722                 y *= 6;
1724                 if (use_precise_scrolling)
1725                 {
1726                     event->mouse_scroll.x_scroll = x;
1727                     event->mouse_scroll.y_scroll = y;
1729                     if (!continuous)
1730                     {
1731                         /* For non-continuous "clicky" wheels, if there was any motion, make
1732                            sure there was at least WHEEL_DELTA motion.  This is so, at slow
1733                            speeds where the system's acceleration curve is actually reducing the
1734                            scroll distance, the user is sure to get some action out of each click.
1735                            For example, this is important for rotating though weapons in a
1736                            first-person shooter. */
1737                         if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1738                             event->mouse_scroll.x_scroll = 120;
1739                         else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1740                             event->mouse_scroll.x_scroll = -120;
1742                         if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1743                             event->mouse_scroll.y_scroll = 120;
1744                         else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1745                             event->mouse_scroll.y_scroll = -120;
1746                     }
1747                 }
1748                 else
1749                 {
1750                     /* If it's been a while since the last scroll event or if the scrolling has
1751                        reversed direction, reset the accumulated scroll value. */
1752                     if ([theEvent timestamp] - lastScrollTime > 1)
1753                         accumScrollX = accumScrollY = 0;
1754                     else
1755                     {
1756                         /* The accumulated scroll value is in the opposite direction/sign of the last
1757                            scroll.  That's because it's the "debt" resulting from over-scrolling in
1758                            that direction.  We accumulate by adding in the scroll amount and then, if
1759                            it has the same sign as the scroll value, we subtract any whole or partial
1760                            WHEEL_DELTAs, leaving it 0 or the opposite sign.  So, the user switched
1761                            scroll direction if the accumulated debt and the new scroll value have the
1762                            same sign. */
1763                         if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1764                             accumScrollX = 0;
1765                         if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1766                             accumScrollY = 0;
1767                     }
1768                     lastScrollTime = [theEvent timestamp];
1770                     accumScrollX += x;
1771                     accumScrollY += y;
1773                     if (accumScrollX > 0 && x > 0)
1774                         event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1775                     if (accumScrollX < 0 && x < 0)
1776                         event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1777                     if (accumScrollY > 0 && y > 0)
1778                         event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1779                     if (accumScrollY < 0 && y < 0)
1780                         event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1782                     accumScrollX -= event->mouse_scroll.x_scroll;
1783                     accumScrollY -= event->mouse_scroll.y_scroll;
1784                 }
1786                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1787                     [window.queue postEvent:event];
1789                 macdrv_release_event(event);
1791                 // Since scroll wheel events deliver absolute cursor position, the
1792                 // accumulating delta from move events is invalidated.  Make sure next
1793                 // mouse move event starts over from an absolute baseline.
1794                 forceNextMouseMoveAbsolute = TRUE;
1795             }
1796         }
1797     }
1799     // Returns TRUE if the event was handled and caller should do nothing more
1800     // with it.  Returns FALSE if the caller should process it as normal and
1801     // then call -didSendEvent:.
1802     - (BOOL) handleEvent:(NSEvent*)anEvent
1803     {
1804         BOOL ret = FALSE;
1805         NSEventType type = [anEvent type];
1807         if (type == NSEventTypeFlagsChanged)
1808             self.lastFlagsChanged = anEvent;
1809         else if (type == NSEventTypeMouseMoved || type == NSEventTypeLeftMouseDragged ||
1810                  type == NSEventTypeRightMouseDragged || type == NSEventTypeOtherMouseDragged)
1811         {
1812             [self handleMouseMove:anEvent];
1813             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1814         }
1815         else if (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp ||
1816                  type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp ||
1817                  type == NSEventTypeOtherMouseDown || type == NSEventTypeOtherMouseUp)
1818         {
1819             [self handleMouseButton:anEvent];
1820             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1821         }
1822         else if (type == NSEventTypeScrollWheel)
1823         {
1824             [self handleScrollWheel:anEvent];
1825             ret = mouseCaptureWindow != nil;
1826         }
1827         else if (type == NSEventTypeKeyDown)
1828         {
1829             // -[NSApplication sendEvent:] seems to consume presses of the Help
1830             // key (Insert key on PC keyboards), so we have to bypass it and
1831             // send the event directly to the window.
1832             if (anEvent.keyCode == kVK_Help)
1833             {
1834                 [anEvent.window sendEvent:anEvent];
1835                 ret = TRUE;
1836             }
1837         }
1838         else if (type == NSEventTypeKeyUp)
1839         {
1840             uint16_t keyCode = [anEvent keyCode];
1841             if ([self isKeyPressed:keyCode])
1842             {
1843                 WineWindow* window = (WineWindow*)[anEvent window];
1844                 [self noteKey:keyCode pressed:FALSE];
1845                 if ([window isKindOfClass:[WineWindow class]])
1846                     [window postKeyEvent:anEvent];
1847             }
1848         }
1849         else if (!useDragNotifications && type == NSEventTypeAppKitDefined)
1850         {
1851             WineWindow *window = (WineWindow *)[anEvent window];
1852             short subtype = [anEvent subtype];
1854             // These subtypes are not documented but they appear to mean
1855             // "a window is being dragged" and "a window is no longer being
1856             // dragged", respectively.
1857             if ((subtype == 20 || subtype == 21) && [window isKindOfClass:[WineWindow class]])
1858                 [self handleWindowDrag:window begin:(subtype == 20)];
1859         }
1861         return ret;
1862     }
1864     - (void) didSendEvent:(NSEvent*)anEvent
1865     {
1866         NSEventType type = [anEvent type];
1868         if (type == NSEventTypeKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1869         {
1870             NSUInteger modifiers = [anEvent modifierFlags];
1871             if ((modifiers & NSEventModifierFlagCommand) &&
1872                 !(modifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)))
1873             {
1874                 // Command-Tab and Command-Shift-Tab would normally be intercepted
1875                 // by the system to switch applications.  If we're seeing it, it's
1876                 // presumably because we've captured the displays, preventing
1877                 // normal application switching.  Do it manually.
1878                 [self handleCommandTab];
1879             }
1880         }
1881     }
1883     - (void) setupObservations
1884     {
1885         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1886         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1887         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
1889         [nc addObserverForName:NSWindowDidBecomeKeyNotification
1890                         object:nil
1891                          queue:nil
1892                     usingBlock:^(NSNotification *note){
1893             NSWindow* window = [note object];
1894             [keyWindows removeObjectIdenticalTo:window];
1895             [keyWindows insertObject:window atIndex:0];
1896         }];
1898         [nc addObserverForName:NSWindowWillCloseNotification
1899                         object:nil
1900                          queue:[NSOperationQueue mainQueue]
1901                     usingBlock:^(NSNotification *note){
1902             NSWindow* window = [note object];
1903             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
1904                 return;
1905             [keyWindows removeObjectIdenticalTo:window];
1906             if (window == lastTargetWindow)
1907                 lastTargetWindow = nil;
1908             if (window == self.mouseCaptureWindow)
1909                 self.mouseCaptureWindow = nil;
1910             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
1911             {
1912                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
1913                     [self updateFullscreenWindows];
1914                 });
1915             }
1916             [windowsBeingDragged removeObject:window];
1917         }];
1919         if (useDragNotifications) {
1920             [nc addObserverForName:NSWindowWillStartDraggingNotification
1921                             object:nil
1922                              queue:[NSOperationQueue mainQueue]
1923                         usingBlock:^(NSNotification *note){
1924                 NSWindow* window = [note object];
1925                 if ([window isKindOfClass:[WineWindow class]])
1926                     [self handleWindowDrag:(WineWindow *)window begin:YES];
1927             }];
1929             [nc addObserverForName:NSWindowDidEndDraggingNotification
1930                             object:nil
1931                              queue:[NSOperationQueue mainQueue]
1932                         usingBlock:^(NSNotification *note){
1933                 NSWindow* window = [note object];
1934                 if ([window isKindOfClass:[WineWindow class]])
1935                     [self handleWindowDrag:(WineWindow *)window begin:NO];
1936             }];
1937         }
1939         [nc addObserver:self
1940                selector:@selector(keyboardSelectionDidChange)
1941                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
1942                  object:nil];
1944         /* The above notification isn't sent unless the NSTextInputContext
1945            class has initialized itself.  Poke it. */
1946         [NSTextInputContext self];
1948         [wsnc addObserver:self
1949                  selector:@selector(activeSpaceDidChange)
1950                      name:NSWorkspaceActiveSpaceDidChangeNotification
1951                    object:nil];
1953         [nc addObserver:self
1954                selector:@selector(releaseMouseCapture)
1955                    name:NSMenuDidBeginTrackingNotification
1956                  object:nil];
1958         [dnc        addObserver:self
1959                        selector:@selector(releaseMouseCapture)
1960                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
1961                          object:nil
1962              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
1964         [dnc addObserver:self
1965                 selector:@selector(enabledKeyboardInputSourcesChanged)
1966                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
1967                   object:nil];
1969         if ([NSApplication instancesRespondToSelector:@selector(yieldActivationToApplication:)])
1970         {
1971             /* App activation cooperation, starting in macOS 14 Sonoma. */
1972             [dnc addObserver:self
1973                     selector:@selector(otherWineAppWillActivate:)
1974                         name:WineAppWillActivateNotification
1975                       object:nil
1976           suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
1977         }
1978     }
1980     - (void) otherWineAppWillActivate:(NSNotification *)note
1981     {
1982         NSProcessInfo *ourProcess;
1983         pid_t otherPID;
1984         NSString *ourConfigDir, *otherConfigDir, *ourPrefix, *otherPrefix;
1985         NSRunningApplication *otherApp;
1987         /* No point in yielding if we're not the foreground app. */
1988         if (![NSApp isActive]) return;
1990         /* Ignore requests from ourself, dead processes, and other prefixes. */
1991         ourProcess = [NSProcessInfo processInfo];
1992         otherPID = [note.userInfo[WineActivatingAppPIDKey] integerValue];
1993         if (otherPID == ourProcess.processIdentifier) return;
1995         otherApp = [NSRunningApplication runningApplicationWithProcessIdentifier:otherPID];
1996         if (!otherApp) return;
1998         ourConfigDir = ourProcess.environment[@"WINECONFIGDIR"];
1999         otherConfigDir = note.userInfo[WineActivatingAppConfigDirKey];
2000         if (ourConfigDir.length && otherConfigDir.length &&
2001             ![ourConfigDir isEqualToString:otherConfigDir])
2002         {
2003             return;
2004         }
2006         ourPrefix = ourProcess.environment[@"WINEPREFIX"];
2007         otherPrefix = note.userInfo[WineActivatingAppPrefixKey];
2008         if (ourPrefix.length && otherPrefix.length &&
2009             ![ourPrefix isEqualToString:otherPrefix])
2010         {
2011             return;
2012         }
2014         /* There's a race condition here. The requesting app sends out
2015            WineAppWillActivateNotification and then activates itself, but since
2016            distributed notifications are asynchronous, we may not have yielded
2017            in time. So we call activateFromApplication: on the other app here,
2018            which will work around that race if it happened. If we didn't hit the
2019            race, the activateFromApplication: call will be a no-op. */
2021         /* We only add this observer if NSApplication responds to the yield
2022            methods, so they're safe to call without checking here. */
2023         [NSApp yieldActivationToApplication:otherApp];
2024         [otherApp activateFromApplication:[NSRunningApplication currentApplication]
2025                                   options:0];
2026     }
2028     - (void) tryToActivateIgnoringOtherApps:(BOOL)ignore
2029     {
2030         NSProcessInfo *processInfo;
2031         NSString *configDir, *prefix;
2032         NSDictionary *userInfo;
2034         if ([NSApp isActive]) return;  /* Nothing to do. */
2036         if (!ignore ||
2037             ![NSApplication instancesRespondToSelector:@selector(yieldActivationToApplication:)])
2038         {
2039             /* Either we don't need to force activation, or the OS is old enough
2040                that this is our only option. */
2041             [NSApp activateIgnoringOtherApps:ignore];
2042             return;
2043         }
2045         /* Ask other Wine apps to yield activation to us. */
2046         processInfo = [NSProcessInfo processInfo];
2047         configDir = processInfo.environment[@"WINECONFIGDIR"];
2048         prefix = processInfo.environment[@"WINEPREFIX"];
2049         userInfo = @{
2050             WineActivatingAppPIDKey: @(processInfo.processIdentifier),
2051             WineActivatingAppPrefixKey: prefix ? prefix : @"",
2052             WineActivatingAppConfigDirKey: configDir ? configDir : @""
2053         };
2055         [[NSDistributedNotificationCenter defaultCenter]
2056             postNotificationName:WineAppWillActivateNotification
2057                           object:nil
2058                         userInfo:userInfo
2059               deliverImmediately:YES];
2061         /* This is racy. See the note in otherWineAppWillActivate:. */
2062         [NSApp activate];
2063      }
2065     - (BOOL) inputSourceIsInputMethod
2066     {
2067         if (!inputSourceIsInputMethodValid)
2068         {
2069             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2070             if (inputSource)
2071             {
2072                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2073                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2074                 CFRelease(inputSource);
2075             }
2076             else
2077                 inputSourceIsInputMethod = FALSE;
2078             inputSourceIsInputMethodValid = TRUE;
2079         }
2081         return inputSourceIsInputMethod;
2082     }
2084     - (void) releaseMouseCapture
2085     {
2086         // This might be invoked on a background thread by the distributed
2087         // notification center.  Shunt it to the main thread.
2088         if (![NSThread isMainThread])
2089         {
2090             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2091             return;
2092         }
2094         if (mouseCaptureWindow)
2095         {
2096             macdrv_event* event;
2098             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2099             [mouseCaptureWindow.queue postEvent:event];
2100             macdrv_release_event(event);
2101         }
2102     }
2104     - (void) unminimizeWindowIfNoneVisible
2105     {
2106         if (![self frontWineWindow])
2107         {
2108             for (WineWindow* window in [NSApp windows])
2109             {
2110                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2111                 {
2112                     [window deminiaturize:self];
2113                     break;
2114                 }
2115             }
2116         }
2117     }
2119     - (void) setRetinaMode:(int)mode
2120     {
2121         retina_on = mode;
2123         [clipCursorHandler setRetinaMode:mode];
2125         for (WineWindow* window in [NSApp windows])
2126         {
2127             if ([window isKindOfClass:[WineWindow class]])
2128                 [window setRetinaMode:mode];
2129         }
2130     }
2133     /*
2134      * ---------- NSApplicationDelegate methods ----------
2135      */
2136     - (void)applicationDidBecomeActive:(NSNotification *)notification
2137     {
2138         NSNumber* displayID;
2139         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2141         latentDisplayModes = [[NSMutableDictionary alloc] init];
2142         for (displayID in modesToRealize)
2143         {
2144             CGDisplayModeRef mode = (CGDisplayModeRef)modesToRealize[displayID];
2145             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2146         }
2148         [self updateFullscreenWindows];
2149         [self adjustWindowLevels:YES];
2151         if (beenActive)
2152             [self unminimizeWindowIfNoneVisible];
2153         beenActive = TRUE;
2155         // If a Wine process terminates abruptly while it has the display captured
2156         // and switched to a different resolution, Mac OS X will uncapture the
2157         // displays and switch their resolutions back.  However, the other Wine
2158         // processes won't have their notion of the desktop rect changed back.
2159         // This can lead them to refuse to draw or acknowledge clicks in certain
2160         // portions of their windows.
2161         //
2162         // To solve this, we synthesize a displays-changed event whenever we're
2163         // activated.  This will provoke a re-synchronization of Wine's notion of
2164         // the desktop rect with the actual state.
2165         [self sendDisplaysChanged:TRUE];
2167         // The cursor probably moved while we were inactive.  Accumulated mouse
2168         // movement deltas are invalidated.  Make sure the next mouse move event
2169         // starts over from an absolute baseline.
2170         forceNextMouseMoveAbsolute = TRUE;
2171     }
2173     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2174     {
2175         primaryScreenHeightValid = FALSE;
2176         [self sendDisplaysChanged:FALSE];
2177         [self adjustWindowLevels];
2179         // When the display configuration changes, the cursor position may jump.
2180         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2181         // mouse move event starts over from an absolute baseline.
2182         forceNextMouseMoveAbsolute = TRUE;
2183     }
2185     - (void)applicationDidResignActive:(NSNotification *)notification
2186     {
2187         macdrv_event* event;
2188         WineEventQueue* queue;
2190         [self invalidateGotFocusEvents];
2192         event = macdrv_create_event(APP_DEACTIVATED, nil);
2194         [eventQueuesLock lock];
2195         for (queue in eventQueues)
2196             [queue postEvent:event];
2197         [eventQueuesLock unlock];
2199         macdrv_release_event(event);
2201         [self releaseMouseCapture];
2202     }
2204     - (void) applicationDidUnhide:(NSNotification*)aNotification
2205     {
2206         [self adjustWindowLevels];
2207     }
2209     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2210     {
2211         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2212         // don't count as "visible windows" for this purpose.
2213         [self unminimizeWindowIfNoneVisible];
2214         return YES;
2215     }
2217     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2218     {
2219         NSApplicationTerminateReply ret = NSTerminateNow;
2220         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2221         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2222         macdrv_event* event;
2223         WineEventQueue* queue;
2225         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2226         event->deliver = 1;
2227         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2228         {
2229             case kAELogOut:
2230             case kAEReallyLogOut:
2231                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2232                 break;
2233             case kAEShowRestartDialog:
2234                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2235                 break;
2236             case kAEShowShutdownDialog:
2237                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2238                 break;
2239             default:
2240                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2241                 break;
2242         }
2244         [eventQueuesLock lock];
2246         if ([eventQueues count])
2247         {
2248             for (queue in eventQueues)
2249                 [queue postEvent:event];
2250             ret = NSTerminateLater;
2251         }
2253         [eventQueuesLock unlock];
2255         macdrv_release_event(event);
2257         return ret;
2258     }
2260     - (void)applicationWillBecomeActive:(NSNotification *)notification
2261     {
2262         macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2263         event->deliver = 1;
2265         [eventQueuesLock lock];
2266         for (WineEventQueue* queue in eventQueues)
2267             [queue postEvent:event];
2268         [eventQueuesLock unlock];
2270         macdrv_release_event(event);
2271     }
2273     - (void)applicationWillResignActive:(NSNotification *)notification
2274     {
2275         [self adjustWindowLevels:NO];
2276     }
2278 /***********************************************************************
2279  *              PerformRequest
2281  * Run-loop-source perform callback.  Pull request blocks from the
2282  * array of queued requests and invoke them.
2283  */
2284 static void PerformRequest(void *info)
2286 @autoreleasepool
2288     WineApplicationController* controller = [WineApplicationController sharedController];
2290     for (;;)
2291     {
2292         @autoreleasepool
2293         {
2294             __block dispatch_block_t block;
2296             dispatch_sync(controller->requestsManipQueue, ^{
2297                 if ([controller->requests count])
2298                 {
2299                     block = (dispatch_block_t)[controller->requests[0] retain];
2300                     [controller->requests removeObjectAtIndex:0];
2301                 }
2302                 else
2303                     block = nil;
2304             });
2306             if (!block)
2307                 break;
2309             block();
2310             [block release];
2311         }
2312     }
2316 /***********************************************************************
2317  *              OnMainThreadAsync
2319  * Run a block on the main thread asynchronously.
2320  */
2321 void OnMainThreadAsync(dispatch_block_t block)
2323     WineApplicationController* controller = [WineApplicationController sharedController];
2325     block = [block copy];
2326     dispatch_sync(controller->requestsManipQueue, ^{
2327         [controller->requests addObject:block];
2328     });
2329     [block release];
2330     CFRunLoopSourceSignal(controller->requestSource);
2331     CFRunLoopWakeUp(CFRunLoopGetMain());
2334 @end
2336 /***********************************************************************
2337  *              LogError
2338  */
2339 void LogError(const char* func, NSString* format, ...)
2341     va_list args;
2342     va_start(args, format);
2343     LogErrorv(func, format, args);
2344     va_end(args);
2347 /***********************************************************************
2348  *              LogErrorv
2349  */
2350 void LogErrorv(const char* func, NSString* format, va_list args)
2352 @autoreleasepool
2354     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2355     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2356     [message release];
2360 /***********************************************************************
2361  *              macdrv_window_rejected_focus
2363  * Pass focus to the next window that hasn't already rejected this same
2364  * WINDOW_GOT_FOCUS event.
2365  */
2366 void macdrv_window_rejected_focus(const macdrv_event *event)
2368     OnMainThread(^{
2369         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2370     });
2373 /***********************************************************************
2374  *              macdrv_get_input_source_info
2376  * Returns the keyboard layout uchr data, keyboard type and input source.
2377  */
2378 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2380     OnMainThread(^{
2381         TISInputSourceRef inputSourceLayout;
2383         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2384         if (inputSourceLayout)
2385         {
2386             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2387                                 kTISPropertyUnicodeKeyLayoutData);
2388             *uchr = CFDataCreateCopy(NULL, data);
2389             CFRelease(inputSourceLayout);
2391             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2392             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2393             if (input_source)
2394                 *input_source = TISCopyCurrentKeyboardInputSource();
2395         }
2396     });
2399 /***********************************************************************
2400  *              macdrv_beep
2402  * Play the beep sound configured by the user in System Preferences.
2403  */
2404 void macdrv_beep(void)
2406     OnMainThreadAsync(^{
2407         NSBeep();
2408     });
2411 /***********************************************************************
2412  *              macdrv_set_display_mode
2413  */
2414 int macdrv_set_display_mode(const struct macdrv_display* display,
2415                             CGDisplayModeRef display_mode)
2417     __block int ret;
2419     OnMainThread(^{
2420         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2421     });
2423     return ret;
2426 /***********************************************************************
2427  *              macdrv_set_cursor
2429  * Set the cursor.
2431  * If name is non-NULL, it is a selector for a class method on NSCursor
2432  * identifying the cursor to set.  In that case, frames is ignored.  If
2433  * name is NULL, then frames is used.
2435  * frames is an array of dictionaries.  Each dictionary is a frame of
2436  * an animated cursor.  Under the key "image" is a CGImage for the
2437  * frame.  Under the key "duration" is a CFNumber time interval, in
2438  * seconds, for how long that frame is presented before proceeding to
2439  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2440  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2441  * This is the hot spot, measured in pixels down and to the right of the
2442  * top-left corner of the image.
2444  * If the array has exactly 1 element, the cursor is static, not
2445  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2446  */
2447 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2449     SEL sel;
2451     sel = NSSelectorFromString((NSString*)name);
2452     if (sel)
2453     {
2454         OnMainThreadAsync(^{
2455             WineApplicationController* controller = [WineApplicationController sharedController];
2456             [controller setCursorWithFrames:nil];
2457             controller.cursor = [NSCursor performSelector:sel];
2458             [controller unhideCursor];
2459         });
2460     }
2461     else
2462     {
2463         NSArray* nsframes = (NSArray*)frames;
2464         if ([nsframes count])
2465         {
2466             OnMainThreadAsync(^{
2467                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2468             });
2469         }
2470         else
2471         {
2472             OnMainThreadAsync(^{
2473                 WineApplicationController* controller = [WineApplicationController sharedController];
2474                 [controller setCursorWithFrames:nil];
2475                 [controller hideCursor];
2476             });
2477         }
2478     }
2481 /***********************************************************************
2482  *              macdrv_get_cursor_position
2484  * Obtains the current cursor position.  Returns zero on failure,
2485  * non-zero on success.
2486  */
2487 int macdrv_get_cursor_position(CGPoint *pos)
2489     OnMainThread(^{
2490         NSPoint location = [NSEvent mouseLocation];
2491         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2492         *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2493     });
2495     return TRUE;
2498 /***********************************************************************
2499  *              macdrv_set_cursor_position
2501  * Sets the cursor position without generating events.  Returns zero on
2502  * failure, non-zero on success.
2503  */
2504 int macdrv_set_cursor_position(CGPoint pos)
2506     __block int ret;
2508     OnMainThread(^{
2509         ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2510     });
2512     return ret;
2515 /***********************************************************************
2516  *              macdrv_clip_cursor
2518  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2519  * to or larger than the whole desktop region, the cursor is unclipped.
2520  * Returns zero on failure, non-zero on success.
2521  */
2522 int macdrv_clip_cursor(CGRect r)
2524     __block int ret;
2526     OnMainThread(^{
2527         WineApplicationController* controller = [WineApplicationController sharedController];
2528         BOOL clipping = FALSE;
2529         CGRect rect = r;
2531         if (!CGRectIsInfinite(rect))
2532             rect = cgrect_mac_from_win(rect);
2534         if (!CGRectIsInfinite(rect))
2535         {
2536             NSRect nsrect = NSRectFromCGRect(rect);
2537             NSScreen* screen;
2539             /* Convert the rectangle from top-down coords to bottom-up. */
2540             [controller flipRect:&nsrect];
2542             clipping = FALSE;
2543             for (screen in [NSScreen screens])
2544             {
2545                 if (!NSContainsRect(nsrect, [screen frame]))
2546                 {
2547                     clipping = TRUE;
2548                     break;
2549                 }
2550             }
2551         }
2553         if (clipping)
2554             ret = [controller startClippingCursor:rect];
2555         else
2556             ret = [controller stopClippingCursor];
2557     });
2559     return ret;
2562 /***********************************************************************
2563  *              macdrv_set_application_icon
2565  * Set the application icon.  The images array contains CGImages.  If
2566  * there are more than one, then they represent different sizes or
2567  * color depths from the icon resource.  If images is NULL or empty,
2568  * restores the default application image.
2569  */
2570 void macdrv_set_application_icon(CFArrayRef images)
2572     NSArray* imageArray = (NSArray*)images;
2574     OnMainThreadAsync(^{
2575         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2576     });
2579 /***********************************************************************
2580  *              macdrv_quit_reply
2581  */
2582 void macdrv_quit_reply(int reply)
2584     OnMainThread(^{
2585         [NSApp replyToApplicationShouldTerminate:reply];
2586     });
2589 /***********************************************************************
2590  *              macdrv_using_input_method
2591  */
2592 int macdrv_using_input_method(void)
2594     __block BOOL ret;
2596     OnMainThread(^{
2597         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2598     });
2600     return ret;
2603 /***********************************************************************
2604  *              macdrv_set_mouse_capture_window
2605  */
2606 void macdrv_set_mouse_capture_window(macdrv_window window)
2608     WineWindow* w = (WineWindow*)window;
2610     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2612     OnMainThread(^{
2613         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2614     });
2617 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2618 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2619 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2621 /***********************************************************************
2622  *              macdrv_create_input_source_list
2623  */
2624 CFArrayRef macdrv_create_input_source_list(void)
2626     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2628     OnMainThread(^{
2629         CFArrayRef input_list;
2630         CFDictionaryRef filter_dict;
2631         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2632         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2633         int i;
2635         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2636                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2637         input_list = TISCreateInputSourceList(filter_dict, false);
2639         for (i = 0; i < CFArrayGetCount(input_list); i++)
2640         {
2641             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2642             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2643             CFDictionaryRef entry;
2644             const void *input_keys[3] = { macdrv_input_source_input_key,
2645                                           macdrv_input_source_type_key,
2646                                           macdrv_input_source_lang_key };
2647             const void *input_values[3];
2649             input_values[0] = input;
2650             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2651             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2653             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2654                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2656             CFArrayAppendValue(ret, entry);
2657             CFRelease(entry);
2658         }
2659         CFRelease(input_list);
2660         CFRelease(filter_dict);
2661     });
2663     return ret;
2666 int macdrv_select_input_source(TISInputSourceRef input_source)
2668     __block int ret = FALSE;
2670     OnMainThread(^{
2671         ret = (TISSelectInputSource(input_source) == noErr);
2672     });
2674     return ret;
2677 void macdrv_set_cocoa_retina_mode(int new_mode)
2679     OnMainThread(^{
2680         [[WineApplicationController sharedController] setRetinaMode:new_mode];
2681     });
2684 int macdrv_is_any_wine_window_visible(void)
2686     __block int ret = FALSE;
2688     OnMainThread(^{
2689         ret = [[WineApplicationController sharedController] isAnyWineWindowVisible];
2690     });
2692     return ret;