msvcrt: Use the remquo()/remquof() implementation from the bundled musl library.
[wine.git] / dlls / winemac.drv / cocoa_app.m
blobd9d56da20f85ca219e0b7b0bc5290c6348dcec8c
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                 // -setLevel: puts the window at the front of its new level.  If
637                 // we decreased the level, that's good (it was in front of that
638                 // level before, so it should still be now).  But if we increased
639                 // the level, the window should be toward the back (but still
640                 // ahead of the previous windows we did this to).
641                 if (origLevel < newLevel)
642                 {
643                     if (prev)
644                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
645                     else
646                         [window orderBack:nil];
647                 }
648             }
650             prev = window;
651         }];
653         NSEnableScreenUpdates();
655         [wineWindows release];
657         // The above took care of the visible windows on the current space.  That
658         // leaves windows on other spaces, minimized windows, and windows which
659         // are not ordered in.  We want to leave windows on other spaces alone
660         // so the space remains just as they left it (when viewed in Exposé or
661         // Mission Control, for example).  We'll adjust the window levels again
662         // after we switch to another space, anyway.  Windows which aren't
663         // ordered in will be handled when we order them in.  Minimized windows
664         // on the current space should be set to the level they would have gotten
665         // if they were at the front of the windows with the same floating-ness,
666         // because that's where they'll go if/when they are unminimized.  Again,
667         // for good measure we'll adjust window levels again when a window is
668         // unminimized, too.
669         for (window in [NSApp windows])
670         {
671             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
672                 [window isOnActiveSpace])
673             {
674                 NSInteger origLevel = [window level];
675                 NSInteger newLevel = [window minimumLevelForActive:YES];
676                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
678                 if (newLevel < maxLevelForType)
679                     newLevel = maxLevelForType;
681                 if (newLevel != origLevel)
682                     [window setLevel:newLevel];
683             }
684         }
685     }
687     - (void) adjustWindowLevels
688     {
689         [self adjustWindowLevels:[NSApp isActive]];
690     }
692     - (void) updateFullscreenWindows
693     {
694         if (capture_displays_for_fullscreen && [NSApp isActive])
695         {
696             BOOL anyFullscreen = FALSE;
697             NSNumber* windowNumber;
698             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
699             {
700                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
701                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
702                 {
703                     anyFullscreen = TRUE;
704                     break;
705                 }
706             }
708             if (anyFullscreen)
709             {
710                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
711                     displaysCapturedForFullscreen = TRUE;
712             }
713             else if (displaysCapturedForFullscreen)
714             {
715                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
716                     displaysCapturedForFullscreen = FALSE;
717             }
718         }
719     }
721     - (void) activeSpaceDidChange
722     {
723         [self updateFullscreenWindows];
724         [self adjustWindowLevels];
725     }
727     - (void) sendDisplaysChanged:(BOOL)activating
728     {
729         macdrv_event* event;
730         WineEventQueue* queue;
732         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
733         event->displays_changed.activating = activating;
735         [eventQueuesLock lock];
737         // If we're activating, then we just need one of our threads to get the
738         // event, so it can send it directly to the desktop window.  Otherwise,
739         // we need all of the threads to get it because we don't know which owns
740         // the desktop window and only that one will do anything with it.
741         if (activating) event->deliver = 1;
743         for (queue in eventQueues)
744             [queue postEvent:event];
745         [eventQueuesLock unlock];
747         macdrv_release_event(event);
748     }
750     // We can compare two modes directly using CFEqual, but that may require that
751     // they are identical to a level that we don't need.  In particular, when the
752     // OS switches between the integrated and discrete GPUs, the set of display
753     // modes can change in subtle ways.  We're interested in whether two modes
754     // match in their most salient features, even if they aren't identical.
755     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
756     {
757         NSString *encoding1, *encoding2;
758         uint32_t ioflags1, ioflags2, different;
759         double refresh1, refresh2;
761         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
762         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
763         if (CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
764         if (CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
766         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
767         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
768         if (![encoding1 isEqualToString:encoding2]) return FALSE;
770         ioflags1 = CGDisplayModeGetIOFlags(mode1);
771         ioflags2 = CGDisplayModeGetIOFlags(mode2);
772         different = ioflags1 ^ ioflags2;
773         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
774                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
775             return FALSE;
777         refresh1 = CGDisplayModeGetRefreshRate(mode1);
778         if (refresh1 == 0) refresh1 = 60;
779         refresh2 = CGDisplayModeGetRefreshRate(mode2);
780         if (refresh2 == 0) refresh2 = 60;
781         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
783         return TRUE;
784     }
786     - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
787     {
788         NSMutableArray* ret = [NSMutableArray array];
789         NSDictionary* options = @{ (NSString*)kCGDisplayShowDuplicateLowResolutionModes: @YES };
791         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
792         for (id candidateModeObject in modes)
793         {
794             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
795             if ([self mode:candidateMode matchesMode:mode])
796                 [ret addObject:candidateModeObject];
797         }
798         return ret;
799     }
801     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
802     {
803         BOOL ret = FALSE;
804         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
805         CGDisplayModeRef originalMode;
807         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
809         if (originalMode && [self mode:mode matchesMode:originalMode])
810         {
811             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
812             {
813                 CGRestorePermanentDisplayConfiguration();
814                 if (!displaysCapturedForFullscreen)
815                     CGReleaseAllDisplays();
816                 [originalDisplayModes removeAllObjects];
817                 ret = TRUE;
818             }
819             else // ... otherwise, try to restore just the one display
820             {
821                 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
822                 {
823                     mode = (CGDisplayModeRef)modeObject;
824                     if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
825                     {
826                         [originalDisplayModes removeObjectForKey:displayIDKey];
827                         ret = TRUE;
828                         break;
829                     }
830                 }
831             }
832         }
833         else
834         {
835             CGDisplayModeRef currentMode;
836             NSArray* modes;
838             currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
839             if (!currentMode)
840                 currentMode = CGDisplayCopyDisplayMode(displayID);
841             if (!currentMode) // Invalid display ID
842                 return FALSE;
844             if ([self mode:mode matchesMode:currentMode]) // Already there!
845             {
846                 CGDisplayModeRelease(currentMode);
847                 return TRUE;
848             }
850             CGDisplayModeRelease(currentMode);
851             currentMode = NULL;
853             modes = [self modesMatchingMode:mode forDisplay:displayID];
854             if (!modes.count)
855                 return FALSE;
857             [self transformProcessToForeground:YES];
859             BOOL active = [NSApp isActive];
861             if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
862                 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
863             {
864                 if (active)
865                 {
866                     // If we get here, we have the displays captured.  If we don't
867                     // know the original mode of the display, the current mode must
868                     // be the original.  We should re-query the current mode since
869                     // another process could have changed it between when we last
870                     // checked and when we captured the displays.
871                     if (!originalMode)
872                         originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
874                     if (originalMode)
875                     {
876                         for (id modeObject in modes)
877                         {
878                             mode = (CGDisplayModeRef)modeObject;
879                             if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
880                             {
881                                 ret = TRUE;
882                                 break;
883                             }
884                         }
885                     }
886                     if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
887                         [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
888                     else if (![originalDisplayModes count])
889                     {
890                         CGRestorePermanentDisplayConfiguration();
891                         if (!displaysCapturedForFullscreen)
892                             CGReleaseAllDisplays();
893                     }
895                     if (currentMode)
896                         CGDisplayModeRelease(currentMode);
897                 }
898                 else
899                 {
900                     [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
901                     ret = TRUE;
902                 }
903             }
904         }
906         if (ret)
907             [self adjustWindowLevels];
909         return ret;
910     }
912     - (BOOL) areDisplaysCaptured
913     {
914         return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
915     }
917     - (void) updateCursor:(BOOL)force
918     {
919         if (force || lastTargetWindow)
920         {
921             if (clientWantsCursorHidden && !cursorHidden)
922             {
923                 [NSCursor hide];
924                 cursorHidden = TRUE;
925             }
927             if (!cursorIsCurrent)
928             {
929                 [cursor set];
930                 cursorIsCurrent = TRUE;
931             }
933             if (!clientWantsCursorHidden && cursorHidden)
934             {
935                 [NSCursor unhide];
936                 cursorHidden = FALSE;
937             }
938         }
939         else
940         {
941             if (cursorIsCurrent)
942             {
943                 [[NSCursor arrowCursor] set];
944                 cursorIsCurrent = FALSE;
945             }
946             if (cursorHidden)
947             {
948                 [NSCursor unhide];
949                 cursorHidden = FALSE;
950             }
951         }
952     }
954     - (void) hideCursor
955     {
956         if (!clientWantsCursorHidden)
957         {
958             clientWantsCursorHidden = TRUE;
959             [self updateCursor:TRUE];
960         }
961     }
963     - (void) unhideCursor
964     {
965         if (clientWantsCursorHidden)
966         {
967             clientWantsCursorHidden = FALSE;
968             [self updateCursor:FALSE];
969         }
970     }
972     - (void) setCursor:(NSCursor*)newCursor
973     {
974         if (newCursor != cursor)
975         {
976             [cursor release];
977             cursor = [newCursor retain];
978             cursorIsCurrent = FALSE;
979             [self updateCursor:FALSE];
980         }
981     }
983     - (void) setCursor
984     {
985         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
986         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
987         CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
988         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
989         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
990         CGPoint hotSpot;
992         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
993             hotSpot = CGPointZero;
994         hotSpot = cgpoint_mac_from_win(hotSpot);
995         self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
996         [image release];
997         [self unhideCursor];
998     }
1000     - (void) nextCursorFrame:(NSTimer*)theTimer
1001     {
1002         NSDictionary* frame;
1003         NSTimeInterval duration;
1004         NSDate* date;
1006         cursorFrame++;
1007         if (cursorFrame >= [cursorFrames count])
1008             cursorFrame = 0;
1009         [self setCursor];
1011         frame = [cursorFrames objectAtIndex:cursorFrame];
1012         duration = [[frame objectForKey:@"duration"] doubleValue];
1013         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1014         [cursorTimer setFireDate:date];
1015     }
1017     - (void) setCursorWithFrames:(NSArray*)frames
1018     {
1019         if (self.cursorFrames == frames)
1020             return;
1022         self.cursorFrames = frames;
1023         cursorFrame = 0;
1024         [cursorTimer invalidate];
1025         self.cursorTimer = nil;
1027         if ([frames count])
1028         {
1029             if ([frames count] > 1)
1030             {
1031                 NSDictionary* frame = [frames objectAtIndex:0];
1032                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
1033                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1034                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1035                                                              interval:1000000
1036                                                                target:self
1037                                                              selector:@selector(nextCursorFrame:)
1038                                                              userInfo:nil
1039                                                               repeats:YES] autorelease];
1040                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1041             }
1043             [self setCursor];
1044         }
1045     }
1047     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1048     {
1049         NSImage* nsimage = nil;
1051         if ([images count])
1052         {
1053             NSSize bestSize = NSZeroSize;
1054             id image;
1056             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1058             for (image in images)
1059             {
1060                 CGImageRef cgimage = (CGImageRef)image;
1061                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1062                 if (imageRep)
1063                 {
1064                     NSSize size = [imageRep size];
1066                     [nsimage addRepresentation:imageRep];
1067                     [imageRep release];
1069                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1070                         bestSize = size;
1071                 }
1072             }
1074             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1075                 [nsimage setSize:bestSize];
1076             else
1077                 nsimage = nil;
1078         }
1080         self.applicationIcon = nsimage;
1081     }
1083     - (void) handleCommandTab
1084     {
1085         if ([NSApp isActive])
1086         {
1087             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1088             NSRunningApplication* app;
1089             NSRunningApplication* otherValidApp = nil;
1091             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1092             {
1093                 NSNumber* displayID;
1094                 for (displayID in originalDisplayModes)
1095                 {
1096                     CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1097                     [latentDisplayModes setObject:(id)mode forKey:displayID];
1098                     CGDisplayModeRelease(mode);
1099                 }
1101                 CGRestorePermanentDisplayConfiguration();
1102                 CGReleaseAllDisplays();
1103                 [originalDisplayModes removeAllObjects];
1104                 displaysCapturedForFullscreen = FALSE;
1105             }
1107             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1108             {
1109                 if (![app isEqual:thisApp] && !app.terminated &&
1110                     app.activationPolicy == NSApplicationActivationPolicyRegular)
1111                 {
1112                     if (!app.hidden)
1113                     {
1114                         // There's another visible app.  Just hide ourselves and let
1115                         // the system activate the other app.
1116                         [NSApp hide:self];
1117                         return;
1118                     }
1120                     if (!otherValidApp)
1121                         otherValidApp = app;
1122                 }
1123             }
1125             // Didn't find a visible GUI app.  Try the Finder or, if that's not
1126             // running, the first hidden GUI app.  If even that doesn't work, we
1127             // just fail to switch and remain the active app.
1128             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1129             if (!app) app = otherValidApp;
1130             [app unhide];
1131             [app activateWithOptions:0];
1132         }
1133     }
1135     - (BOOL) setCursorPosition:(CGPoint)pos
1136     {
1137         BOOL ret;
1139         if ([windowsBeingDragged count])
1140             ret = FALSE;
1141         else if (self.clippingCursor && [clipCursorHandler respondsToSelector:@selector(setCursorPosition:)])
1142             ret = [clipCursorHandler setCursorPosition:pos];
1143         else
1144         {
1145             if (self.clippingCursor)
1146                 [clipCursorHandler clipCursorLocation:&pos];
1148             // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1149             // the mouse from the cursor position for 0.25 seconds.  This means
1150             // that mouse movement during that interval doesn't move the cursor
1151             // and events carry a constant location (the warped-to position)
1152             // even though they have delta values.  For apps which warp the
1153             // cursor frequently (like after every mouse move), this makes
1154             // cursor movement horribly laggy and jerky, as only a fraction of
1155             // mouse move events have any effect.
1156             //
1157             // On some versions of OS X, it's sufficient to forcibly reassociate
1158             // the mouse and cursor position.  On others, it's necessary to set
1159             // the local events suppression interval to 0 for the warp.  That's
1160             // deprecated, but I'm not aware of any other way.  For good
1161             // measure, we do both.
1162             CGSetLocalEventsSuppressionInterval(0);
1163             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1164             CGSetLocalEventsSuppressionInterval(0.25);
1165             if (ret)
1166             {
1167                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1169                 CGAssociateMouseAndMouseCursorPosition(true);
1170             }
1171         }
1173         if (ret)
1174         {
1175             WineEventQueue* queue;
1177             // Discard all pending mouse move events.
1178             [eventQueuesLock lock];
1179             for (queue in eventQueues)
1180             {
1181                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED_RELATIVE) |
1182                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1183                                        forWindow:nil];
1184                 [queue resetMouseEventPositions:pos];
1185             }
1186             [eventQueuesLock unlock];
1187         }
1189         return ret;
1190     }
1192     - (void) updateWindowsForCursorClipping
1193     {
1194         WineWindow* window;
1195         for (window in [NSApp windows])
1196         {
1197             if ([window isKindOfClass:[WineWindow class]])
1198                 [window updateForCursorClipping];
1199         }
1200     }
1202     - (BOOL) startClippingCursor:(CGRect)rect
1203     {
1204         if (!clipCursorHandler) {
1205             if (use_confinement_cursor_clipping && [WineConfinementClipCursorHandler isAvailable])
1206                 clipCursorHandler = [[WineConfinementClipCursorHandler alloc] init];
1207             else
1208                 clipCursorHandler = [[WineEventTapClipCursorHandler alloc] init];
1209         }
1211         if (self.clippingCursor && CGRectEqualToRect(rect, clipCursorHandler.cursorClipRect))
1212             return TRUE;
1214         if (![clipCursorHandler startClippingCursor:rect])
1215             return FALSE;
1217         [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1219         [self updateWindowsForCursorClipping];
1221         return TRUE;
1222     }
1224     - (BOOL) stopClippingCursor
1225     {
1226         if (!self.clippingCursor)
1227             return TRUE;
1229         if (![clipCursorHandler stopClippingCursor])
1230             return FALSE;
1232         lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1234         [self updateWindowsForCursorClipping];
1236         return TRUE;
1237     }
1239     - (BOOL) clippingCursor
1240     {
1241         return clipCursorHandler.clippingCursor;
1242     }
1244     - (BOOL) isKeyPressed:(uint16_t)keyCode
1245     {
1246         int bits = sizeof(pressedKeyCodes[0]) * 8;
1247         int index = keyCode / bits;
1248         uint32_t mask = 1 << (keyCode % bits);
1249         return (pressedKeyCodes[index] & mask) != 0;
1250     }
1252     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1253     {
1254         int bits = sizeof(pressedKeyCodes[0]) * 8;
1255         int index = keyCode / bits;
1256         uint32_t mask = 1 << (keyCode % bits);
1257         if (pressed)
1258             pressedKeyCodes[index] |= mask;
1259         else
1260             pressedKeyCodes[index] &= ~mask;
1261     }
1263     - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1264     {
1265         if (dragged)
1266             [windowsBeingDragged addObject:window];
1267         else
1268             [windowsBeingDragged removeObject:window];
1269     }
1271     - (void) windowWillOrderOut:(WineWindow*)window
1272     {
1273         if ([windowsBeingDragged containsObject:window])
1274         {
1275             [self window:window isBeingDragged:NO];
1277             macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1278             [window.queue postEvent:event];
1279             macdrv_release_event(event);
1280         }
1281     }
1283     - (BOOL) isAnyWineWindowVisible
1284     {
1285         for (WineWindow* w in [NSApp windows])
1286         {
1287             if ([w isKindOfClass:[WineWindow class]] && ![w isMiniaturized] && [w isVisible])
1288                 return YES;
1289         }
1291         return NO;
1292     }
1294     - (void) handleWindowDrag:(WineWindow*)window begin:(BOOL)begin
1295     {
1296         macdrv_event* event;
1297         int eventType;
1299         if (begin)
1300         {
1301             [windowsBeingDragged addObject:window];
1302             eventType = WINDOW_DRAG_BEGIN;
1303         }
1304         else
1305         {
1306             [windowsBeingDragged removeObject:window];
1307             eventType = WINDOW_DRAG_END;
1308         }
1310         event = macdrv_create_event(eventType, window);
1311         if (eventType == WINDOW_DRAG_BEGIN)
1312             event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
1313         [window.queue postEvent:event];
1314         macdrv_release_event(event);
1315     }
1317     - (void) handleMouseMove:(NSEvent*)anEvent
1318     {
1319         WineWindow* targetWindow;
1320         BOOL drag = [anEvent type] != NSEventTypeMouseMoved;
1322         if ([windowsBeingDragged count])
1323             targetWindow = nil;
1324         else if (mouseCaptureWindow)
1325             targetWindow = mouseCaptureWindow;
1326         else if (drag)
1327             targetWindow = (WineWindow*)[anEvent window];
1328         else
1329         {
1330             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1331                event indicates its window is the main window, even if the cursor is
1332                over a different window.  Find the actual WineWindow that is under the
1333                cursor and post the event as being for that window. */
1334             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1335             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1336             NSInteger windowUnderNumber;
1338             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1339                                   belowWindowWithWindowNumber:0];
1340             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1341             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1342                 targetWindow = nil;
1343         }
1345         if ([targetWindow isKindOfClass:[WineWindow class]])
1346         {
1347             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1348             macdrv_event* event;
1349             BOOL absolute;
1351             // If we recently warped the cursor (other than in our cursor-clipping
1352             // event tap), discard mouse move events until we see an event which is
1353             // later than that time.
1354             if (lastSetCursorPositionTime)
1355             {
1356                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1357                     return;
1359                 lastSetCursorPositionTime = 0;
1360                 forceNextMouseMoveAbsolute = TRUE;
1361             }
1363             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1364             {
1365                 absolute = TRUE;
1366                 forceNextMouseMoveAbsolute = FALSE;
1367             }
1368             else
1369             {
1370                 // Send absolute move events if the cursor is in the interior of
1371                 // its range.  Only send relative moves if the cursor is pinned to
1372                 // the boundaries of where it can go.  We compute the position
1373                 // that's one additional point in the direction of movement.  If
1374                 // that is outside of the clipping rect or desktop region (the
1375                 // union of the screen frames), then we figure the cursor would
1376                 // have moved outside if it could but it was pinned.
1377                 CGPoint computedPoint = point;
1378                 CGFloat deltaX = [anEvent deltaX];
1379                 CGFloat deltaY = [anEvent deltaY];
1381                 if (deltaX > 0.001)
1382                     computedPoint.x++;
1383                 else if (deltaX < -0.001)
1384                     computedPoint.x--;
1386                 if (deltaY > 0.001)
1387                     computedPoint.y++;
1388                 else if (deltaY < -0.001)
1389                     computedPoint.y--;
1391                 // Assume cursor is pinned for now
1392                 absolute = FALSE;
1393                 if (!self.clippingCursor || CGRectContainsPoint(clipCursorHandler.cursorClipRect, computedPoint))
1394                 {
1395                     const CGRect* rects;
1396                     NSUInteger count, i;
1398                     // Caches screenFrameCGRects if necessary
1399                     [self primaryScreenHeight];
1401                     rects = [screenFrameCGRects bytes];
1402                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1404                     for (i = 0; i < count; i++)
1405                     {
1406                         if (CGRectContainsPoint(rects[i], computedPoint))
1407                         {
1408                             absolute = TRUE;
1409                             break;
1410                         }
1411                     }
1412                 }
1413             }
1415             if (absolute)
1416             {
1417                 if (self.clippingCursor)
1418                     [clipCursorHandler clipCursorLocation:&point];
1419                 point = cgpoint_win_from_mac(point);
1421                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1422                 event->mouse_moved.x = floor(point.x);
1423                 event->mouse_moved.y = floor(point.y);
1425                 mouseMoveDeltaX = 0;
1426                 mouseMoveDeltaY = 0;
1427             }
1428             else
1429             {
1430                 double scale = retina_on ? 2 : 1;
1432                 /* Add event delta to accumulated delta error */
1433                 /* deltaY is already flipped */
1434                 mouseMoveDeltaX += [anEvent deltaX];
1435                 mouseMoveDeltaY += [anEvent deltaY];
1437                 event = macdrv_create_event(MOUSE_MOVED_RELATIVE, targetWindow);
1438                 event->mouse_moved.x = mouseMoveDeltaX * scale;
1439                 event->mouse_moved.y = mouseMoveDeltaY * scale;
1441                 /* Keep the remainder after integer truncation. */
1442                 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1443                 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1444             }
1446             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1447             {
1448                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1449                 event->mouse_moved.drag = drag;
1451                 [targetWindow.queue postEvent:event];
1452             }
1454             macdrv_release_event(event);
1456             lastTargetWindow = targetWindow;
1457         }
1458         else
1459             lastTargetWindow = nil;
1461         [self updateCursor:FALSE];
1462     }
1464     - (void) handleMouseButton:(NSEvent*)theEvent
1465     {
1466         WineWindow* window = (WineWindow*)[theEvent window];
1467         NSEventType type = [theEvent type];
1468         WineWindow* windowBroughtForward = nil;
1469         BOOL process = FALSE;
1471         if ([window isKindOfClass:[WineWindow class]] &&
1472             type == NSEventTypeLeftMouseDown &&
1473             ![theEvent wine_commandKeyDown])
1474         {
1475             NSWindowButton windowButton;
1477             windowBroughtForward = window;
1479             /* Any left-click on our window anyplace other than the close or
1480                minimize buttons will bring it forward. */
1481             for (windowButton = NSWindowCloseButton;
1482                  windowButton <= NSWindowMiniaturizeButton;
1483                  windowButton++)
1484             {
1485                 NSButton* button = [window standardWindowButton:windowButton];
1486                 if (button)
1487                 {
1488                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1489                     if ([button mouse:point inRect:[button bounds]])
1490                     {
1491                         windowBroughtForward = nil;
1492                         break;
1493                     }
1494                 }
1495             }
1496         }
1498         if ([windowsBeingDragged count])
1499             window = nil;
1500         else if (mouseCaptureWindow)
1501             window = mouseCaptureWindow;
1503         if ([window isKindOfClass:[WineWindow class]])
1504         {
1505             BOOL pressed = (type == NSEventTypeLeftMouseDown ||
1506                             type == NSEventTypeRightMouseDown ||
1507                             type == NSEventTypeOtherMouseDown);
1508             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1510             if (self.clippingCursor)
1511                 [clipCursorHandler clipCursorLocation:&pt];
1513             if (pressed)
1514             {
1515                 if (mouseCaptureWindow)
1516                     process = TRUE;
1517                 else
1518                 {
1519                     // Test if the click was in the window's content area.
1520                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1521                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1522                     process = NSMouseInRect(nspoint, contentRect, NO);
1523                     if (process && [window styleMask] & NSWindowStyleMaskResizable)
1524                     {
1525                         // Ignore clicks in the grow box (resize widget).
1526                         HIPoint origin = { 0, 0 };
1527                         HIThemeGrowBoxDrawInfo info = { 0 };
1528                         HIRect bounds;
1529                         OSStatus status;
1531                         info.kind = kHIThemeGrowBoxKindNormal;
1532                         info.direction = kThemeGrowRight | kThemeGrowDown;
1533                         if ([window styleMask] & NSWindowStyleMaskUtilityWindow)
1534                             info.size = kHIThemeGrowBoxSizeSmall;
1535                         else
1536                             info.size = kHIThemeGrowBoxSizeNormal;
1538                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1539                         if (status == noErr)
1540                         {
1541                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1542                                                         NSMinY(contentRect),
1543                                                         bounds.size.width,
1544                                                         bounds.size.height);
1545                             process = !NSMouseInRect(nspoint, growBox, NO);
1546                         }
1547                     }
1548                 }
1549                 if (process)
1550                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1551             }
1552             else
1553             {
1554                 NSEventType downType = type - 1;
1555                 NSUInteger downMask = NSEventMaskFromType(downType);
1556                 process = (unmatchedMouseDowns & downMask) != 0;
1557                 unmatchedMouseDowns &= ~downMask;
1558             }
1560             if (process)
1561             {
1562                 macdrv_event* event;
1564                 pt = cgpoint_win_from_mac(pt);
1566                 event = macdrv_create_event(MOUSE_BUTTON, window);
1567                 event->mouse_button.button = [theEvent buttonNumber];
1568                 event->mouse_button.pressed = pressed;
1569                 event->mouse_button.x = floor(pt.x);
1570                 event->mouse_button.y = floor(pt.y);
1571                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1573                 [window.queue postEvent:event];
1575                 macdrv_release_event(event);
1576             }
1577         }
1579         if (windowBroughtForward)
1580         {
1581             WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1582             NSInteger ancestorNumber = [ancestor windowNumber];
1583             NSInteger ancestorLevel = [ancestor level];
1585             for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1586             {
1587                 NSInteger windowNumber = [windowNumberObject integerValue];
1588                 if (windowNumber == ancestorNumber)
1589                     break;
1590                 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1591                 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1592                     [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1593                 {
1594                     [ancestor postBroughtForwardEvent];
1595                     break;
1596                 }
1597             }
1598             if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noForeground)
1599                 [self windowGotFocus:windowBroughtForward];
1600         }
1602         // Since mouse button events deliver absolute cursor position, the
1603         // accumulating delta from move events is invalidated.  Make sure
1604         // next mouse move event starts over from an absolute baseline.
1605         // Also, it's at least possible that the title bar widgets (e.g. close
1606         // button, etc.) could enter an internal event loop on a mouse down that
1607         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1608         // dragged events and, after that, any notion of the cursor position
1609         // computed from accumulating deltas would be wrong.
1610         forceNextMouseMoveAbsolute = TRUE;
1611     }
1613     - (void) handleScrollWheel:(NSEvent*)theEvent
1614     {
1615         WineWindow* window;
1617         if (mouseCaptureWindow)
1618             window = mouseCaptureWindow;
1619         else
1620             window = (WineWindow*)[theEvent window];
1622         if ([window isKindOfClass:[WineWindow class]])
1623         {
1624             CGEventRef cgevent = [theEvent CGEvent];
1625             CGPoint pt = CGEventGetLocation(cgevent);
1626             BOOL process;
1628             if (self.clippingCursor)
1629                 [clipCursorHandler clipCursorLocation:&pt];
1631             if (mouseCaptureWindow)
1632                 process = TRUE;
1633             else
1634             {
1635                 // Only process the event if it was in the window's content area.
1636                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1637                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1638                 process = NSMouseInRect(nspoint, contentRect, NO);
1639             }
1641             if (process)
1642             {
1643                 macdrv_event* event;
1644                 double x, y;
1645                 BOOL continuous = FALSE;
1647                 pt = cgpoint_win_from_mac(pt);
1649                 event = macdrv_create_event(MOUSE_SCROLL, window);
1650                 event->mouse_scroll.x = floor(pt.x);
1651                 event->mouse_scroll.y = floor(pt.y);
1652                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1654                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1655                 {
1656                     continuous = TRUE;
1658                     /* Continuous scroll wheel events come from high-precision scrolling
1659                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1660                        For these, we can get more precise data from the CGEvent API. */
1661                     /* Axis 1 is vertical, axis 2 is horizontal. */
1662                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1663                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1664                 }
1665                 else
1666                 {
1667                     double pixelsPerLine = 10;
1668                     CGEventSourceRef source;
1670                     /* The non-continuous values are in units of "lines", not pixels. */
1671                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1672                     {
1673                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1674                         CFRelease(source);
1675                     }
1677                     x = pixelsPerLine * [theEvent deltaX];
1678                     y = pixelsPerLine * [theEvent deltaY];
1679                 }
1681                 /* Mac: negative is right or down, positive is left or up.
1682                    Win32: negative is left or down, positive is right or up.
1683                    So, negate the X scroll value to translate. */
1684                 x = -x;
1686                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1687                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1688                    6 times the pixel value. */
1689                 x *= 6;
1690                 y *= 6;
1692                 if (use_precise_scrolling)
1693                 {
1694                     event->mouse_scroll.x_scroll = x;
1695                     event->mouse_scroll.y_scroll = y;
1697                     if (!continuous)
1698                     {
1699                         /* For non-continuous "clicky" wheels, if there was any motion, make
1700                            sure there was at least WHEEL_DELTA motion.  This is so, at slow
1701                            speeds where the system's acceleration curve is actually reducing the
1702                            scroll distance, the user is sure to get some action out of each click.
1703                            For example, this is important for rotating though weapons in a
1704                            first-person shooter. */
1705                         if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1706                             event->mouse_scroll.x_scroll = 120;
1707                         else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1708                             event->mouse_scroll.x_scroll = -120;
1710                         if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1711                             event->mouse_scroll.y_scroll = 120;
1712                         else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1713                             event->mouse_scroll.y_scroll = -120;
1714                     }
1715                 }
1716                 else
1717                 {
1718                     /* If it's been a while since the last scroll event or if the scrolling has
1719                        reversed direction, reset the accumulated scroll value. */
1720                     if ([theEvent timestamp] - lastScrollTime > 1)
1721                         accumScrollX = accumScrollY = 0;
1722                     else
1723                     {
1724                         /* The accumulated scroll value is in the opposite direction/sign of the last
1725                            scroll.  That's because it's the "debt" resulting from over-scrolling in
1726                            that direction.  We accumulate by adding in the scroll amount and then, if
1727                            it has the same sign as the scroll value, we subtract any whole or partial
1728                            WHEEL_DELTAs, leaving it 0 or the opposite sign.  So, the user switched
1729                            scroll direction if the accumulated debt and the new scroll value have the
1730                            same sign. */
1731                         if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1732                             accumScrollX = 0;
1733                         if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1734                             accumScrollY = 0;
1735                     }
1736                     lastScrollTime = [theEvent timestamp];
1738                     accumScrollX += x;
1739                     accumScrollY += y;
1741                     if (accumScrollX > 0 && x > 0)
1742                         event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1743                     if (accumScrollX < 0 && x < 0)
1744                         event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1745                     if (accumScrollY > 0 && y > 0)
1746                         event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1747                     if (accumScrollY < 0 && y < 0)
1748                         event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1750                     accumScrollX -= event->mouse_scroll.x_scroll;
1751                     accumScrollY -= event->mouse_scroll.y_scroll;
1752                 }
1754                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1755                     [window.queue postEvent:event];
1757                 macdrv_release_event(event);
1759                 // Since scroll wheel events deliver absolute cursor position, the
1760                 // accumulating delta from move events is invalidated.  Make sure next
1761                 // mouse move event starts over from an absolute baseline.
1762                 forceNextMouseMoveAbsolute = TRUE;
1763             }
1764         }
1765     }
1767     // Returns TRUE if the event was handled and caller should do nothing more
1768     // with it.  Returns FALSE if the caller should process it as normal and
1769     // then call -didSendEvent:.
1770     - (BOOL) handleEvent:(NSEvent*)anEvent
1771     {
1772         BOOL ret = FALSE;
1773         NSEventType type = [anEvent type];
1775         if (type == NSEventTypeFlagsChanged)
1776             self.lastFlagsChanged = anEvent;
1777         else if (type == NSEventTypeMouseMoved || type == NSEventTypeLeftMouseDragged ||
1778                  type == NSEventTypeRightMouseDragged || type == NSEventTypeOtherMouseDragged)
1779         {
1780             [self handleMouseMove:anEvent];
1781             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1782         }
1783         else if (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp ||
1784                  type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp ||
1785                  type == NSEventTypeOtherMouseDown || type == NSEventTypeOtherMouseUp)
1786         {
1787             [self handleMouseButton:anEvent];
1788             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1789         }
1790         else if (type == NSEventTypeScrollWheel)
1791         {
1792             [self handleScrollWheel:anEvent];
1793             ret = mouseCaptureWindow != nil;
1794         }
1795         else if (type == NSEventTypeKeyDown)
1796         {
1797             // -[NSApplication sendEvent:] seems to consume presses of the Help
1798             // key (Insert key on PC keyboards), so we have to bypass it and
1799             // send the event directly to the window.
1800             if (anEvent.keyCode == kVK_Help)
1801             {
1802                 [anEvent.window sendEvent:anEvent];
1803                 ret = TRUE;
1804             }
1805         }
1806         else if (type == NSEventTypeKeyUp)
1807         {
1808             uint16_t keyCode = [anEvent keyCode];
1809             if ([self isKeyPressed:keyCode])
1810             {
1811                 WineWindow* window = (WineWindow*)[anEvent window];
1812                 [self noteKey:keyCode pressed:FALSE];
1813                 if ([window isKindOfClass:[WineWindow class]])
1814                     [window postKeyEvent:anEvent];
1815             }
1816         }
1817         else if (!useDragNotifications && type == NSEventTypeAppKitDefined)
1818         {
1819             WineWindow *window = (WineWindow *)[anEvent window];
1820             short subtype = [anEvent subtype];
1822             // These subtypes are not documented but they appear to mean
1823             // "a window is being dragged" and "a window is no longer being
1824             // dragged", respectively.
1825             if ((subtype == 20 || subtype == 21) && [window isKindOfClass:[WineWindow class]])
1826                 [self handleWindowDrag:window begin:(subtype == 20)];
1827         }
1829         return ret;
1830     }
1832     - (void) didSendEvent:(NSEvent*)anEvent
1833     {
1834         NSEventType type = [anEvent type];
1836         if (type == NSEventTypeKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1837         {
1838             NSUInteger modifiers = [anEvent modifierFlags];
1839             if ((modifiers & NSEventModifierFlagCommand) &&
1840                 !(modifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)))
1841             {
1842                 // Command-Tab and Command-Shift-Tab would normally be intercepted
1843                 // by the system to switch applications.  If we're seeing it, it's
1844                 // presumably because we've captured the displays, preventing
1845                 // normal application switching.  Do it manually.
1846                 [self handleCommandTab];
1847             }
1848         }
1849     }
1851     - (void) setupObservations
1852     {
1853         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1854         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1855         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
1857         [nc addObserverForName:NSWindowDidBecomeKeyNotification
1858                         object:nil
1859                          queue:nil
1860                     usingBlock:^(NSNotification *note){
1861             NSWindow* window = [note object];
1862             [keyWindows removeObjectIdenticalTo:window];
1863             [keyWindows insertObject:window atIndex:0];
1864         }];
1866         [nc addObserverForName:NSWindowWillCloseNotification
1867                         object:nil
1868                          queue:[NSOperationQueue mainQueue]
1869                     usingBlock:^(NSNotification *note){
1870             NSWindow* window = [note object];
1871             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
1872                 return;
1873             [keyWindows removeObjectIdenticalTo:window];
1874             if (window == lastTargetWindow)
1875                 lastTargetWindow = nil;
1876             if (window == self.mouseCaptureWindow)
1877                 self.mouseCaptureWindow = nil;
1878             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
1879             {
1880                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
1881                     [self updateFullscreenWindows];
1882                 });
1883             }
1884             [windowsBeingDragged removeObject:window];
1885         }];
1887         if (useDragNotifications) {
1888             [nc addObserverForName:NSWindowWillStartDraggingNotification
1889                             object:nil
1890                              queue:[NSOperationQueue mainQueue]
1891                         usingBlock:^(NSNotification *note){
1892                 NSWindow* window = [note object];
1893                 if ([window isKindOfClass:[WineWindow class]])
1894                     [self handleWindowDrag:(WineWindow *)window begin:YES];
1895             }];
1897             [nc addObserverForName:NSWindowDidEndDraggingNotification
1898                             object:nil
1899                              queue:[NSOperationQueue mainQueue]
1900                         usingBlock:^(NSNotification *note){
1901                 NSWindow* window = [note object];
1902                 if ([window isKindOfClass:[WineWindow class]])
1903                     [self handleWindowDrag:(WineWindow *)window begin:NO];
1904             }];
1905         }
1907         [nc addObserver:self
1908                selector:@selector(keyboardSelectionDidChange)
1909                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
1910                  object:nil];
1912         /* The above notification isn't sent unless the NSTextInputContext
1913            class has initialized itself.  Poke it. */
1914         [NSTextInputContext self];
1916         [wsnc addObserver:self
1917                  selector:@selector(activeSpaceDidChange)
1918                      name:NSWorkspaceActiveSpaceDidChangeNotification
1919                    object:nil];
1921         [nc addObserver:self
1922                selector:@selector(releaseMouseCapture)
1923                    name:NSMenuDidBeginTrackingNotification
1924                  object:nil];
1926         [dnc        addObserver:self
1927                        selector:@selector(releaseMouseCapture)
1928                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
1929                          object:nil
1930              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
1932         [dnc addObserver:self
1933                 selector:@selector(enabledKeyboardInputSourcesChanged)
1934                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
1935                   object:nil];
1936     }
1938     - (BOOL) inputSourceIsInputMethod
1939     {
1940         if (!inputSourceIsInputMethodValid)
1941         {
1942             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
1943             if (inputSource)
1944             {
1945                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
1946                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
1947                 CFRelease(inputSource);
1948             }
1949             else
1950                 inputSourceIsInputMethod = FALSE;
1951             inputSourceIsInputMethodValid = TRUE;
1952         }
1954         return inputSourceIsInputMethod;
1955     }
1957     - (void) releaseMouseCapture
1958     {
1959         // This might be invoked on a background thread by the distributed
1960         // notification center.  Shunt it to the main thread.
1961         if (![NSThread isMainThread])
1962         {
1963             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
1964             return;
1965         }
1967         if (mouseCaptureWindow)
1968         {
1969             macdrv_event* event;
1971             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
1972             [mouseCaptureWindow.queue postEvent:event];
1973             macdrv_release_event(event);
1974         }
1975     }
1977     - (void) unminimizeWindowIfNoneVisible
1978     {
1979         if (![self frontWineWindow])
1980         {
1981             for (WineWindow* window in [NSApp windows])
1982             {
1983                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
1984                 {
1985                     [window deminiaturize:self];
1986                     break;
1987                 }
1988             }
1989         }
1990     }
1992     - (void) setRetinaMode:(int)mode
1993     {
1994         retina_on = mode;
1996         [clipCursorHandler setRetinaMode:mode];
1998         for (WineWindow* window in [NSApp windows])
1999         {
2000             if ([window isKindOfClass:[WineWindow class]])
2001                 [window setRetinaMode:mode];
2002         }
2003     }
2006     /*
2007      * ---------- NSApplicationDelegate methods ----------
2008      */
2009     - (void)applicationDidBecomeActive:(NSNotification *)notification
2010     {
2011         NSNumber* displayID;
2012         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2014         latentDisplayModes = [[NSMutableDictionary alloc] init];
2015         for (displayID in modesToRealize)
2016         {
2017             CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2018             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2019         }
2021         [self updateFullscreenWindows];
2022         [self adjustWindowLevels:YES];
2024         if (beenActive)
2025             [self unminimizeWindowIfNoneVisible];
2026         beenActive = TRUE;
2028         // If a Wine process terminates abruptly while it has the display captured
2029         // and switched to a different resolution, Mac OS X will uncapture the
2030         // displays and switch their resolutions back.  However, the other Wine
2031         // processes won't have their notion of the desktop rect changed back.
2032         // This can lead them to refuse to draw or acknowledge clicks in certain
2033         // portions of their windows.
2034         //
2035         // To solve this, we synthesize a displays-changed event whenever we're
2036         // activated.  This will provoke a re-synchronization of Wine's notion of
2037         // the desktop rect with the actual state.
2038         [self sendDisplaysChanged:TRUE];
2040         // The cursor probably moved while we were inactive.  Accumulated mouse
2041         // movement deltas are invalidated.  Make sure the next mouse move event
2042         // starts over from an absolute baseline.
2043         forceNextMouseMoveAbsolute = TRUE;
2044     }
2046     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2047     {
2048         primaryScreenHeightValid = FALSE;
2049         [self sendDisplaysChanged:FALSE];
2050         [self adjustWindowLevels];
2052         // When the display configuration changes, the cursor position may jump.
2053         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2054         // mouse move event starts over from an absolute baseline.
2055         forceNextMouseMoveAbsolute = TRUE;
2056     }
2058     - (void)applicationDidResignActive:(NSNotification *)notification
2059     {
2060         macdrv_event* event;
2061         WineEventQueue* queue;
2063         [self invalidateGotFocusEvents];
2065         event = macdrv_create_event(APP_DEACTIVATED, nil);
2067         [eventQueuesLock lock];
2068         for (queue in eventQueues)
2069             [queue postEvent:event];
2070         [eventQueuesLock unlock];
2072         macdrv_release_event(event);
2074         [self releaseMouseCapture];
2075     }
2077     - (void) applicationDidUnhide:(NSNotification*)aNotification
2078     {
2079         [self adjustWindowLevels];
2080     }
2082     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2083     {
2084         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2085         // don't count as "visible windows" for this purpose.
2086         [self unminimizeWindowIfNoneVisible];
2087         return YES;
2088     }
2090     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2091     {
2092         NSApplicationTerminateReply ret = NSTerminateNow;
2093         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2094         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2095         macdrv_event* event;
2096         WineEventQueue* queue;
2098         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2099         event->deliver = 1;
2100         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2101         {
2102             case kAELogOut:
2103             case kAEReallyLogOut:
2104                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2105                 break;
2106             case kAEShowRestartDialog:
2107                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2108                 break;
2109             case kAEShowShutdownDialog:
2110                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2111                 break;
2112             default:
2113                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2114                 break;
2115         }
2117         [eventQueuesLock lock];
2119         if ([eventQueues count])
2120         {
2121             for (queue in eventQueues)
2122                 [queue postEvent:event];
2123             ret = NSTerminateLater;
2124         }
2126         [eventQueuesLock unlock];
2128         macdrv_release_event(event);
2130         return ret;
2131     }
2133     - (void)applicationWillBecomeActive:(NSNotification *)notification
2134     {
2135         macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2136         event->deliver = 1;
2138         [eventQueuesLock lock];
2139         for (WineEventQueue* queue in eventQueues)
2140             [queue postEvent:event];
2141         [eventQueuesLock unlock];
2143         macdrv_release_event(event);
2144     }
2146     - (void)applicationWillResignActive:(NSNotification *)notification
2147     {
2148         [self adjustWindowLevels:NO];
2149     }
2151 /***********************************************************************
2152  *              PerformRequest
2154  * Run-loop-source perform callback.  Pull request blocks from the
2155  * array of queued requests and invoke them.
2156  */
2157 static void PerformRequest(void *info)
2159     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2160     WineApplicationController* controller = [WineApplicationController sharedController];
2162     for (;;)
2163     {
2164         __block dispatch_block_t block;
2166         dispatch_sync(controller->requestsManipQueue, ^{
2167             if ([controller->requests count])
2168             {
2169                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2170                 [controller->requests removeObjectAtIndex:0];
2171             }
2172             else
2173                 block = nil;
2174         });
2176         if (!block)
2177             break;
2179         block();
2180         [block release];
2182         [pool release];
2183         pool = [[NSAutoreleasePool alloc] init];
2184     }
2186     [pool release];
2189 /***********************************************************************
2190  *              OnMainThreadAsync
2192  * Run a block on the main thread asynchronously.
2193  */
2194 void OnMainThreadAsync(dispatch_block_t block)
2196     WineApplicationController* controller = [WineApplicationController sharedController];
2198     block = [block copy];
2199     dispatch_sync(controller->requestsManipQueue, ^{
2200         [controller->requests addObject:block];
2201     });
2202     [block release];
2203     CFRunLoopSourceSignal(controller->requestSource);
2204     CFRunLoopWakeUp(CFRunLoopGetMain());
2207 @end
2209 /***********************************************************************
2210  *              LogError
2211  */
2212 void LogError(const char* func, NSString* format, ...)
2214     va_list args;
2215     va_start(args, format);
2216     LogErrorv(func, format, args);
2217     va_end(args);
2220 /***********************************************************************
2221  *              LogErrorv
2222  */
2223 void LogErrorv(const char* func, NSString* format, va_list args)
2225     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2227     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2228     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2229     [message release];
2231     [pool release];
2234 /***********************************************************************
2235  *              macdrv_window_rejected_focus
2237  * Pass focus to the next window that hasn't already rejected this same
2238  * WINDOW_GOT_FOCUS event.
2239  */
2240 void macdrv_window_rejected_focus(const macdrv_event *event)
2242     OnMainThread(^{
2243         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2244     });
2247 /***********************************************************************
2248  *              macdrv_get_input_source_info
2250  * Returns the keyboard layout uchr data, keyboard type and input source.
2251  */
2252 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2254     OnMainThread(^{
2255         TISInputSourceRef inputSourceLayout;
2257         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2258         if (inputSourceLayout)
2259         {
2260             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2261                                 kTISPropertyUnicodeKeyLayoutData);
2262             *uchr = CFDataCreateCopy(NULL, data);
2263             CFRelease(inputSourceLayout);
2265             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2266             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2267             if (input_source)
2268                 *input_source = TISCopyCurrentKeyboardInputSource();
2269         }
2270     });
2273 /***********************************************************************
2274  *              macdrv_beep
2276  * Play the beep sound configured by the user in System Preferences.
2277  */
2278 void macdrv_beep(void)
2280     OnMainThreadAsync(^{
2281         NSBeep();
2282     });
2285 /***********************************************************************
2286  *              macdrv_set_display_mode
2287  */
2288 int macdrv_set_display_mode(const struct macdrv_display* display,
2289                             CGDisplayModeRef display_mode)
2291     __block int ret;
2293     OnMainThread(^{
2294         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2295     });
2297     return ret;
2300 /***********************************************************************
2301  *              macdrv_set_cursor
2303  * Set the cursor.
2305  * If name is non-NULL, it is a selector for a class method on NSCursor
2306  * identifying the cursor to set.  In that case, frames is ignored.  If
2307  * name is NULL, then frames is used.
2309  * frames is an array of dictionaries.  Each dictionary is a frame of
2310  * an animated cursor.  Under the key "image" is a CGImage for the
2311  * frame.  Under the key "duration" is a CFNumber time interval, in
2312  * seconds, for how long that frame is presented before proceeding to
2313  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2314  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2315  * This is the hot spot, measured in pixels down and to the right of the
2316  * top-left corner of the image.
2318  * If the array has exactly 1 element, the cursor is static, not
2319  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2320  */
2321 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2323     SEL sel;
2325     sel = NSSelectorFromString((NSString*)name);
2326     if (sel)
2327     {
2328         OnMainThreadAsync(^{
2329             WineApplicationController* controller = [WineApplicationController sharedController];
2330             [controller setCursorWithFrames:nil];
2331             controller.cursor = [NSCursor performSelector:sel];
2332             [controller unhideCursor];
2333         });
2334     }
2335     else
2336     {
2337         NSArray* nsframes = (NSArray*)frames;
2338         if ([nsframes count])
2339         {
2340             OnMainThreadAsync(^{
2341                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2342             });
2343         }
2344         else
2345         {
2346             OnMainThreadAsync(^{
2347                 WineApplicationController* controller = [WineApplicationController sharedController];
2348                 [controller setCursorWithFrames:nil];
2349                 [controller hideCursor];
2350             });
2351         }
2352     }
2355 /***********************************************************************
2356  *              macdrv_get_cursor_position
2358  * Obtains the current cursor position.  Returns zero on failure,
2359  * non-zero on success.
2360  */
2361 int macdrv_get_cursor_position(CGPoint *pos)
2363     OnMainThread(^{
2364         NSPoint location = [NSEvent mouseLocation];
2365         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2366         *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2367     });
2369     return TRUE;
2372 /***********************************************************************
2373  *              macdrv_set_cursor_position
2375  * Sets the cursor position without generating events.  Returns zero on
2376  * failure, non-zero on success.
2377  */
2378 int macdrv_set_cursor_position(CGPoint pos)
2380     __block int ret;
2382     OnMainThread(^{
2383         ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2384     });
2386     return ret;
2389 /***********************************************************************
2390  *              macdrv_clip_cursor
2392  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2393  * to or larger than the whole desktop region, the cursor is unclipped.
2394  * Returns zero on failure, non-zero on success.
2395  */
2396 int macdrv_clip_cursor(CGRect r)
2398     __block int ret;
2400     OnMainThread(^{
2401         WineApplicationController* controller = [WineApplicationController sharedController];
2402         BOOL clipping = FALSE;
2403         CGRect rect = r;
2405         if (!CGRectIsInfinite(rect))
2406             rect = cgrect_mac_from_win(rect);
2408         if (!CGRectIsInfinite(rect))
2409         {
2410             NSRect nsrect = NSRectFromCGRect(rect);
2411             NSScreen* screen;
2413             /* Convert the rectangle from top-down coords to bottom-up. */
2414             [controller flipRect:&nsrect];
2416             clipping = FALSE;
2417             for (screen in [NSScreen screens])
2418             {
2419                 if (!NSContainsRect(nsrect, [screen frame]))
2420                 {
2421                     clipping = TRUE;
2422                     break;
2423                 }
2424             }
2425         }
2427         if (clipping)
2428             ret = [controller startClippingCursor:rect];
2429         else
2430             ret = [controller stopClippingCursor];
2431     });
2433     return ret;
2436 /***********************************************************************
2437  *              macdrv_set_application_icon
2439  * Set the application icon.  The images array contains CGImages.  If
2440  * there are more than one, then they represent different sizes or
2441  * color depths from the icon resource.  If images is NULL or empty,
2442  * restores the default application image.
2443  */
2444 void macdrv_set_application_icon(CFArrayRef images)
2446     NSArray* imageArray = (NSArray*)images;
2448     OnMainThreadAsync(^{
2449         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2450     });
2453 /***********************************************************************
2454  *              macdrv_quit_reply
2455  */
2456 void macdrv_quit_reply(int reply)
2458     OnMainThread(^{
2459         [NSApp replyToApplicationShouldTerminate:reply];
2460     });
2463 /***********************************************************************
2464  *              macdrv_using_input_method
2465  */
2466 int macdrv_using_input_method(void)
2468     __block BOOL ret;
2470     OnMainThread(^{
2471         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2472     });
2474     return ret;
2477 /***********************************************************************
2478  *              macdrv_set_mouse_capture_window
2479  */
2480 void macdrv_set_mouse_capture_window(macdrv_window window)
2482     WineWindow* w = (WineWindow*)window;
2484     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2486     OnMainThread(^{
2487         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2488     });
2491 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2492 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2493 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2495 /***********************************************************************
2496  *              macdrv_create_input_source_list
2497  */
2498 CFArrayRef macdrv_create_input_source_list(void)
2500     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2502     OnMainThread(^{
2503         CFArrayRef input_list;
2504         CFDictionaryRef filter_dict;
2505         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2506         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2507         int i;
2509         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2510                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2511         input_list = TISCreateInputSourceList(filter_dict, false);
2513         for (i = 0; i < CFArrayGetCount(input_list); i++)
2514         {
2515             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2516             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2517             CFDictionaryRef entry;
2518             const void *input_keys[3] = { macdrv_input_source_input_key,
2519                                           macdrv_input_source_type_key,
2520                                           macdrv_input_source_lang_key };
2521             const void *input_values[3];
2523             input_values[0] = input;
2524             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2525             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2527             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2528                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2530             CFArrayAppendValue(ret, entry);
2531             CFRelease(entry);
2532         }
2533         CFRelease(input_list);
2534         CFRelease(filter_dict);
2535     });
2537     return ret;
2540 int macdrv_select_input_source(TISInputSourceRef input_source)
2542     __block int ret = FALSE;
2544     OnMainThread(^{
2545         ret = (TISSelectInputSource(input_source) == noErr);
2546     });
2548     return ret;
2551 void macdrv_set_cocoa_retina_mode(int new_mode)
2553     OnMainThread(^{
2554         [[WineApplicationController sharedController] setRetinaMode:new_mode];
2555     });
2558 int macdrv_is_any_wine_window_visible(void)
2560     __block int ret = FALSE;
2562     OnMainThread(^{
2563         ret = [[WineApplicationController sharedController] isAnyWineWindowVisible];
2564     });
2566     return ret;