winecoreaudio: Use mmdevapi's AudioClient's GetService.
[wine.git] / dlls / winemac.drv / cocoa_app.m
blob6a16d1ba8323d563a5c2f71d5d78a1797223dfb2
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";
38 int macdrv_err_on;
41 #if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
42 @interface NSWindow (WineAutoTabbingExtensions)
44     + (void) setAllowsAutomaticWindowTabbing:(BOOL)allows;
46 @end
47 #endif
50 /***********************************************************************
51  *              WineLocalizedString
52  *
53  * Look up a localized string by its ID in the dictionary.
54  */
55 static NSString* WineLocalizedString(unsigned int stringID)
57     NSNumber* key = [NSNumber numberWithUnsignedInt:stringID];
58     return [(NSDictionary*)localized_strings objectForKey:key];
62 @implementation WineApplication
64 @synthesize wineController;
66     - (void) sendEvent:(NSEvent*)anEvent
67     {
68         if (![wineController handleEvent:anEvent])
69         {
70             [super sendEvent:anEvent];
71             [wineController didSendEvent:anEvent];
72         }
73     }
75     - (void) setWineController:(WineApplicationController*)newController
76     {
77         wineController = newController;
78         [self setDelegate:wineController];
79     }
81 @end
84 @interface WineApplicationController ()
86 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
87 @property (copy, nonatomic) NSArray* cursorFrames;
88 @property (retain, nonatomic) NSTimer* cursorTimer;
89 @property (retain, nonatomic) NSCursor* cursor;
90 @property (retain, nonatomic) NSImage* applicationIcon;
91 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
92 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
94     - (void) setupObservations;
95     - (void) applicationDidBecomeActive:(NSNotification *)notification;
97     static void PerformRequest(void *info);
99 @end
102 @implementation WineApplicationController
104     @synthesize keyboardType, lastFlagsChanged;
105     @synthesize applicationIcon;
106     @synthesize cursorFrames, cursorTimer, cursor;
107     @synthesize mouseCaptureWindow;
108     @synthesize lastSetCursorPositionTime;
110     + (void) initialize
111     {
112         if (self == [WineApplicationController class])
113         {
114             NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
115                                       @"", @"NSQuotedKeystrokeBinding",
116                                       @"", @"NSRepeatCountBinding",
117                                       [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
118                                       nil];
119             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
121             if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
122                 [NSWindow setAllowsAutomaticWindowTabbing:NO];
123         }
124     }
126     + (WineApplicationController*) sharedController
127     {
128         static WineApplicationController* sharedController;
129         static dispatch_once_t once;
131         dispatch_once(&once, ^{
132             sharedController = [[self alloc] init];
133         });
135         return sharedController;
136     }
138     - (id) init
139     {
140         self = [super init];
141         if (self != nil)
142         {
143             CFRunLoopSourceContext context = { 0 };
144             context.perform = PerformRequest;
145             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
146             if (!requestSource)
147             {
148                 [self release];
149                 return nil;
150             }
151             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
152             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
154             requests =  [[NSMutableArray alloc] init];
155             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
157             eventQueues = [[NSMutableArray alloc] init];
158             eventQueuesLock = [[NSLock alloc] init];
160             keyWindows = [[NSMutableArray alloc] init];
162             originalDisplayModes = [[NSMutableDictionary alloc] init];
163             latentDisplayModes = [[NSMutableDictionary alloc] init];
165             windowsBeingDragged = [[NSMutableSet alloc] init];
167             // On macOS 10.12+, use notifications to more reliably detect when windows are being dragged.
168             if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)])
169             {
170                 NSOperatingSystemVersion requiredVersion = { 10, 12, 0 };
171                 useDragNotifications = [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:requiredVersion];
172             }
173             else
174                 useDragNotifications = NO;
176             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
177                 !keyWindows || !originalDisplayModes || !latentDisplayModes)
178             {
179                 [self release];
180                 return nil;
181             }
183             [self setupObservations];
185             keyboardType = LMGetKbdType();
187             if ([NSApp isActive])
188                 [self applicationDidBecomeActive:nil];
189         }
190         return self;
191     }
193     - (void) dealloc
194     {
195         [windowsBeingDragged release];
196         [cursor release];
197         [screenFrameCGRects release];
198         [applicationIcon release];
199         [clipCursorHandler release];
200         [cursorTimer release];
201         [cursorFrames release];
202         [latentDisplayModes release];
203         [originalDisplayModes release];
204         [keyWindows release];
205         [eventQueues release];
206         [eventQueuesLock release];
207         if (requestsManipQueue) dispatch_release(requestsManipQueue);
208         [requests release];
209         if (requestSource)
210         {
211             CFRunLoopSourceInvalidate(requestSource);
212             CFRelease(requestSource);
213         }
214         [super dealloc];
215     }
217     - (void) transformProcessToForeground:(BOOL)activateIfTransformed
218     {
219         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
220         {
221             NSMenu* mainMenu;
222             NSMenu* submenu;
223             NSString* bundleName;
224             NSString* title;
225             NSMenuItem* item;
227             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
229             if (activateIfTransformed)
230                 [NSApp activateIgnoringOtherApps:YES];
232 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
233             if (!enable_app_nap && [NSProcessInfo instancesRespondToSelector:@selector(beginActivityWithOptions:reason:)])
234             {
235                 [[[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep
236                                                                 reason:@"Running Windows program"] retain]; // intentional leak
237             }
238 #endif
240             mainMenu = [[[NSMenu alloc] init] autorelease];
242             // Application menu
243             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
244             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
246             if ([bundleName length])
247                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
248             else
249                 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
250             item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
252             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
253                                       action:@selector(hideOtherApplications:)
254                                keyEquivalent:@"h"];
255             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
257             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
258                                       action:@selector(unhideAllApplications:)
259                                keyEquivalent:@""];
261             [submenu addItem:[NSMenuItem separatorItem]];
263             if ([bundleName length])
264                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
265             else
266                 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
267             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
268             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
269             item = [[[NSMenuItem alloc] init] autorelease];
270             [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
271             [item setSubmenu:submenu];
272             [mainMenu addItem:item];
274             // Window menu
275             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
276             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
277                                action:@selector(performMiniaturize:)
278                         keyEquivalent:@""];
279             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
280                                action:@selector(performZoom:)
281                         keyEquivalent:@""];
282             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
283                                       action:@selector(toggleFullScreen:)
284                                keyEquivalent:@"f"];
285             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand |
286                                                NSEventModifierFlagOption |
287                                                NSEventModifierFlagControl];
288             [submenu addItem:[NSMenuItem separatorItem]];
289             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
290                                action:@selector(arrangeInFront:)
291                         keyEquivalent:@""];
292             item = [[[NSMenuItem alloc] init] autorelease];
293             [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
294             [item setSubmenu:submenu];
295             [mainMenu addItem:item];
297             [NSApp setMainMenu:mainMenu];
298             [NSApp setWindowsMenu:submenu];
300             [NSApp setApplicationIconImage:self.applicationIcon];
301         }
302     }
304     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
305     {
306         PerformRequest(NULL);
308         do
309         {
310             if (processEvents)
311             {
312                 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
313                 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
314                                                     untilDate:timeout
315                                                        inMode:NSDefaultRunLoopMode
316                                                       dequeue:YES];
317                 if (event)
318                     [NSApp sendEvent:event];
319                 [pool release];
320             }
321             else
322                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
323         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
325         return *done;
326     }
328     - (BOOL) registerEventQueue:(WineEventQueue*)queue
329     {
330         [eventQueuesLock lock];
331         [eventQueues addObject:queue];
332         [eventQueuesLock unlock];
333         return TRUE;
334     }
336     - (void) unregisterEventQueue:(WineEventQueue*)queue
337     {
338         [eventQueuesLock lock];
339         [eventQueues removeObjectIdenticalTo:queue];
340         [eventQueuesLock unlock];
341     }
343     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
344     {
345         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
346     }
348     - (double) ticksForEventTime:(NSTimeInterval)eventTime
349     {
350         return (eventTime + eventTimeAdjustment) * 1000;
351     }
353     /* Invalidate old focus offers across all queues. */
354     - (void) invalidateGotFocusEvents
355     {
356         WineEventQueue* queue;
358         windowFocusSerial++;
360         [eventQueuesLock lock];
361         for (queue in eventQueues)
362         {
363             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
364                                    forWindow:nil];
365         }
366         [eventQueuesLock unlock];
367     }
369     - (void) windowGotFocus:(WineWindow*)window
370     {
371         macdrv_event* event;
373         [self invalidateGotFocusEvents];
375         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
376         event->window_got_focus.serial = windowFocusSerial;
377         if (triedWindows)
378             event->window_got_focus.tried_windows = [triedWindows retain];
379         else
380             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
381         [window.queue postEvent:event];
382         macdrv_release_event(event);
383     }
385     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
386     {
387         if (event->window_got_focus.serial == windowFocusSerial)
388         {
389             NSMutableArray* windows = [keyWindows mutableCopy];
390             NSNumber* windowNumber;
391             WineWindow* window;
393             for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
394             {
395                 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
396                 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
397                     ![windows containsObject:window])
398                     [windows addObject:window];
399             }
401             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
402             [triedWindows addObject:(WineWindow*)event->window];
403             for (window in windows)
404             {
405                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
406                 {
407                     [window makeKeyWindow];
408                     break;
409                 }
410             }
411             triedWindows = nil;
412             [windows release];
413         }
414     }
416     static BOOL EqualInputSource(TISInputSourceRef source1, TISInputSourceRef source2)
417     {
418         if (!source1 && !source2)
419             return TRUE;
420         if (!source1 || !source2)
421             return FALSE;
422         return CFEqual(source1, source2);
423     }
425     - (void) keyboardSelectionDidChange:(BOOL)force
426     {
427         TISInputSourceRef inputSource, inputSourceLayout;
429         if (!force)
430         {
431             NSTextInputContext* context = [NSTextInputContext currentInputContext];
432             if (!context || ![context client])
433                 return;
434         }
436         inputSource = TISCopyCurrentKeyboardInputSource();
437         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
438         if (!force && EqualInputSource(inputSource, lastKeyboardInputSource) &&
439             EqualInputSource(inputSourceLayout, lastKeyboardLayoutInputSource))
440         {
441             if (inputSource) CFRelease(inputSource);
442             if (inputSourceLayout) CFRelease(inputSourceLayout);
443             return;
444         }
446         if (lastKeyboardInputSource)
447             CFRelease(lastKeyboardInputSource);
448         lastKeyboardInputSource = inputSource;
449         if (lastKeyboardLayoutInputSource)
450             CFRelease(lastKeyboardLayoutInputSource);
451         lastKeyboardLayoutInputSource = inputSourceLayout;
453         inputSourceIsInputMethodValid = FALSE;
455         if (inputSourceLayout)
456         {
457             CFDataRef uchr;
458             uchr = TISGetInputSourceProperty(inputSourceLayout,
459                     kTISPropertyUnicodeKeyLayoutData);
460             if (uchr)
461             {
462                 macdrv_event* event;
463                 WineEventQueue* queue;
465                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
466                 event->keyboard_changed.keyboard_type = self.keyboardType;
467                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
468                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
469                 event->keyboard_changed.input_source = (TISInputSourceRef)CFRetain(inputSource);
471                 if (event->keyboard_changed.uchr)
472                 {
473                     [eventQueuesLock lock];
475                     for (queue in eventQueues)
476                         [queue postEvent:event];
478                     [eventQueuesLock unlock];
479                 }
481                 macdrv_release_event(event);
482             }
483         }
484     }
486     - (void) keyboardSelectionDidChange
487     {
488         [self keyboardSelectionDidChange:NO];
489     }
491     - (void) setKeyboardType:(CGEventSourceKeyboardType)newType
492     {
493         if (newType != keyboardType)
494         {
495             keyboardType = newType;
496             [self keyboardSelectionDidChange:YES];
497         }
498     }
500     - (void) enabledKeyboardInputSourcesChanged
501     {
502         macdrv_layout_list_needs_update = TRUE;
503     }
505     - (CGFloat) primaryScreenHeight
506     {
507         if (!primaryScreenHeightValid)
508         {
509             NSArray* screens = [NSScreen screens];
510             NSUInteger count = [screens count];
511             if (count)
512             {
513                 NSUInteger size;
514                 CGRect* rect;
515                 NSScreen* screen;
517                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
518                 primaryScreenHeightValid = TRUE;
520                 size = count * sizeof(CGRect);
521                 if (!screenFrameCGRects)
522                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
523                 else
524                     [screenFrameCGRects setLength:size];
526                 rect = [screenFrameCGRects mutableBytes];
527                 for (screen in screens)
528                 {
529                     CGRect temp = NSRectToCGRect([screen frame]);
530                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
531                     *rect++ = temp;
532                 }
533             }
534             else
535                 return 1280; /* arbitrary value */
536         }
538         return primaryScreenHeight;
539     }
541     - (NSPoint) flippedMouseLocation:(NSPoint)point
542     {
543         /* This relies on the fact that Cocoa's mouse location points are
544            actually off by one (precisely because they were flipped from
545            Quartz screen coordinates using this same technique). */
546         point.y = [self primaryScreenHeight] - point.y;
547         return point;
548     }
550     - (void) flipRect:(NSRect*)rect
551     {
552         // We don't use -primaryScreenHeight here so there's no chance of having
553         // out-of-date cached info.  This method is called infrequently enough
554         // that getting the screen height each time is not prohibitively expensive.
555         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
556     }
558     - (WineWindow*) frontWineWindow
559     {
560         NSNumber* windowNumber;
561         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
562         {
563             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
564             if ([window isKindOfClass:[WineWindow class]] && [window screen])
565                 return (WineWindow*)window;
566         }
568         return nil;
569     }
571     - (void) adjustWindowLevels:(BOOL)active
572     {
573         NSArray* windowNumbers;
574         NSMutableArray* wineWindows;
575         NSNumber* windowNumber;
576         NSUInteger nextFloatingIndex = 0;
577         __block NSInteger maxLevel = NSIntegerMin;
578         __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
579         __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
580         __block WineWindow* prev = nil;
581         WineWindow* window;
583         if ([NSApp isHidden]) return;
585         windowNumbers = [NSWindow windowNumbersWithOptions:0];
586         wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
588         // For the most part, we rely on the window server's ordering of the windows
589         // to be authoritative.  The one exception is if the "floating" property of
590         // one of the windows has been changed, it may be in the wrong level and thus
591         // in the order.  This method is what's supposed to fix that up.  So build
592         // a list of Wine windows sorted first by floating-ness and then by order
593         // as indicated by the window server.
594         for (windowNumber in windowNumbers)
595         {
596             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
597             if ([window isKindOfClass:[WineWindow class]])
598             {
599                 if (window.floating)
600                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
601                 else
602                     [wineWindows addObject:window];
603             }
604         }
606         NSDisableScreenUpdates();
608         // Go from back to front so that all windows in front of one which is
609         // elevated for full-screen are also elevated.
610         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
611                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
612             WineWindow* window = (WineWindow*)obj;
613             NSInteger origLevel = [window level];
614             NSInteger newLevel = [window minimumLevelForActive:active];
616             if (window.floating)
617             {
618                 if (minFloatingLevel <= maxNonfloatingLevel)
619                     minFloatingLevel = maxNonfloatingLevel + 1;
620                 if (newLevel < minFloatingLevel)
621                     newLevel = minFloatingLevel;
622             }
624             if (newLevel < maxLevel)
625                 newLevel = maxLevel;
626             else
627                 maxLevel = newLevel;
629             if (!window.floating && maxNonfloatingLevel < newLevel)
630                 maxNonfloatingLevel = newLevel;
632             if (newLevel != origLevel)
633             {
634                 [window setLevel:newLevel];
636                 if (origLevel < newLevel)
637                 {
638                     // If we increased the level, the window should be toward the
639                     // back of its new level (but still ahead of the previous
640                     // windows we did this to).
641                     if (prev)
642                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
643                     else
644                         [window orderBack:nil];
645                 }
646                 else
647                 {
648                     // If we decreased the level, we want the window at the top
649                     // of its new level. -setLevel: is documented to do that on
650                     // its own, but that's buggy on Ventura. Since we're looping
651                     // back-to-front here, -orderFront: will do the right thing.
652                     [window orderFront:nil];
653                 }
654             }
656             prev = window;
657         }];
659         NSEnableScreenUpdates();
661         [wineWindows release];
663         // The above took care of the visible windows on the current space.  That
664         // leaves windows on other spaces, minimized windows, and windows which
665         // are not ordered in.  We want to leave windows on other spaces alone
666         // so the space remains just as they left it (when viewed in Exposé or
667         // Mission Control, for example).  We'll adjust the window levels again
668         // after we switch to another space, anyway.  Windows which aren't
669         // ordered in will be handled when we order them in.  Minimized windows
670         // on the current space should be set to the level they would have gotten
671         // if they were at the front of the windows with the same floating-ness,
672         // because that's where they'll go if/when they are unminimized.  Again,
673         // for good measure we'll adjust window levels again when a window is
674         // unminimized, too.
675         for (window in [NSApp windows])
676         {
677             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
678                 [window isOnActiveSpace])
679             {
680                 NSInteger origLevel = [window level];
681                 NSInteger newLevel = [window minimumLevelForActive:YES];
682                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
684                 if (newLevel < maxLevelForType)
685                     newLevel = maxLevelForType;
687                 if (newLevel != origLevel)
688                     [window setLevel:newLevel];
689             }
690         }
691     }
693     - (void) adjustWindowLevels
694     {
695         [self adjustWindowLevels:[NSApp isActive]];
696     }
698     - (void) updateFullscreenWindows
699     {
700         if (capture_displays_for_fullscreen && [NSApp isActive])
701         {
702             BOOL anyFullscreen = FALSE;
703             NSNumber* windowNumber;
704             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
705             {
706                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
707                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
708                 {
709                     anyFullscreen = TRUE;
710                     break;
711                 }
712             }
714             if (anyFullscreen)
715             {
716                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
717                     displaysCapturedForFullscreen = TRUE;
718             }
719             else if (displaysCapturedForFullscreen)
720             {
721                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
722                     displaysCapturedForFullscreen = FALSE;
723             }
724         }
725     }
727     - (void) activeSpaceDidChange
728     {
729         [self updateFullscreenWindows];
730         [self adjustWindowLevels];
731     }
733     - (void) sendDisplaysChanged:(BOOL)activating
734     {
735         macdrv_event* event;
736         WineEventQueue* queue;
738         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
739         event->displays_changed.activating = activating;
741         [eventQueuesLock lock];
743         // If we're activating, then we just need one of our threads to get the
744         // event, so it can send it directly to the desktop window.  Otherwise,
745         // we need all of the threads to get it because we don't know which owns
746         // the desktop window and only that one will do anything with it.
747         if (activating) event->deliver = 1;
749         for (queue in eventQueues)
750             [queue postEvent:event];
751         [eventQueuesLock unlock];
753         macdrv_release_event(event);
754     }
756     // We can compare two modes directly using CFEqual, but that may require that
757     // they are identical to a level that we don't need.  In particular, when the
758     // OS switches between the integrated and discrete GPUs, the set of display
759     // modes can change in subtle ways.  We're interested in whether two modes
760     // match in their most salient features, even if they aren't identical.
761     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
762     {
763         NSString *encoding1, *encoding2;
764         uint32_t ioflags1, ioflags2, different;
765         double refresh1, refresh2;
767         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
768         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
769         if (CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
770         if (CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
772         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
773         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
774         if (![encoding1 isEqualToString:encoding2]) return FALSE;
776         ioflags1 = CGDisplayModeGetIOFlags(mode1);
777         ioflags2 = CGDisplayModeGetIOFlags(mode2);
778         different = ioflags1 ^ ioflags2;
779         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
780                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
781             return FALSE;
783         refresh1 = CGDisplayModeGetRefreshRate(mode1);
784         if (refresh1 == 0) refresh1 = 60;
785         refresh2 = CGDisplayModeGetRefreshRate(mode2);
786         if (refresh2 == 0) refresh2 = 60;
787         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
789         return TRUE;
790     }
792     - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
793     {
794         NSMutableArray* ret = [NSMutableArray array];
795         NSDictionary* options = @{ (NSString*)kCGDisplayShowDuplicateLowResolutionModes: @YES };
797         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
798         for (id candidateModeObject in modes)
799         {
800             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
801             if ([self mode:candidateMode matchesMode:mode])
802                 [ret addObject:candidateModeObject];
803         }
804         return ret;
805     }
807     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
808     {
809         BOOL ret = FALSE;
810         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
811         CGDisplayModeRef originalMode;
813         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
815         if (originalMode && [self mode:mode matchesMode:originalMode])
816         {
817             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
818             {
819                 CGRestorePermanentDisplayConfiguration();
820                 if (!displaysCapturedForFullscreen)
821                     CGReleaseAllDisplays();
822                 [originalDisplayModes removeAllObjects];
823                 ret = TRUE;
824             }
825             else // ... otherwise, try to restore just the one display
826             {
827                 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
828                 {
829                     mode = (CGDisplayModeRef)modeObject;
830                     if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
831                     {
832                         [originalDisplayModes removeObjectForKey:displayIDKey];
833                         ret = TRUE;
834                         break;
835                     }
836                 }
837             }
838         }
839         else
840         {
841             CGDisplayModeRef currentMode;
842             NSArray* modes;
844             currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
845             if (!currentMode)
846                 currentMode = CGDisplayCopyDisplayMode(displayID);
847             if (!currentMode) // Invalid display ID
848                 return FALSE;
850             if ([self mode:mode matchesMode:currentMode]) // Already there!
851             {
852                 CGDisplayModeRelease(currentMode);
853                 return TRUE;
854             }
856             CGDisplayModeRelease(currentMode);
857             currentMode = NULL;
859             modes = [self modesMatchingMode:mode forDisplay:displayID];
860             if (!modes.count)
861                 return FALSE;
863             [self transformProcessToForeground:YES];
865             BOOL active = [NSApp isActive];
867             if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
868                 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
869             {
870                 if (active)
871                 {
872                     // If we get here, we have the displays captured.  If we don't
873                     // know the original mode of the display, the current mode must
874                     // be the original.  We should re-query the current mode since
875                     // another process could have changed it between when we last
876                     // checked and when we captured the displays.
877                     if (!originalMode)
878                         originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
880                     if (originalMode)
881                     {
882                         for (id modeObject in modes)
883                         {
884                             mode = (CGDisplayModeRef)modeObject;
885                             if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
886                             {
887                                 ret = TRUE;
888                                 break;
889                             }
890                         }
891                     }
892                     if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
893                         [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
894                     else if (![originalDisplayModes count])
895                     {
896                         CGRestorePermanentDisplayConfiguration();
897                         if (!displaysCapturedForFullscreen)
898                             CGReleaseAllDisplays();
899                     }
901                     if (currentMode)
902                         CGDisplayModeRelease(currentMode);
903                 }
904                 else
905                 {
906                     [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
907                     ret = TRUE;
908                 }
909             }
910         }
912         if (ret)
913             [self adjustWindowLevels];
915         return ret;
916     }
918     - (BOOL) areDisplaysCaptured
919     {
920         return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
921     }
923     - (void) updateCursor:(BOOL)force
924     {
925         if (force || lastTargetWindow)
926         {
927             if (clientWantsCursorHidden && !cursorHidden)
928             {
929                 [NSCursor hide];
930                 cursorHidden = TRUE;
931             }
933             if (!cursorIsCurrent)
934             {
935                 [cursor set];
936                 cursorIsCurrent = TRUE;
937             }
939             if (!clientWantsCursorHidden && cursorHidden)
940             {
941                 [NSCursor unhide];
942                 cursorHidden = FALSE;
943             }
944         }
945         else
946         {
947             if (cursorIsCurrent)
948             {
949                 [[NSCursor arrowCursor] set];
950                 cursorIsCurrent = FALSE;
951             }
952             if (cursorHidden)
953             {
954                 [NSCursor unhide];
955                 cursorHidden = FALSE;
956             }
957         }
958     }
960     - (void) hideCursor
961     {
962         if (!clientWantsCursorHidden)
963         {
964             clientWantsCursorHidden = TRUE;
965             [self updateCursor:TRUE];
966         }
967     }
969     - (void) unhideCursor
970     {
971         if (clientWantsCursorHidden)
972         {
973             clientWantsCursorHidden = FALSE;
974             [self updateCursor:FALSE];
975         }
976     }
978     - (void) setCursor:(NSCursor*)newCursor
979     {
980         if (newCursor != cursor)
981         {
982             [cursor release];
983             cursor = [newCursor retain];
984             cursorIsCurrent = FALSE;
985             [self updateCursor:FALSE];
986         }
987     }
989     - (void) setCursor
990     {
991         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
992         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
993         CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
994         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
995         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
996         CGPoint hotSpot;
998         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
999             hotSpot = CGPointZero;
1000         hotSpot = cgpoint_mac_from_win(hotSpot);
1001         self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1002         [image release];
1003         [self unhideCursor];
1004     }
1006     - (void) nextCursorFrame:(NSTimer*)theTimer
1007     {
1008         NSDictionary* frame;
1009         NSTimeInterval duration;
1010         NSDate* date;
1012         cursorFrame++;
1013         if (cursorFrame >= [cursorFrames count])
1014             cursorFrame = 0;
1015         [self setCursor];
1017         frame = [cursorFrames objectAtIndex:cursorFrame];
1018         duration = [[frame objectForKey:@"duration"] doubleValue];
1019         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1020         [cursorTimer setFireDate:date];
1021     }
1023     - (void) setCursorWithFrames:(NSArray*)frames
1024     {
1025         if (self.cursorFrames == frames)
1026             return;
1028         self.cursorFrames = frames;
1029         cursorFrame = 0;
1030         [cursorTimer invalidate];
1031         self.cursorTimer = nil;
1033         if ([frames count])
1034         {
1035             if ([frames count] > 1)
1036             {
1037                 NSDictionary* frame = [frames objectAtIndex:0];
1038                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
1039                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1040                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1041                                                              interval:1000000
1042                                                                target:self
1043                                                              selector:@selector(nextCursorFrame:)
1044                                                              userInfo:nil
1045                                                               repeats:YES] autorelease];
1046                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1047             }
1049             [self setCursor];
1050         }
1051     }
1053     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1054     {
1055         NSImage* nsimage = nil;
1057         if ([images count])
1058         {
1059             NSSize bestSize = NSZeroSize;
1060             id image;
1062             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1064             for (image in images)
1065             {
1066                 CGImageRef cgimage = (CGImageRef)image;
1067                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1068                 if (imageRep)
1069                 {
1070                     NSSize size = [imageRep size];
1072                     [nsimage addRepresentation:imageRep];
1073                     [imageRep release];
1075                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1076                         bestSize = size;
1077                 }
1078             }
1080             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1081                 [nsimage setSize:bestSize];
1082             else
1083                 nsimage = nil;
1084         }
1086         self.applicationIcon = nsimage;
1087     }
1089     - (void) handleCommandTab
1090     {
1091         if ([NSApp isActive])
1092         {
1093             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1094             NSRunningApplication* app;
1095             NSRunningApplication* otherValidApp = nil;
1097             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1098             {
1099                 NSNumber* displayID;
1100                 for (displayID in originalDisplayModes)
1101                 {
1102                     CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1103                     [latentDisplayModes setObject:(id)mode forKey:displayID];
1104                     CGDisplayModeRelease(mode);
1105                 }
1107                 CGRestorePermanentDisplayConfiguration();
1108                 CGReleaseAllDisplays();
1109                 [originalDisplayModes removeAllObjects];
1110                 displaysCapturedForFullscreen = FALSE;
1111             }
1113             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1114             {
1115                 if (![app isEqual:thisApp] && !app.terminated &&
1116                     app.activationPolicy == NSApplicationActivationPolicyRegular)
1117                 {
1118                     if (!app.hidden)
1119                     {
1120                         // There's another visible app.  Just hide ourselves and let
1121                         // the system activate the other app.
1122                         [NSApp hide:self];
1123                         return;
1124                     }
1126                     if (!otherValidApp)
1127                         otherValidApp = app;
1128                 }
1129             }
1131             // Didn't find a visible GUI app.  Try the Finder or, if that's not
1132             // running, the first hidden GUI app.  If even that doesn't work, we
1133             // just fail to switch and remain the active app.
1134             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1135             if (!app) app = otherValidApp;
1136             [app unhide];
1137             [app activateWithOptions:0];
1138         }
1139     }
1141     - (BOOL) setCursorPosition:(CGPoint)pos
1142     {
1143         BOOL ret;
1145         if ([windowsBeingDragged count])
1146             ret = FALSE;
1147         else if (self.clippingCursor && [clipCursorHandler respondsToSelector:@selector(setCursorPosition:)])
1148             ret = [clipCursorHandler setCursorPosition:pos];
1149         else
1150         {
1151             if (self.clippingCursor)
1152                 [clipCursorHandler clipCursorLocation:&pos];
1154             // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1155             // the mouse from the cursor position for 0.25 seconds.  This means
1156             // that mouse movement during that interval doesn't move the cursor
1157             // and events carry a constant location (the warped-to position)
1158             // even though they have delta values.  For apps which warp the
1159             // cursor frequently (like after every mouse move), this makes
1160             // cursor movement horribly laggy and jerky, as only a fraction of
1161             // mouse move events have any effect.
1162             //
1163             // On some versions of OS X, it's sufficient to forcibly reassociate
1164             // the mouse and cursor position.  On others, it's necessary to set
1165             // the local events suppression interval to 0 for the warp.  That's
1166             // deprecated, but I'm not aware of any other way.  For good
1167             // measure, we do both.
1168             CGSetLocalEventsSuppressionInterval(0);
1169             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1170             CGSetLocalEventsSuppressionInterval(0.25);
1171             if (ret)
1172             {
1173                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1175                 CGAssociateMouseAndMouseCursorPosition(true);
1176             }
1177         }
1179         if (ret)
1180         {
1181             WineEventQueue* queue;
1183             // Discard all pending mouse move events.
1184             [eventQueuesLock lock];
1185             for (queue in eventQueues)
1186             {
1187                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED_RELATIVE) |
1188                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1189                                        forWindow:nil];
1190                 [queue resetMouseEventPositions:pos];
1191             }
1192             [eventQueuesLock unlock];
1193         }
1195         return ret;
1196     }
1198     - (void) updateWindowsForCursorClipping
1199     {
1200         WineWindow* window;
1201         for (window in [NSApp windows])
1202         {
1203             if ([window isKindOfClass:[WineWindow class]])
1204                 [window updateForCursorClipping];
1205         }
1206     }
1208     - (BOOL) startClippingCursor:(CGRect)rect
1209     {
1210         if (!clipCursorHandler) {
1211             if (use_confinement_cursor_clipping && [WineConfinementClipCursorHandler isAvailable])
1212                 clipCursorHandler = [[WineConfinementClipCursorHandler alloc] init];
1213             else
1214                 clipCursorHandler = [[WineEventTapClipCursorHandler alloc] init];
1215         }
1217         if (self.clippingCursor && CGRectEqualToRect(rect, clipCursorHandler.cursorClipRect))
1218             return TRUE;
1220         if (![clipCursorHandler startClippingCursor:rect])
1221             return FALSE;
1223         [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1225         [self updateWindowsForCursorClipping];
1227         return TRUE;
1228     }
1230     - (BOOL) stopClippingCursor
1231     {
1232         if (!self.clippingCursor)
1233             return TRUE;
1235         if (![clipCursorHandler stopClippingCursor])
1236             return FALSE;
1238         lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1240         [self updateWindowsForCursorClipping];
1242         return TRUE;
1243     }
1245     - (BOOL) clippingCursor
1246     {
1247         return clipCursorHandler.clippingCursor;
1248     }
1250     - (BOOL) isKeyPressed:(uint16_t)keyCode
1251     {
1252         int bits = sizeof(pressedKeyCodes[0]) * 8;
1253         int index = keyCode / bits;
1254         uint32_t mask = 1 << (keyCode % bits);
1255         return (pressedKeyCodes[index] & mask) != 0;
1256     }
1258     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1259     {
1260         int bits = sizeof(pressedKeyCodes[0]) * 8;
1261         int index = keyCode / bits;
1262         uint32_t mask = 1 << (keyCode % bits);
1263         if (pressed)
1264             pressedKeyCodes[index] |= mask;
1265         else
1266             pressedKeyCodes[index] &= ~mask;
1267     }
1269     - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1270     {
1271         if (dragged)
1272             [windowsBeingDragged addObject:window];
1273         else
1274             [windowsBeingDragged removeObject:window];
1275     }
1277     - (void) windowWillOrderOut:(WineWindow*)window
1278     {
1279         if ([windowsBeingDragged containsObject:window])
1280         {
1281             [self window:window isBeingDragged:NO];
1283             macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1284             [window.queue postEvent:event];
1285             macdrv_release_event(event);
1286         }
1287     }
1289     - (BOOL) isAnyWineWindowVisible
1290     {
1291         for (WineWindow* w in [NSApp windows])
1292         {
1293             if ([w isKindOfClass:[WineWindow class]] && ![w isMiniaturized] && [w isVisible])
1294                 return YES;
1295         }
1297         return NO;
1298     }
1300     - (void) handleWindowDrag:(WineWindow*)window begin:(BOOL)begin
1301     {
1302         macdrv_event* event;
1303         int eventType;
1305         if (begin)
1306         {
1307             [windowsBeingDragged addObject:window];
1308             eventType = WINDOW_DRAG_BEGIN;
1309         }
1310         else
1311         {
1312             [windowsBeingDragged removeObject:window];
1313             eventType = WINDOW_DRAG_END;
1314         }
1316         event = macdrv_create_event(eventType, window);
1317         if (eventType == WINDOW_DRAG_BEGIN)
1318             event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
1319         [window.queue postEvent:event];
1320         macdrv_release_event(event);
1321     }
1323     - (void) handleMouseMove:(NSEvent*)anEvent
1324     {
1325         WineWindow* targetWindow;
1326         BOOL drag = [anEvent type] != NSEventTypeMouseMoved;
1328         if ([windowsBeingDragged count])
1329             targetWindow = nil;
1330         else if (mouseCaptureWindow)
1331             targetWindow = mouseCaptureWindow;
1332         else if (drag)
1333             targetWindow = (WineWindow*)[anEvent window];
1334         else
1335         {
1336             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1337                event indicates its window is the main window, even if the cursor is
1338                over a different window.  Find the actual WineWindow that is under the
1339                cursor and post the event as being for that window. */
1340             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1341             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1342             NSInteger windowUnderNumber;
1344             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1345                                   belowWindowWithWindowNumber:0];
1346             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1347             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1348                 targetWindow = nil;
1349         }
1351         if ([targetWindow isKindOfClass:[WineWindow class]])
1352         {
1353             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1354             macdrv_event* event;
1355             BOOL absolute;
1357             // If we recently warped the cursor (other than in our cursor-clipping
1358             // event tap), discard mouse move events until we see an event which is
1359             // later than that time.
1360             if (lastSetCursorPositionTime)
1361             {
1362                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1363                     return;
1365                 lastSetCursorPositionTime = 0;
1366                 forceNextMouseMoveAbsolute = TRUE;
1367             }
1369             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1370             {
1371                 absolute = TRUE;
1372                 forceNextMouseMoveAbsolute = FALSE;
1373             }
1374             else
1375             {
1376                 // Send absolute move events if the cursor is in the interior of
1377                 // its range.  Only send relative moves if the cursor is pinned to
1378                 // the boundaries of where it can go.  We compute the position
1379                 // that's one additional point in the direction of movement.  If
1380                 // that is outside of the clipping rect or desktop region (the
1381                 // union of the screen frames), then we figure the cursor would
1382                 // have moved outside if it could but it was pinned.
1383                 CGPoint computedPoint = point;
1384                 CGFloat deltaX = [anEvent deltaX];
1385                 CGFloat deltaY = [anEvent deltaY];
1387                 if (deltaX > 0.001)
1388                     computedPoint.x++;
1389                 else if (deltaX < -0.001)
1390                     computedPoint.x--;
1392                 if (deltaY > 0.001)
1393                     computedPoint.y++;
1394                 else if (deltaY < -0.001)
1395                     computedPoint.y--;
1397                 // Assume cursor is pinned for now
1398                 absolute = FALSE;
1399                 if (!self.clippingCursor || CGRectContainsPoint(clipCursorHandler.cursorClipRect, computedPoint))
1400                 {
1401                     const CGRect* rects;
1402                     NSUInteger count, i;
1404                     // Caches screenFrameCGRects if necessary
1405                     [self primaryScreenHeight];
1407                     rects = [screenFrameCGRects bytes];
1408                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1410                     for (i = 0; i < count; i++)
1411                     {
1412                         if (CGRectContainsPoint(rects[i], computedPoint))
1413                         {
1414                             absolute = TRUE;
1415                             break;
1416                         }
1417                     }
1418                 }
1419             }
1421             if (absolute)
1422             {
1423                 if (self.clippingCursor)
1424                     [clipCursorHandler clipCursorLocation:&point];
1425                 point = cgpoint_win_from_mac(point);
1427                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1428                 event->mouse_moved.x = floor(point.x);
1429                 event->mouse_moved.y = floor(point.y);
1431                 mouseMoveDeltaX = 0;
1432                 mouseMoveDeltaY = 0;
1433             }
1434             else
1435             {
1436                 double scale = retina_on ? 2 : 1;
1438                 /* Add event delta to accumulated delta error */
1439                 /* deltaY is already flipped */
1440                 mouseMoveDeltaX += [anEvent deltaX];
1441                 mouseMoveDeltaY += [anEvent deltaY];
1443                 event = macdrv_create_event(MOUSE_MOVED_RELATIVE, targetWindow);
1444                 event->mouse_moved.x = mouseMoveDeltaX * scale;
1445                 event->mouse_moved.y = mouseMoveDeltaY * scale;
1447                 /* Keep the remainder after integer truncation. */
1448                 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1449                 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1450             }
1452             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1453             {
1454                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1455                 event->mouse_moved.drag = drag;
1457                 [targetWindow.queue postEvent:event];
1458             }
1460             macdrv_release_event(event);
1462             lastTargetWindow = targetWindow;
1463         }
1464         else
1465             lastTargetWindow = nil;
1467         [self updateCursor:FALSE];
1468     }
1470     - (void) handleMouseButton:(NSEvent*)theEvent
1471     {
1472         WineWindow* window = (WineWindow*)[theEvent window];
1473         NSEventType type = [theEvent type];
1474         WineWindow* windowBroughtForward = nil;
1475         BOOL process = FALSE;
1477         if ([window isKindOfClass:[WineWindow class]] &&
1478             type == NSEventTypeLeftMouseDown &&
1479             ![theEvent wine_commandKeyDown])
1480         {
1481             NSWindowButton windowButton;
1483             windowBroughtForward = window;
1485             /* Any left-click on our window anyplace other than the close or
1486                minimize buttons will bring it forward. */
1487             for (windowButton = NSWindowCloseButton;
1488                  windowButton <= NSWindowMiniaturizeButton;
1489                  windowButton++)
1490             {
1491                 NSButton* button = [window standardWindowButton:windowButton];
1492                 if (button)
1493                 {
1494                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1495                     if ([button mouse:point inRect:[button bounds]])
1496                     {
1497                         windowBroughtForward = nil;
1498                         break;
1499                     }
1500                 }
1501             }
1502         }
1504         if ([windowsBeingDragged count])
1505             window = nil;
1506         else if (mouseCaptureWindow)
1507             window = mouseCaptureWindow;
1509         if ([window isKindOfClass:[WineWindow class]])
1510         {
1511             BOOL pressed = (type == NSEventTypeLeftMouseDown ||
1512                             type == NSEventTypeRightMouseDown ||
1513                             type == NSEventTypeOtherMouseDown);
1514             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1516             if (self.clippingCursor)
1517                 [clipCursorHandler clipCursorLocation:&pt];
1519             if (pressed)
1520             {
1521                 if (mouseCaptureWindow)
1522                     process = TRUE;
1523                 else
1524                 {
1525                     // Test if the click was in the window's content area.
1526                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1527                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1528                     process = NSMouseInRect(nspoint, contentRect, NO);
1529                     if (process && [window styleMask] & NSWindowStyleMaskResizable)
1530                     {
1531                         // Ignore clicks in the grow box (resize widget).
1532                         HIPoint origin = { 0, 0 };
1533                         HIThemeGrowBoxDrawInfo info = { 0 };
1534                         HIRect bounds;
1535                         OSStatus status;
1537                         info.kind = kHIThemeGrowBoxKindNormal;
1538                         info.direction = kThemeGrowRight | kThemeGrowDown;
1539                         if ([window styleMask] & NSWindowStyleMaskUtilityWindow)
1540                             info.size = kHIThemeGrowBoxSizeSmall;
1541                         else
1542                             info.size = kHIThemeGrowBoxSizeNormal;
1544                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1545                         if (status == noErr)
1546                         {
1547                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1548                                                         NSMinY(contentRect),
1549                                                         bounds.size.width,
1550                                                         bounds.size.height);
1551                             process = !NSMouseInRect(nspoint, growBox, NO);
1552                         }
1553                     }
1554                 }
1555                 if (process)
1556                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1557             }
1558             else
1559             {
1560                 NSEventType downType = type - 1;
1561                 NSUInteger downMask = NSEventMaskFromType(downType);
1562                 process = (unmatchedMouseDowns & downMask) != 0;
1563                 unmatchedMouseDowns &= ~downMask;
1564             }
1566             if (process)
1567             {
1568                 macdrv_event* event;
1570                 pt = cgpoint_win_from_mac(pt);
1572                 event = macdrv_create_event(MOUSE_BUTTON, window);
1573                 event->mouse_button.button = [theEvent buttonNumber];
1574                 event->mouse_button.pressed = pressed;
1575                 event->mouse_button.x = floor(pt.x);
1576                 event->mouse_button.y = floor(pt.y);
1577                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1579                 [window.queue postEvent:event];
1581                 macdrv_release_event(event);
1582             }
1583         }
1585         if (windowBroughtForward)
1586         {
1587             WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1588             NSInteger ancestorNumber = [ancestor windowNumber];
1589             NSInteger ancestorLevel = [ancestor level];
1591             for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1592             {
1593                 NSInteger windowNumber = [windowNumberObject integerValue];
1594                 if (windowNumber == ancestorNumber)
1595                     break;
1596                 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1597                 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1598                     [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1599                 {
1600                     [ancestor postBroughtForwardEvent];
1601                     break;
1602                 }
1603             }
1604             if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noForeground)
1605                 [self windowGotFocus:windowBroughtForward];
1606         }
1608         // Since mouse button events deliver absolute cursor position, the
1609         // accumulating delta from move events is invalidated.  Make sure
1610         // next mouse move event starts over from an absolute baseline.
1611         // Also, it's at least possible that the title bar widgets (e.g. close
1612         // button, etc.) could enter an internal event loop on a mouse down that
1613         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1614         // dragged events and, after that, any notion of the cursor position
1615         // computed from accumulating deltas would be wrong.
1616         forceNextMouseMoveAbsolute = TRUE;
1617     }
1619     - (void) handleScrollWheel:(NSEvent*)theEvent
1620     {
1621         WineWindow* window;
1623         if (mouseCaptureWindow)
1624             window = mouseCaptureWindow;
1625         else
1626             window = (WineWindow*)[theEvent window];
1628         if ([window isKindOfClass:[WineWindow class]])
1629         {
1630             CGEventRef cgevent = [theEvent CGEvent];
1631             CGPoint pt = CGEventGetLocation(cgevent);
1632             BOOL process;
1634             if (self.clippingCursor)
1635                 [clipCursorHandler clipCursorLocation:&pt];
1637             if (mouseCaptureWindow)
1638                 process = TRUE;
1639             else
1640             {
1641                 // Only process the event if it was in the window's content area.
1642                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1643                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1644                 process = NSMouseInRect(nspoint, contentRect, NO);
1645             }
1647             if (process)
1648             {
1649                 macdrv_event* event;
1650                 double x, y;
1651                 BOOL continuous = FALSE;
1653                 pt = cgpoint_win_from_mac(pt);
1655                 event = macdrv_create_event(MOUSE_SCROLL, window);
1656                 event->mouse_scroll.x = floor(pt.x);
1657                 event->mouse_scroll.y = floor(pt.y);
1658                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1660                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1661                 {
1662                     continuous = TRUE;
1664                     /* Continuous scroll wheel events come from high-precision scrolling
1665                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1666                        For these, we can get more precise data from the CGEvent API. */
1667                     /* Axis 1 is vertical, axis 2 is horizontal. */
1668                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1669                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1670                 }
1671                 else
1672                 {
1673                     double pixelsPerLine = 10;
1674                     CGEventSourceRef source;
1676                     /* The non-continuous values are in units of "lines", not pixels. */
1677                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1678                     {
1679                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1680                         CFRelease(source);
1681                     }
1683                     x = pixelsPerLine * [theEvent deltaX];
1684                     y = pixelsPerLine * [theEvent deltaY];
1685                 }
1687                 /* Mac: negative is right or down, positive is left or up.
1688                    Win32: negative is left or down, positive is right or up.
1689                    So, negate the X scroll value to translate. */
1690                 x = -x;
1692                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1693                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1694                    6 times the pixel value. */
1695                 x *= 6;
1696                 y *= 6;
1698                 if (use_precise_scrolling)
1699                 {
1700                     event->mouse_scroll.x_scroll = x;
1701                     event->mouse_scroll.y_scroll = y;
1703                     if (!continuous)
1704                     {
1705                         /* For non-continuous "clicky" wheels, if there was any motion, make
1706                            sure there was at least WHEEL_DELTA motion.  This is so, at slow
1707                            speeds where the system's acceleration curve is actually reducing the
1708                            scroll distance, the user is sure to get some action out of each click.
1709                            For example, this is important for rotating though weapons in a
1710                            first-person shooter. */
1711                         if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1712                             event->mouse_scroll.x_scroll = 120;
1713                         else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1714                             event->mouse_scroll.x_scroll = -120;
1716                         if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1717                             event->mouse_scroll.y_scroll = 120;
1718                         else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1719                             event->mouse_scroll.y_scroll = -120;
1720                     }
1721                 }
1722                 else
1723                 {
1724                     /* If it's been a while since the last scroll event or if the scrolling has
1725                        reversed direction, reset the accumulated scroll value. */
1726                     if ([theEvent timestamp] - lastScrollTime > 1)
1727                         accumScrollX = accumScrollY = 0;
1728                     else
1729                     {
1730                         /* The accumulated scroll value is in the opposite direction/sign of the last
1731                            scroll.  That's because it's the "debt" resulting from over-scrolling in
1732                            that direction.  We accumulate by adding in the scroll amount and then, if
1733                            it has the same sign as the scroll value, we subtract any whole or partial
1734                            WHEEL_DELTAs, leaving it 0 or the opposite sign.  So, the user switched
1735                            scroll direction if the accumulated debt and the new scroll value have the
1736                            same sign. */
1737                         if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1738                             accumScrollX = 0;
1739                         if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1740                             accumScrollY = 0;
1741                     }
1742                     lastScrollTime = [theEvent timestamp];
1744                     accumScrollX += x;
1745                     accumScrollY += y;
1747                     if (accumScrollX > 0 && x > 0)
1748                         event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1749                     if (accumScrollX < 0 && x < 0)
1750                         event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1751                     if (accumScrollY > 0 && y > 0)
1752                         event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1753                     if (accumScrollY < 0 && y < 0)
1754                         event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1756                     accumScrollX -= event->mouse_scroll.x_scroll;
1757                     accumScrollY -= event->mouse_scroll.y_scroll;
1758                 }
1760                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1761                     [window.queue postEvent:event];
1763                 macdrv_release_event(event);
1765                 // Since scroll wheel events deliver absolute cursor position, the
1766                 // accumulating delta from move events is invalidated.  Make sure next
1767                 // mouse move event starts over from an absolute baseline.
1768                 forceNextMouseMoveAbsolute = TRUE;
1769             }
1770         }
1771     }
1773     // Returns TRUE if the event was handled and caller should do nothing more
1774     // with it.  Returns FALSE if the caller should process it as normal and
1775     // then call -didSendEvent:.
1776     - (BOOL) handleEvent:(NSEvent*)anEvent
1777     {
1778         BOOL ret = FALSE;
1779         NSEventType type = [anEvent type];
1781         if (type == NSEventTypeFlagsChanged)
1782             self.lastFlagsChanged = anEvent;
1783         else if (type == NSEventTypeMouseMoved || type == NSEventTypeLeftMouseDragged ||
1784                  type == NSEventTypeRightMouseDragged || type == NSEventTypeOtherMouseDragged)
1785         {
1786             [self handleMouseMove:anEvent];
1787             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1788         }
1789         else if (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp ||
1790                  type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp ||
1791                  type == NSEventTypeOtherMouseDown || type == NSEventTypeOtherMouseUp)
1792         {
1793             [self handleMouseButton:anEvent];
1794             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1795         }
1796         else if (type == NSEventTypeScrollWheel)
1797         {
1798             [self handleScrollWheel:anEvent];
1799             ret = mouseCaptureWindow != nil;
1800         }
1801         else if (type == NSEventTypeKeyDown)
1802         {
1803             // -[NSApplication sendEvent:] seems to consume presses of the Help
1804             // key (Insert key on PC keyboards), so we have to bypass it and
1805             // send the event directly to the window.
1806             if (anEvent.keyCode == kVK_Help)
1807             {
1808                 [anEvent.window sendEvent:anEvent];
1809                 ret = TRUE;
1810             }
1811         }
1812         else if (type == NSEventTypeKeyUp)
1813         {
1814             uint16_t keyCode = [anEvent keyCode];
1815             if ([self isKeyPressed:keyCode])
1816             {
1817                 WineWindow* window = (WineWindow*)[anEvent window];
1818                 [self noteKey:keyCode pressed:FALSE];
1819                 if ([window isKindOfClass:[WineWindow class]])
1820                     [window postKeyEvent:anEvent];
1821             }
1822         }
1823         else if (!useDragNotifications && type == NSEventTypeAppKitDefined)
1824         {
1825             WineWindow *window = (WineWindow *)[anEvent window];
1826             short subtype = [anEvent subtype];
1828             // These subtypes are not documented but they appear to mean
1829             // "a window is being dragged" and "a window is no longer being
1830             // dragged", respectively.
1831             if ((subtype == 20 || subtype == 21) && [window isKindOfClass:[WineWindow class]])
1832                 [self handleWindowDrag:window begin:(subtype == 20)];
1833         }
1835         return ret;
1836     }
1838     - (void) didSendEvent:(NSEvent*)anEvent
1839     {
1840         NSEventType type = [anEvent type];
1842         if (type == NSEventTypeKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1843         {
1844             NSUInteger modifiers = [anEvent modifierFlags];
1845             if ((modifiers & NSEventModifierFlagCommand) &&
1846                 !(modifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)))
1847             {
1848                 // Command-Tab and Command-Shift-Tab would normally be intercepted
1849                 // by the system to switch applications.  If we're seeing it, it's
1850                 // presumably because we've captured the displays, preventing
1851                 // normal application switching.  Do it manually.
1852                 [self handleCommandTab];
1853             }
1854         }
1855     }
1857     - (void) setupObservations
1858     {
1859         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1860         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1861         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
1863         [nc addObserverForName:NSWindowDidBecomeKeyNotification
1864                         object:nil
1865                          queue:nil
1866                     usingBlock:^(NSNotification *note){
1867             NSWindow* window = [note object];
1868             [keyWindows removeObjectIdenticalTo:window];
1869             [keyWindows insertObject:window atIndex:0];
1870         }];
1872         [nc addObserverForName:NSWindowWillCloseNotification
1873                         object:nil
1874                          queue:[NSOperationQueue mainQueue]
1875                     usingBlock:^(NSNotification *note){
1876             NSWindow* window = [note object];
1877             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
1878                 return;
1879             [keyWindows removeObjectIdenticalTo:window];
1880             if (window == lastTargetWindow)
1881                 lastTargetWindow = nil;
1882             if (window == self.mouseCaptureWindow)
1883                 self.mouseCaptureWindow = nil;
1884             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
1885             {
1886                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
1887                     [self updateFullscreenWindows];
1888                 });
1889             }
1890             [windowsBeingDragged removeObject:window];
1891         }];
1893         if (useDragNotifications) {
1894             [nc addObserverForName:NSWindowWillStartDraggingNotification
1895                             object:nil
1896                              queue:[NSOperationQueue mainQueue]
1897                         usingBlock:^(NSNotification *note){
1898                 NSWindow* window = [note object];
1899                 if ([window isKindOfClass:[WineWindow class]])
1900                     [self handleWindowDrag:(WineWindow *)window begin:YES];
1901             }];
1903             [nc addObserverForName:NSWindowDidEndDraggingNotification
1904                             object:nil
1905                              queue:[NSOperationQueue mainQueue]
1906                         usingBlock:^(NSNotification *note){
1907                 NSWindow* window = [note object];
1908                 if ([window isKindOfClass:[WineWindow class]])
1909                     [self handleWindowDrag:(WineWindow *)window begin:NO];
1910             }];
1911         }
1913         [nc addObserver:self
1914                selector:@selector(keyboardSelectionDidChange)
1915                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
1916                  object:nil];
1918         /* The above notification isn't sent unless the NSTextInputContext
1919            class has initialized itself.  Poke it. */
1920         [NSTextInputContext self];
1922         [wsnc addObserver:self
1923                  selector:@selector(activeSpaceDidChange)
1924                      name:NSWorkspaceActiveSpaceDidChangeNotification
1925                    object:nil];
1927         [nc addObserver:self
1928                selector:@selector(releaseMouseCapture)
1929                    name:NSMenuDidBeginTrackingNotification
1930                  object:nil];
1932         [dnc        addObserver:self
1933                        selector:@selector(releaseMouseCapture)
1934                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
1935                          object:nil
1936              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
1938         [dnc addObserver:self
1939                 selector:@selector(enabledKeyboardInputSourcesChanged)
1940                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
1941                   object:nil];
1942     }
1944     - (BOOL) inputSourceIsInputMethod
1945     {
1946         if (!inputSourceIsInputMethodValid)
1947         {
1948             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
1949             if (inputSource)
1950             {
1951                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
1952                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
1953                 CFRelease(inputSource);
1954             }
1955             else
1956                 inputSourceIsInputMethod = FALSE;
1957             inputSourceIsInputMethodValid = TRUE;
1958         }
1960         return inputSourceIsInputMethod;
1961     }
1963     - (void) releaseMouseCapture
1964     {
1965         // This might be invoked on a background thread by the distributed
1966         // notification center.  Shunt it to the main thread.
1967         if (![NSThread isMainThread])
1968         {
1969             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
1970             return;
1971         }
1973         if (mouseCaptureWindow)
1974         {
1975             macdrv_event* event;
1977             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
1978             [mouseCaptureWindow.queue postEvent:event];
1979             macdrv_release_event(event);
1980         }
1981     }
1983     - (void) unminimizeWindowIfNoneVisible
1984     {
1985         if (![self frontWineWindow])
1986         {
1987             for (WineWindow* window in [NSApp windows])
1988             {
1989                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
1990                 {
1991                     [window deminiaturize:self];
1992                     break;
1993                 }
1994             }
1995         }
1996     }
1998     - (void) setRetinaMode:(int)mode
1999     {
2000         retina_on = mode;
2002         [clipCursorHandler setRetinaMode:mode];
2004         for (WineWindow* window in [NSApp windows])
2005         {
2006             if ([window isKindOfClass:[WineWindow class]])
2007                 [window setRetinaMode:mode];
2008         }
2009     }
2012     /*
2013      * ---------- NSApplicationDelegate methods ----------
2014      */
2015     - (void)applicationDidBecomeActive:(NSNotification *)notification
2016     {
2017         NSNumber* displayID;
2018         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2020         latentDisplayModes = [[NSMutableDictionary alloc] init];
2021         for (displayID in modesToRealize)
2022         {
2023             CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2024             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2025         }
2027         [self updateFullscreenWindows];
2028         [self adjustWindowLevels:YES];
2030         if (beenActive)
2031             [self unminimizeWindowIfNoneVisible];
2032         beenActive = TRUE;
2034         // If a Wine process terminates abruptly while it has the display captured
2035         // and switched to a different resolution, Mac OS X will uncapture the
2036         // displays and switch their resolutions back.  However, the other Wine
2037         // processes won't have their notion of the desktop rect changed back.
2038         // This can lead them to refuse to draw or acknowledge clicks in certain
2039         // portions of their windows.
2040         //
2041         // To solve this, we synthesize a displays-changed event whenever we're
2042         // activated.  This will provoke a re-synchronization of Wine's notion of
2043         // the desktop rect with the actual state.
2044         [self sendDisplaysChanged:TRUE];
2046         // The cursor probably moved while we were inactive.  Accumulated mouse
2047         // movement deltas are invalidated.  Make sure the next mouse move event
2048         // starts over from an absolute baseline.
2049         forceNextMouseMoveAbsolute = TRUE;
2050     }
2052     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2053     {
2054         primaryScreenHeightValid = FALSE;
2055         [self sendDisplaysChanged:FALSE];
2056         [self adjustWindowLevels];
2058         // When the display configuration changes, the cursor position may jump.
2059         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2060         // mouse move event starts over from an absolute baseline.
2061         forceNextMouseMoveAbsolute = TRUE;
2062     }
2064     - (void)applicationDidResignActive:(NSNotification *)notification
2065     {
2066         macdrv_event* event;
2067         WineEventQueue* queue;
2069         [self invalidateGotFocusEvents];
2071         event = macdrv_create_event(APP_DEACTIVATED, nil);
2073         [eventQueuesLock lock];
2074         for (queue in eventQueues)
2075             [queue postEvent:event];
2076         [eventQueuesLock unlock];
2078         macdrv_release_event(event);
2080         [self releaseMouseCapture];
2081     }
2083     - (void) applicationDidUnhide:(NSNotification*)aNotification
2084     {
2085         [self adjustWindowLevels];
2086     }
2088     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2089     {
2090         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2091         // don't count as "visible windows" for this purpose.
2092         [self unminimizeWindowIfNoneVisible];
2093         return YES;
2094     }
2096     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2097     {
2098         NSApplicationTerminateReply ret = NSTerminateNow;
2099         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2100         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2101         macdrv_event* event;
2102         WineEventQueue* queue;
2104         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2105         event->deliver = 1;
2106         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2107         {
2108             case kAELogOut:
2109             case kAEReallyLogOut:
2110                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2111                 break;
2112             case kAEShowRestartDialog:
2113                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2114                 break;
2115             case kAEShowShutdownDialog:
2116                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2117                 break;
2118             default:
2119                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2120                 break;
2121         }
2123         [eventQueuesLock lock];
2125         if ([eventQueues count])
2126         {
2127             for (queue in eventQueues)
2128                 [queue postEvent:event];
2129             ret = NSTerminateLater;
2130         }
2132         [eventQueuesLock unlock];
2134         macdrv_release_event(event);
2136         return ret;
2137     }
2139     - (void)applicationWillBecomeActive:(NSNotification *)notification
2140     {
2141         macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2142         event->deliver = 1;
2144         [eventQueuesLock lock];
2145         for (WineEventQueue* queue in eventQueues)
2146             [queue postEvent:event];
2147         [eventQueuesLock unlock];
2149         macdrv_release_event(event);
2150     }
2152     - (void)applicationWillResignActive:(NSNotification *)notification
2153     {
2154         [self adjustWindowLevels:NO];
2155     }
2157 /***********************************************************************
2158  *              PerformRequest
2160  * Run-loop-source perform callback.  Pull request blocks from the
2161  * array of queued requests and invoke them.
2162  */
2163 static void PerformRequest(void *info)
2165     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2166     WineApplicationController* controller = [WineApplicationController sharedController];
2168     for (;;)
2169     {
2170         __block dispatch_block_t block;
2172         dispatch_sync(controller->requestsManipQueue, ^{
2173             if ([controller->requests count])
2174             {
2175                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2176                 [controller->requests removeObjectAtIndex:0];
2177             }
2178             else
2179                 block = nil;
2180         });
2182         if (!block)
2183             break;
2185         block();
2186         [block release];
2188         [pool release];
2189         pool = [[NSAutoreleasePool alloc] init];
2190     }
2192     [pool release];
2195 /***********************************************************************
2196  *              OnMainThreadAsync
2198  * Run a block on the main thread asynchronously.
2199  */
2200 void OnMainThreadAsync(dispatch_block_t block)
2202     WineApplicationController* controller = [WineApplicationController sharedController];
2204     block = [block copy];
2205     dispatch_sync(controller->requestsManipQueue, ^{
2206         [controller->requests addObject:block];
2207     });
2208     [block release];
2209     CFRunLoopSourceSignal(controller->requestSource);
2210     CFRunLoopWakeUp(CFRunLoopGetMain());
2213 @end
2215 /***********************************************************************
2216  *              LogError
2217  */
2218 void LogError(const char* func, NSString* format, ...)
2220     va_list args;
2221     va_start(args, format);
2222     LogErrorv(func, format, args);
2223     va_end(args);
2226 /***********************************************************************
2227  *              LogErrorv
2228  */
2229 void LogErrorv(const char* func, NSString* format, va_list args)
2231     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2233     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2234     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2235     [message release];
2237     [pool release];
2240 /***********************************************************************
2241  *              macdrv_window_rejected_focus
2243  * Pass focus to the next window that hasn't already rejected this same
2244  * WINDOW_GOT_FOCUS event.
2245  */
2246 void macdrv_window_rejected_focus(const macdrv_event *event)
2248     OnMainThread(^{
2249         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2250     });
2253 /***********************************************************************
2254  *              macdrv_get_input_source_info
2256  * Returns the keyboard layout uchr data, keyboard type and input source.
2257  */
2258 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2260     OnMainThread(^{
2261         TISInputSourceRef inputSourceLayout;
2263         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2264         if (inputSourceLayout)
2265         {
2266             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2267                                 kTISPropertyUnicodeKeyLayoutData);
2268             *uchr = CFDataCreateCopy(NULL, data);
2269             CFRelease(inputSourceLayout);
2271             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2272             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2273             if (input_source)
2274                 *input_source = TISCopyCurrentKeyboardInputSource();
2275         }
2276     });
2279 /***********************************************************************
2280  *              macdrv_beep
2282  * Play the beep sound configured by the user in System Preferences.
2283  */
2284 void macdrv_beep(void)
2286     OnMainThreadAsync(^{
2287         NSBeep();
2288     });
2291 /***********************************************************************
2292  *              macdrv_set_display_mode
2293  */
2294 int macdrv_set_display_mode(const struct macdrv_display* display,
2295                             CGDisplayModeRef display_mode)
2297     __block int ret;
2299     OnMainThread(^{
2300         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2301     });
2303     return ret;
2306 /***********************************************************************
2307  *              macdrv_set_cursor
2309  * Set the cursor.
2311  * If name is non-NULL, it is a selector for a class method on NSCursor
2312  * identifying the cursor to set.  In that case, frames is ignored.  If
2313  * name is NULL, then frames is used.
2315  * frames is an array of dictionaries.  Each dictionary is a frame of
2316  * an animated cursor.  Under the key "image" is a CGImage for the
2317  * frame.  Under the key "duration" is a CFNumber time interval, in
2318  * seconds, for how long that frame is presented before proceeding to
2319  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2320  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2321  * This is the hot spot, measured in pixels down and to the right of the
2322  * top-left corner of the image.
2324  * If the array has exactly 1 element, the cursor is static, not
2325  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2326  */
2327 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2329     SEL sel;
2331     sel = NSSelectorFromString((NSString*)name);
2332     if (sel)
2333     {
2334         OnMainThreadAsync(^{
2335             WineApplicationController* controller = [WineApplicationController sharedController];
2336             [controller setCursorWithFrames:nil];
2337             controller.cursor = [NSCursor performSelector:sel];
2338             [controller unhideCursor];
2339         });
2340     }
2341     else
2342     {
2343         NSArray* nsframes = (NSArray*)frames;
2344         if ([nsframes count])
2345         {
2346             OnMainThreadAsync(^{
2347                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2348             });
2349         }
2350         else
2351         {
2352             OnMainThreadAsync(^{
2353                 WineApplicationController* controller = [WineApplicationController sharedController];
2354                 [controller setCursorWithFrames:nil];
2355                 [controller hideCursor];
2356             });
2357         }
2358     }
2361 /***********************************************************************
2362  *              macdrv_get_cursor_position
2364  * Obtains the current cursor position.  Returns zero on failure,
2365  * non-zero on success.
2366  */
2367 int macdrv_get_cursor_position(CGPoint *pos)
2369     OnMainThread(^{
2370         NSPoint location = [NSEvent mouseLocation];
2371         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2372         *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2373     });
2375     return TRUE;
2378 /***********************************************************************
2379  *              macdrv_set_cursor_position
2381  * Sets the cursor position without generating events.  Returns zero on
2382  * failure, non-zero on success.
2383  */
2384 int macdrv_set_cursor_position(CGPoint pos)
2386     __block int ret;
2388     OnMainThread(^{
2389         ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2390     });
2392     return ret;
2395 /***********************************************************************
2396  *              macdrv_clip_cursor
2398  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2399  * to or larger than the whole desktop region, the cursor is unclipped.
2400  * Returns zero on failure, non-zero on success.
2401  */
2402 int macdrv_clip_cursor(CGRect r)
2404     __block int ret;
2406     OnMainThread(^{
2407         WineApplicationController* controller = [WineApplicationController sharedController];
2408         BOOL clipping = FALSE;
2409         CGRect rect = r;
2411         if (!CGRectIsInfinite(rect))
2412             rect = cgrect_mac_from_win(rect);
2414         if (!CGRectIsInfinite(rect))
2415         {
2416             NSRect nsrect = NSRectFromCGRect(rect);
2417             NSScreen* screen;
2419             /* Convert the rectangle from top-down coords to bottom-up. */
2420             [controller flipRect:&nsrect];
2422             clipping = FALSE;
2423             for (screen in [NSScreen screens])
2424             {
2425                 if (!NSContainsRect(nsrect, [screen frame]))
2426                 {
2427                     clipping = TRUE;
2428                     break;
2429                 }
2430             }
2431         }
2433         if (clipping)
2434             ret = [controller startClippingCursor:rect];
2435         else
2436             ret = [controller stopClippingCursor];
2437     });
2439     return ret;
2442 /***********************************************************************
2443  *              macdrv_set_application_icon
2445  * Set the application icon.  The images array contains CGImages.  If
2446  * there are more than one, then they represent different sizes or
2447  * color depths from the icon resource.  If images is NULL or empty,
2448  * restores the default application image.
2449  */
2450 void macdrv_set_application_icon(CFArrayRef images)
2452     NSArray* imageArray = (NSArray*)images;
2454     OnMainThreadAsync(^{
2455         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2456     });
2459 /***********************************************************************
2460  *              macdrv_quit_reply
2461  */
2462 void macdrv_quit_reply(int reply)
2464     OnMainThread(^{
2465         [NSApp replyToApplicationShouldTerminate:reply];
2466     });
2469 /***********************************************************************
2470  *              macdrv_using_input_method
2471  */
2472 int macdrv_using_input_method(void)
2474     __block BOOL ret;
2476     OnMainThread(^{
2477         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2478     });
2480     return ret;
2483 /***********************************************************************
2484  *              macdrv_set_mouse_capture_window
2485  */
2486 void macdrv_set_mouse_capture_window(macdrv_window window)
2488     WineWindow* w = (WineWindow*)window;
2490     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2492     OnMainThread(^{
2493         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2494     });
2497 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2498 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2499 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2501 /***********************************************************************
2502  *              macdrv_create_input_source_list
2503  */
2504 CFArrayRef macdrv_create_input_source_list(void)
2506     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2508     OnMainThread(^{
2509         CFArrayRef input_list;
2510         CFDictionaryRef filter_dict;
2511         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2512         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2513         int i;
2515         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2516                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2517         input_list = TISCreateInputSourceList(filter_dict, false);
2519         for (i = 0; i < CFArrayGetCount(input_list); i++)
2520         {
2521             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2522             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2523             CFDictionaryRef entry;
2524             const void *input_keys[3] = { macdrv_input_source_input_key,
2525                                           macdrv_input_source_type_key,
2526                                           macdrv_input_source_lang_key };
2527             const void *input_values[3];
2529             input_values[0] = input;
2530             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2531             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2533             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2534                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2536             CFArrayAppendValue(ret, entry);
2537             CFRelease(entry);
2538         }
2539         CFRelease(input_list);
2540         CFRelease(filter_dict);
2541     });
2543     return ret;
2546 int macdrv_select_input_source(TISInputSourceRef input_source)
2548     __block int ret = FALSE;
2550     OnMainThread(^{
2551         ret = (TISSelectInputSource(input_source) == noErr);
2552     });
2554     return ret;
2557 void macdrv_set_cocoa_retina_mode(int new_mode)
2559     OnMainThread(^{
2560         [[WineApplicationController sharedController] setRetinaMode:new_mode];
2561     });
2564 int macdrv_is_any_wine_window_visible(void)
2566     __block int ret = FALSE;
2568     OnMainThread(^{
2569         ret = [[WineApplicationController sharedController] isAnyWineWindowVisible];
2570     });
2572     return ret;