winemac: Keep floating windows in a higher window level than non-floating full-screen...
[wine.git] / dlls / winemac.drv / cocoa_app.m
blob09c95d069e1ea1b90d19965b73bc947a8a3608a0
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>
22 #include <dlfcn.h>
24 #import "cocoa_app.h"
25 #import "cocoa_event.h"
26 #import "cocoa_window.h"
29 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
32 int macdrv_err_on;
35 /***********************************************************************
36  *              WineLocalizedString
37  *
38  * Look up a localized string by its ID in the dictionary.
39  */
40 static NSString* WineLocalizedString(unsigned int stringID)
42     NSNumber* key = [NSNumber numberWithUnsignedInt:stringID];
43     return [(NSDictionary*)localized_strings objectForKey:key];
47 @implementation WineApplication
49 @synthesize wineController;
51     - (void) sendEvent:(NSEvent*)anEvent
52     {
53         if (![wineController handleEvent:anEvent])
54         {
55             [super sendEvent:anEvent];
56             [wineController didSendEvent:anEvent];
57         }
58     }
60     - (void) setWineController:(WineApplicationController*)newController
61     {
62         wineController = newController;
63         [self setDelegate:wineController];
64     }
66 @end
69 @interface WarpRecord : NSObject
71     CGEventTimestamp timeBefore, timeAfter;
72     CGPoint from, to;
75 @property (nonatomic) CGEventTimestamp timeBefore;
76 @property (nonatomic) CGEventTimestamp timeAfter;
77 @property (nonatomic) CGPoint from;
78 @property (nonatomic) CGPoint to;
80 @end
83 @implementation WarpRecord
85 @synthesize timeBefore, timeAfter, from, to;
87 @end;
90 @interface WineApplicationController ()
92 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
93 @property (copy, nonatomic) NSArray* cursorFrames;
94 @property (retain, nonatomic) NSTimer* cursorTimer;
95 @property (retain, nonatomic) NSCursor* cursor;
96 @property (retain, nonatomic) NSImage* applicationIcon;
97 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
98 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
100     - (void) setupObservations;
101     - (void) applicationDidBecomeActive:(NSNotification *)notification;
103     static void PerformRequest(void *info);
105 @end
108 @implementation WineApplicationController
110     @synthesize keyboardType, lastFlagsChanged;
111     @synthesize applicationIcon;
112     @synthesize cursorFrames, cursorTimer, cursor;
113     @synthesize mouseCaptureWindow;
115     @synthesize clippingCursor;
117     + (void) initialize
118     {
119         if (self == [WineApplicationController class])
120         {
121             NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
122                                       @"", @"NSQuotedKeystrokeBinding",
123                                       @"", @"NSRepeatCountBinding",
124                                       [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
125                                       nil];
126             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
127         }
128     }
130     + (WineApplicationController*) sharedController
131     {
132         static WineApplicationController* sharedController;
133         static dispatch_once_t once;
135         dispatch_once(&once, ^{
136             sharedController = [[self alloc] init];
137         });
139         return sharedController;
140     }
142     - (id) init
143     {
144         self = [super init];
145         if (self != nil)
146         {
147             CFRunLoopSourceContext context = { 0 };
148             context.perform = PerformRequest;
149             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
150             if (!requestSource)
151             {
152                 [self release];
153                 return nil;
154             }
155             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
156             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
158             requests =  [[NSMutableArray alloc] init];
159             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
161             eventQueues = [[NSMutableArray alloc] init];
162             eventQueuesLock = [[NSLock alloc] init];
164             keyWindows = [[NSMutableArray alloc] init];
166             originalDisplayModes = [[NSMutableDictionary alloc] init];
167             latentDisplayModes = [[NSMutableDictionary alloc] init];
169             warpRecords = [[NSMutableArray alloc] init];
171             windowsBeingDragged = [[NSMutableSet alloc] init];
173             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
174                 !keyWindows || !originalDisplayModes || !latentDisplayModes || !warpRecords)
175             {
176                 [self release];
177                 return nil;
178             }
180             [self setupObservations];
182             keyboardType = LMGetKbdType();
184             if ([NSApp isActive])
185                 [self applicationDidBecomeActive:nil];
186         }
187         return self;
188     }
190     - (void) dealloc
191     {
192         [windowsBeingDragged release];
193         [cursor release];
194         [screenFrameCGRects release];
195         [applicationIcon release];
196         [warpRecords release];
197         [cursorTimer release];
198         [cursorFrames release];
199         [latentDisplayModes release];
200         [originalDisplayModes release];
201         [keyWindows release];
202         [eventQueues release];
203         [eventQueuesLock release];
204         if (requestsManipQueue) dispatch_release(requestsManipQueue);
205         [requests release];
206         if (requestSource)
207         {
208             CFRunLoopSourceInvalidate(requestSource);
209             CFRelease(requestSource);
210         }
211         [super dealloc];
212     }
214     - (void) transformProcessToForeground
215     {
216         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
217         {
218             NSMenu* mainMenu;
219             NSMenu* submenu;
220             NSString* bundleName;
221             NSString* title;
222             NSMenuItem* item;
224             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
225             [NSApp activateIgnoringOtherApps:YES];
227             mainMenu = [[[NSMenu alloc] init] autorelease];
229             // Application menu
230             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
231             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
233             if ([bundleName length])
234                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
235             else
236                 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
237             item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
239             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
240                                       action:@selector(hideOtherApplications:)
241                                keyEquivalent:@"h"];
242             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
244             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
245                                       action:@selector(unhideAllApplications:)
246                                keyEquivalent:@""];
248             [submenu addItem:[NSMenuItem separatorItem]];
250             if ([bundleName length])
251                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
252             else
253                 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
254             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
255             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
256             item = [[[NSMenuItem alloc] init] autorelease];
257             [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
258             [item setSubmenu:submenu];
259             [mainMenu addItem:item];
261             // Window menu
262             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
263             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
264                                action:@selector(performMiniaturize:)
265                         keyEquivalent:@""];
266             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
267                                action:@selector(performZoom:)
268                         keyEquivalent:@""];
269             if ([NSWindow instancesRespondToSelector:@selector(toggleFullScreen:)])
270             {
271                 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
272                                           action:@selector(toggleFullScreen:)
273                                    keyEquivalent:@"f"];
274                 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask];
275             }
276             [submenu addItem:[NSMenuItem separatorItem]];
277             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
278                                action:@selector(arrangeInFront:)
279                         keyEquivalent:@""];
280             item = [[[NSMenuItem alloc] init] autorelease];
281             [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
282             [item setSubmenu:submenu];
283             [mainMenu addItem:item];
285             [NSApp setMainMenu:mainMenu];
286             [NSApp setWindowsMenu:submenu];
288             [NSApp setApplicationIconImage:self.applicationIcon];
289         }
290     }
292     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
293     {
294         PerformRequest(NULL);
296         do
297         {
298             if (processEvents)
299             {
300                 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
301                 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
302                                                     untilDate:timeout
303                                                        inMode:NSDefaultRunLoopMode
304                                                       dequeue:YES];
305                 if (event)
306                     [NSApp sendEvent:event];
307                 [pool release];
308             }
309             else
310                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
311         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
313         return *done;
314     }
316     - (BOOL) registerEventQueue:(WineEventQueue*)queue
317     {
318         [eventQueuesLock lock];
319         [eventQueues addObject:queue];
320         [eventQueuesLock unlock];
321         return TRUE;
322     }
324     - (void) unregisterEventQueue:(WineEventQueue*)queue
325     {
326         [eventQueuesLock lock];
327         [eventQueues removeObjectIdenticalTo:queue];
328         [eventQueuesLock unlock];
329     }
331     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
332     {
333         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
334     }
336     - (double) ticksForEventTime:(NSTimeInterval)eventTime
337     {
338         return (eventTime + eventTimeAdjustment) * 1000;
339     }
341     /* Invalidate old focus offers across all queues. */
342     - (void) invalidateGotFocusEvents
343     {
344         WineEventQueue* queue;
346         windowFocusSerial++;
348         [eventQueuesLock lock];
349         for (queue in eventQueues)
350         {
351             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
352                                    forWindow:nil];
353         }
354         [eventQueuesLock unlock];
355     }
357     - (void) windowGotFocus:(WineWindow*)window
358     {
359         macdrv_event* event;
361         [self invalidateGotFocusEvents];
363         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
364         event->window_got_focus.serial = windowFocusSerial;
365         if (triedWindows)
366             event->window_got_focus.tried_windows = [triedWindows retain];
367         else
368             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
369         [window.queue postEvent:event];
370         macdrv_release_event(event);
371     }
373     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
374     {
375         if (event->window_got_focus.serial == windowFocusSerial)
376         {
377             NSMutableArray* windows = [keyWindows mutableCopy];
378             NSNumber* windowNumber;
379             WineWindow* window;
381             for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
382             {
383                 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
384                 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
385                     ![windows containsObject:window])
386                     [windows addObject:window];
387             }
389             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
390             [triedWindows addObject:(WineWindow*)event->window];
391             for (window in windows)
392             {
393                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
394                 {
395                     [window makeKeyWindow];
396                     break;
397                 }
398             }
399             triedWindows = nil;
400             [windows release];
401         }
402     }
404     - (void) keyboardSelectionDidChange
405     {
406         TISInputSourceRef inputSourceLayout;
408         inputSourceIsInputMethodValid = FALSE;
410         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
411         if (inputSourceLayout)
412         {
413             CFDataRef uchr;
414             uchr = TISGetInputSourceProperty(inputSourceLayout,
415                     kTISPropertyUnicodeKeyLayoutData);
416             if (uchr)
417             {
418                 macdrv_event* event;
419                 WineEventQueue* queue;
421                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
422                 event->keyboard_changed.keyboard_type = self.keyboardType;
423                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
424                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
425                 event->keyboard_changed.input_source = TISCopyCurrentKeyboardInputSource();
427                 if (event->keyboard_changed.uchr)
428                 {
429                     [eventQueuesLock lock];
431                     for (queue in eventQueues)
432                         [queue postEvent:event];
434                     [eventQueuesLock unlock];
435                 }
437                 macdrv_release_event(event);
438             }
440             CFRelease(inputSourceLayout);
441         }
442     }
444     - (void) enabledKeyboardInputSourcesChanged
445     {
446         macdrv_layout_list_needs_update = TRUE;
447     }
449     - (CGFloat) primaryScreenHeight
450     {
451         if (!primaryScreenHeightValid)
452         {
453             NSArray* screens = [NSScreen screens];
454             NSUInteger count = [screens count];
455             if (count)
456             {
457                 NSUInteger size;
458                 CGRect* rect;
459                 NSScreen* screen;
461                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
462                 primaryScreenHeightValid = TRUE;
464                 size = count * sizeof(CGRect);
465                 if (!screenFrameCGRects)
466                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
467                 else
468                     [screenFrameCGRects setLength:size];
470                 rect = [screenFrameCGRects mutableBytes];
471                 for (screen in screens)
472                 {
473                     CGRect temp = NSRectToCGRect([screen frame]);
474                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
475                     *rect++ = temp;
476                 }
477             }
478             else
479                 return 1280; /* arbitrary value */
480         }
482         return primaryScreenHeight;
483     }
485     - (NSPoint) flippedMouseLocation:(NSPoint)point
486     {
487         /* This relies on the fact that Cocoa's mouse location points are
488            actually off by one (precisely because they were flipped from
489            Quartz screen coordinates using this same technique). */
490         point.y = [self primaryScreenHeight] - point.y;
491         return point;
492     }
494     - (void) flipRect:(NSRect*)rect
495     {
496         // We don't use -primaryScreenHeight here so there's no chance of having
497         // out-of-date cached info.  This method is called infrequently enough
498         // that getting the screen height each time is not prohibitively expensive.
499         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
500     }
502     - (WineWindow*) frontWineWindow
503     {
504         NSNumber* windowNumber;
505         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
506         {
507             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
508             if ([window isKindOfClass:[WineWindow class]] && [window screen])
509                 return (WineWindow*)window;
510         }
512         return nil;
513     }
515     - (void) adjustWindowLevels:(BOOL)active
516     {
517         NSArray* windowNumbers;
518         NSMutableArray* wineWindows;
519         NSNumber* windowNumber;
520         NSUInteger nextFloatingIndex = 0;
521         __block NSInteger maxLevel = NSIntegerMin;
522         __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
523         __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
524         __block WineWindow* prev = nil;
525         WineWindow* window;
527         if ([NSApp isHidden]) return;
529         windowNumbers = [NSWindow windowNumbersWithOptions:0];
530         wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
532         // For the most part, we rely on the window server's ordering of the windows
533         // to be authoritative.  The one exception is if the "floating" property of
534         // one of the windows has been changed, it may be in the wrong level and thus
535         // in the order.  This method is what's supposed to fix that up.  So build
536         // a list of Wine windows sorted first by floating-ness and then by order
537         // as indicated by the window server.
538         for (windowNumber in windowNumbers)
539         {
540             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
541             if ([window isKindOfClass:[WineWindow class]])
542             {
543                 if (window.floating)
544                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
545                 else
546                     [wineWindows addObject:window];
547             }
548         }
550         NSDisableScreenUpdates();
552         // Go from back to front so that all windows in front of one which is
553         // elevated for full-screen are also elevated.
554         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
555                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
556             WineWindow* window = (WineWindow*)obj;
557             NSInteger origLevel = [window level];
558             NSInteger newLevel = [window minimumLevelForActive:active];
560             if (window.floating)
561             {
562                 if (minFloatingLevel <= maxNonfloatingLevel)
563                     minFloatingLevel = maxNonfloatingLevel + 1;
564                 if (newLevel < minFloatingLevel)
565                     newLevel = minFloatingLevel;
566             }
568             if (newLevel < maxLevel)
569                 newLevel = maxLevel;
570             else
571                 maxLevel = newLevel;
573             if (!window.floating && maxNonfloatingLevel < newLevel)
574                 maxNonfloatingLevel = newLevel;
576             if (newLevel != origLevel)
577             {
578                 [window setLevel:newLevel];
580                 // -setLevel: puts the window at the front of its new level.  If
581                 // we decreased the level, that's good (it was in front of that
582                 // level before, so it should still be now).  But if we increased
583                 // the level, the window should be toward the back (but still
584                 // ahead of the previous windows we did this to).
585                 if (origLevel < newLevel)
586                 {
587                     if (prev)
588                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
589                     else
590                         [window orderBack:nil];
591                 }
592             }
594             prev = window;
595         }];
597         NSEnableScreenUpdates();
599         [wineWindows release];
601         // The above took care of the visible windows on the current space.  That
602         // leaves windows on other spaces, minimized windows, and windows which
603         // are not ordered in.  We want to leave windows on other spaces alone
604         // so the space remains just as they left it (when viewed in Exposé or
605         // Mission Control, for example).  We'll adjust the window levels again
606         // after we switch to another space, anyway.  Windows which aren't
607         // ordered in will be handled when we order them in.  Minimized windows
608         // on the current space should be set to the level they would have gotten
609         // if they were at the front of the windows with the same floating-ness,
610         // because that's where they'll go if/when they are unminimized.  Again,
611         // for good measure we'll adjust window levels again when a window is
612         // unminimized, too.
613         for (window in [NSApp windows])
614         {
615             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
616                 [window isOnActiveSpace])
617             {
618                 NSInteger origLevel = [window level];
619                 NSInteger newLevel = [window minimumLevelForActive:YES];
620                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
622                 if (newLevel < maxLevelForType)
623                     newLevel = maxLevelForType;
625                 if (newLevel != origLevel)
626                     [window setLevel:newLevel];
627             }
628         }
629     }
631     - (void) adjustWindowLevels
632     {
633         [self adjustWindowLevels:[NSApp isActive]];
634     }
636     - (void) updateFullscreenWindows
637     {
638         if (capture_displays_for_fullscreen && [NSApp isActive])
639         {
640             BOOL anyFullscreen = FALSE;
641             NSNumber* windowNumber;
642             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
643             {
644                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
645                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
646                 {
647                     anyFullscreen = TRUE;
648                     break;
649                 }
650             }
652             if (anyFullscreen)
653             {
654                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
655                     displaysCapturedForFullscreen = TRUE;
656             }
657             else if (displaysCapturedForFullscreen)
658             {
659                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
660                     displaysCapturedForFullscreen = FALSE;
661             }
662         }
663     }
665     - (void) activeSpaceDidChange
666     {
667         [self updateFullscreenWindows];
668         [self adjustWindowLevels];
669     }
671     - (void) sendDisplaysChanged:(BOOL)activating
672     {
673         macdrv_event* event;
674         WineEventQueue* queue;
676         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
677         event->displays_changed.activating = activating;
679         [eventQueuesLock lock];
681         // If we're activating, then we just need one of our threads to get the
682         // event, so it can send it directly to the desktop window.  Otherwise,
683         // we need all of the threads to get it because we don't know which owns
684         // the desktop window and only that one will do anything with it.
685         if (activating) event->deliver = 1;
687         for (queue in eventQueues)
688             [queue postEvent:event];
689         [eventQueuesLock unlock];
691         macdrv_release_event(event);
692     }
694     // We can compare two modes directly using CFEqual, but that may require that
695     // they are identical to a level that we don't need.  In particular, when the
696     // OS switches between the integrated and discrete GPUs, the set of display
697     // modes can change in subtle ways.  We're interested in whether two modes
698     // match in their most salient features, even if they aren't identical.
699     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
700     {
701         NSString *encoding1, *encoding2;
702         uint32_t ioflags1, ioflags2, different;
703         double refresh1, refresh2;
705         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
706         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
708 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
709         if (CGDisplayModeGetPixelWidth != NULL &&
710             CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
711         if (CGDisplayModeGetPixelHeight != NULL &&
712             CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
713 #endif
715         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
716         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
717         if (![encoding1 isEqualToString:encoding2]) return FALSE;
719         ioflags1 = CGDisplayModeGetIOFlags(mode1);
720         ioflags2 = CGDisplayModeGetIOFlags(mode2);
721         different = ioflags1 ^ ioflags2;
722         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
723                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
724             return FALSE;
726         refresh1 = CGDisplayModeGetRefreshRate(mode1);
727         if (refresh1 == 0) refresh1 = 60;
728         refresh2 = CGDisplayModeGetRefreshRate(mode2);
729         if (refresh2 == 0) refresh2 = 60;
730         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
732         return TRUE;
733     }
735     - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
736     {
737         NSMutableArray* ret = [NSMutableArray array];
738         NSDictionary* options = nil;
740 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
741         if (&kCGDisplayShowDuplicateLowResolutionModes != NULL)
742             options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:TRUE]
743                                                   forKey:(NSString*)kCGDisplayShowDuplicateLowResolutionModes];
744 #endif
746         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
747         for (id candidateModeObject in modes)
748         {
749             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
750             if ([self mode:candidateMode matchesMode:mode])
751                 [ret addObject:candidateModeObject];
752         }
753         return ret;
754     }
756     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
757     {
758         BOOL ret = FALSE;
759         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
760         CGDisplayModeRef originalMode;
762         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
764         if (originalMode && [self mode:mode matchesMode:originalMode])
765         {
766             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
767             {
768                 CGRestorePermanentDisplayConfiguration();
769                 if (!displaysCapturedForFullscreen)
770                     CGReleaseAllDisplays();
771                 [originalDisplayModes removeAllObjects];
772                 ret = TRUE;
773             }
774             else // ... otherwise, try to restore just the one display
775             {
776                 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
777                 {
778                     mode = (CGDisplayModeRef)modeObject;
779                     if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
780                     {
781                         [originalDisplayModes removeObjectForKey:displayIDKey];
782                         ret = TRUE;
783                         break;
784                     }
785                 }
786             }
787         }
788         else
789         {
790             BOOL active = [NSApp isActive];
791             CGDisplayModeRef currentMode;
792             NSArray* modes;
794             currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
795             if (!currentMode)
796                 currentMode = CGDisplayCopyDisplayMode(displayID);
797             if (!currentMode) // Invalid display ID
798                 return FALSE;
800             if ([self mode:mode matchesMode:currentMode]) // Already there!
801             {
802                 CGDisplayModeRelease(currentMode);
803                 return TRUE;
804             }
806             CGDisplayModeRelease(currentMode);
807             currentMode = NULL;
809             modes = [self modesMatchingMode:mode forDisplay:displayID];
810             if (!modes.count)
811                 return FALSE;
813             if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
814                 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
815             {
816                 if (active)
817                 {
818                     // If we get here, we have the displays captured.  If we don't
819                     // know the original mode of the display, the current mode must
820                     // be the original.  We should re-query the current mode since
821                     // another process could have changed it between when we last
822                     // checked and when we captured the displays.
823                     if (!originalMode)
824                         originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
826                     if (originalMode)
827                     {
828                         for (id modeObject in modes)
829                         {
830                             mode = (CGDisplayModeRef)modeObject;
831                             if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
832                             {
833                                 ret = TRUE;
834                                 break;
835                             }
836                         }
837                     }
838                     if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
839                         [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
840                     else if (![originalDisplayModes count])
841                     {
842                         CGRestorePermanentDisplayConfiguration();
843                         if (!displaysCapturedForFullscreen)
844                             CGReleaseAllDisplays();
845                     }
847                     if (currentMode)
848                         CGDisplayModeRelease(currentMode);
849                 }
850                 else
851                 {
852                     [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
853                     ret = TRUE;
854                 }
855             }
856         }
858         if (ret)
859             [self adjustWindowLevels];
861         return ret;
862     }
864     - (BOOL) areDisplaysCaptured
865     {
866         return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
867     }
869     - (void) updateCursor:(BOOL)force
870     {
871         if (force || lastTargetWindow)
872         {
873             if (clientWantsCursorHidden && !cursorHidden)
874             {
875                 [NSCursor hide];
876                 cursorHidden = TRUE;
877             }
879             if (!cursorIsCurrent)
880             {
881                 [cursor set];
882                 cursorIsCurrent = TRUE;
883             }
885             if (!clientWantsCursorHidden && cursorHidden)
886             {
887                 [NSCursor unhide];
888                 cursorHidden = FALSE;
889             }
890         }
891         else
892         {
893             if (cursorIsCurrent)
894             {
895                 [[NSCursor arrowCursor] set];
896                 cursorIsCurrent = FALSE;
897             }
898             if (cursorHidden)
899             {
900                 [NSCursor unhide];
901                 cursorHidden = FALSE;
902             }
903         }
904     }
906     - (void) hideCursor
907     {
908         if (!clientWantsCursorHidden)
909         {
910             clientWantsCursorHidden = TRUE;
911             [self updateCursor:TRUE];
912         }
913     }
915     - (void) unhideCursor
916     {
917         if (clientWantsCursorHidden)
918         {
919             clientWantsCursorHidden = FALSE;
920             [self updateCursor:FALSE];
921         }
922     }
924     - (void) setCursor:(NSCursor*)newCursor
925     {
926         if (newCursor != cursor)
927         {
928             [cursor release];
929             cursor = [newCursor retain];
930             cursorIsCurrent = FALSE;
931             [self updateCursor:FALSE];
932         }
933     }
935     - (void) setCursor
936     {
937         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
938         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
939         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
940         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
941         CGPoint hotSpot;
943         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
944             hotSpot = CGPointZero;
945         self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
946         [image release];
947         [self unhideCursor];
948     }
950     - (void) nextCursorFrame:(NSTimer*)theTimer
951     {
952         NSDictionary* frame;
953         NSTimeInterval duration;
954         NSDate* date;
956         cursorFrame++;
957         if (cursorFrame >= [cursorFrames count])
958             cursorFrame = 0;
959         [self setCursor];
961         frame = [cursorFrames objectAtIndex:cursorFrame];
962         duration = [[frame objectForKey:@"duration"] doubleValue];
963         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
964         [cursorTimer setFireDate:date];
965     }
967     - (void) setCursorWithFrames:(NSArray*)frames
968     {
969         if (self.cursorFrames == frames)
970             return;
972         self.cursorFrames = frames;
973         cursorFrame = 0;
974         [cursorTimer invalidate];
975         self.cursorTimer = nil;
977         if ([frames count])
978         {
979             if ([frames count] > 1)
980             {
981                 NSDictionary* frame = [frames objectAtIndex:0];
982                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
983                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
984                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
985                                                              interval:1000000
986                                                                target:self
987                                                              selector:@selector(nextCursorFrame:)
988                                                              userInfo:nil
989                                                               repeats:YES] autorelease];
990                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
991             }
993             [self setCursor];
994         }
995     }
997     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
998     {
999         NSImage* nsimage = nil;
1001         if ([images count])
1002         {
1003             NSSize bestSize = NSZeroSize;
1004             id image;
1006             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1008             for (image in images)
1009             {
1010                 CGImageRef cgimage = (CGImageRef)image;
1011                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1012                 if (imageRep)
1013                 {
1014                     NSSize size = [imageRep size];
1016                     [nsimage addRepresentation:imageRep];
1017                     [imageRep release];
1019                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1020                         bestSize = size;
1021                 }
1022             }
1024             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1025                 [nsimage setSize:bestSize];
1026             else
1027                 nsimage = nil;
1028         }
1030         self.applicationIcon = nsimage;
1031     }
1033     - (void) handleCommandTab
1034     {
1035         if ([NSApp isActive])
1036         {
1037             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1038             NSRunningApplication* app;
1039             NSRunningApplication* otherValidApp = nil;
1041             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1042             {
1043                 NSNumber* displayID;
1044                 for (displayID in originalDisplayModes)
1045                 {
1046                     CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1047                     [latentDisplayModes setObject:(id)mode forKey:displayID];
1048                     CGDisplayModeRelease(mode);
1049                 }
1051                 CGRestorePermanentDisplayConfiguration();
1052                 CGReleaseAllDisplays();
1053                 [originalDisplayModes removeAllObjects];
1054                 displaysCapturedForFullscreen = FALSE;
1055             }
1057             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1058             {
1059                 if (![app isEqual:thisApp] && !app.terminated &&
1060                     app.activationPolicy == NSApplicationActivationPolicyRegular)
1061                 {
1062                     if (!app.hidden)
1063                     {
1064                         // There's another visible app.  Just hide ourselves and let
1065                         // the system activate the other app.
1066                         [NSApp hide:self];
1067                         return;
1068                     }
1070                     if (!otherValidApp)
1071                         otherValidApp = app;
1072                 }
1073             }
1075             // Didn't find a visible GUI app.  Try the Finder or, if that's not
1076             // running, the first hidden GUI app.  If even that doesn't work, we
1077             // just fail to switch and remain the active app.
1078             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1079             if (!app) app = otherValidApp;
1080             [app unhide];
1081             [app activateWithOptions:0];
1082         }
1083     }
1085     /*
1086      * ---------- Cursor clipping methods ----------
1087      *
1088      * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1089      * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1090      * equivalent: CGAssociateMouseAndMouseCursorPosition(false).  For the
1091      * general case, we leverage that.  We disassociate mouse movements from
1092      * the cursor position and then move the cursor manually, keeping it within
1093      * the clipping rectangle.
1094      *
1095      * Moving the cursor manually isn't enough.  We need to modify the event
1096      * stream so that the events have the new location, too.  We need to do
1097      * this at a point before the events enter Cocoa, so that Cocoa will assign
1098      * the correct window to the event.  So, we install a Quartz event tap to
1099      * do that.
1100      *
1101      * Also, there's a complication when we move the cursor.  We use
1102      * CGWarpMouseCursorPosition().  That doesn't generate mouse movement
1103      * events, but the change of cursor position is incorporated into the
1104      * deltas of the next mouse move event.  When the mouse is disassociated
1105      * from the cursor position, we need the deltas to only reflect actual
1106      * device movement, not programmatic changes.  So, the event tap cancels
1107      * out the change caused by our calls to CGWarpMouseCursorPosition().
1108      */
1109     - (void) clipCursorLocation:(CGPoint*)location
1110     {
1111         if (location->x < CGRectGetMinX(cursorClipRect))
1112             location->x = CGRectGetMinX(cursorClipRect);
1113         if (location->y < CGRectGetMinY(cursorClipRect))
1114             location->y = CGRectGetMinY(cursorClipRect);
1115         if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1116             location->x = CGRectGetMaxX(cursorClipRect) - 1;
1117         if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1118             location->y = CGRectGetMaxY(cursorClipRect) - 1;
1119     }
1121     - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1122     {
1123         CGPoint oldLocation;
1125         if (currentLocation)
1126             oldLocation = *currentLocation;
1127         else
1128             oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1130         if (!CGPointEqualToPoint(oldLocation, *newLocation))
1131         {
1132             WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1133             CGError err;
1135             warpRecord.from = oldLocation;
1136             warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1138             /* Actually move the cursor. */
1139             err = CGWarpMouseCursorPosition(*newLocation);
1140             if (err != kCGErrorSuccess)
1141                 return FALSE;
1143             warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1144             *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1146             if (!CGPointEqualToPoint(oldLocation, *newLocation))
1147             {
1148                 warpRecord.to = *newLocation;
1149                 [warpRecords addObject:warpRecord];
1150             }
1151         }
1153         return TRUE;
1154     }
1156     - (BOOL) isMouseMoveEventType:(CGEventType)type
1157     {
1158         switch(type)
1159         {
1160         case kCGEventMouseMoved:
1161         case kCGEventLeftMouseDragged:
1162         case kCGEventRightMouseDragged:
1163         case kCGEventOtherMouseDragged:
1164             return TRUE;
1165         }
1167         return FALSE;
1168     }
1170     - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1171     {
1172         int warpsFinished = 0;
1173         for (WarpRecord* warpRecord in warpRecords)
1174         {
1175             if (warpRecord.timeAfter < eventTime ||
1176                 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1177                 warpsFinished++;
1178             else
1179                 break;
1180         }
1182         return warpsFinished;
1183     }
1185     - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1186                                 type:(CGEventType)type
1187                                event:(CGEventRef)event
1188     {
1189         CGEventTimestamp eventTime;
1190         CGPoint eventLocation, cursorLocation;
1192         if (type == kCGEventTapDisabledByUserInput)
1193             return event;
1194         if (type == kCGEventTapDisabledByTimeout)
1195         {
1196             CGEventTapEnable(cursorClippingEventTap, TRUE);
1197             return event;
1198         }
1200         if (!clippingCursor)
1201             return event;
1203         eventTime = CGEventGetTimestamp(event);
1204         lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1206         eventLocation = CGEventGetLocation(event);
1208         cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1210         if ([self isMouseMoveEventType:type])
1211         {
1212             double deltaX, deltaY;
1213             int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1214             int i;
1216             deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1217             deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1219             for (i = 0; i < warpsFinished; i++)
1220             {
1221                 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1222                 deltaX -= warpRecord.to.x - warpRecord.from.x;
1223                 deltaY -= warpRecord.to.y - warpRecord.from.y;
1224                 [warpRecords removeObjectAtIndex:0];
1225             }
1227             if (warpsFinished)
1228             {
1229                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1230                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1231             }
1233             synthesizedLocation.x += deltaX;
1234             synthesizedLocation.y += deltaY;
1235         }
1237         // If the event is destined for another process, don't clip it.  This may
1238         // happen if the user activates Exposé or Mission Control.  In that case,
1239         // our app does not resign active status, so clipping is still in effect,
1240         // but the cursor should not actually be clipped.
1241         //
1242         // In addition, the fact that mouse moves may have been delivered to a
1243         // different process means we have to treat the next one we receive as
1244         // absolute rather than relative.
1245         if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1246             [self clipCursorLocation:&synthesizedLocation];
1247         else
1248             lastSetCursorPositionTime = lastEventTapEventTime;
1250         [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1251         if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1252             CGEventSetLocation(event, synthesizedLocation);
1254         return event;
1255     }
1257     CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1258                                        CGEventRef event, void *refcon)
1259     {
1260         WineApplicationController* controller = refcon;
1261         return [controller eventTapWithProxy:proxy type:type event:event];
1262     }
1264     - (BOOL) installEventTap
1265     {
1266         ProcessSerialNumber psn;
1267         OSErr err;
1268         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
1269                            CGEventMaskBit(kCGEventLeftMouseUp)          |
1270                            CGEventMaskBit(kCGEventRightMouseDown)       |
1271                            CGEventMaskBit(kCGEventRightMouseUp)         |
1272                            CGEventMaskBit(kCGEventMouseMoved)           |
1273                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
1274                            CGEventMaskBit(kCGEventRightMouseDragged)    |
1275                            CGEventMaskBit(kCGEventOtherMouseDown)       |
1276                            CGEventMaskBit(kCGEventOtherMouseUp)         |
1277                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
1278                            CGEventMaskBit(kCGEventScrollWheel);
1279         CFRunLoopSourceRef source;
1280         void* appServices;
1281         OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1283         if (cursorClippingEventTap)
1284             return TRUE;
1286         // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1287         // framework with dlsym() because the Win32 function of the same name
1288         // obscures it.
1289         appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1290         if (!appServices)
1291             return FALSE;
1293         pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1294         if (!pGetCurrentProcess)
1295         {
1296             dlclose(appServices);
1297             return FALSE;
1298         }
1300         err = pGetCurrentProcess(&psn);
1301         dlclose(appServices);
1302         if (err != noErr)
1303             return FALSE;
1305         // We create an annotated session event tap rather than a process-specific
1306         // event tap because we need to programmatically move the cursor even when
1307         // mouse moves are directed to other processes.  We disable our tap when
1308         // other processes are active, but things like Exposé are handled by other
1309         // processes even when we remain active.
1310         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1311             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1312         if (!cursorClippingEventTap)
1313             return FALSE;
1315         CGEventTapEnable(cursorClippingEventTap, FALSE);
1317         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1318         if (!source)
1319         {
1320             CFRelease(cursorClippingEventTap);
1321             cursorClippingEventTap = NULL;
1322             return FALSE;
1323         }
1325         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1326         CFRelease(source);
1327         return TRUE;
1328     }
1330     - (BOOL) setCursorPosition:(CGPoint)pos
1331     {
1332         BOOL ret;
1334         if ([windowsBeingDragged count])
1335             ret = FALSE;
1336         else if (clippingCursor)
1337         {
1338             [self clipCursorLocation:&pos];
1340             ret = [self warpCursorTo:&pos from:NULL];
1341             synthesizedLocation = pos;
1342             if (ret)
1343             {
1344                 // We want to discard mouse-move events that have already been
1345                 // through the event tap, because it's too late to account for
1346                 // the setting of the cursor position with them.  However, the
1347                 // events that may be queued with times after that but before
1348                 // the above warp can still be used.  So, use the last event
1349                 // tap event time so that -sendEvent: doesn't discard them.
1350                 lastSetCursorPositionTime = lastEventTapEventTime;
1351             }
1352         }
1353         else
1354         {
1355             // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1356             // the mouse from the cursor position for 0.25 seconds.  This means
1357             // that mouse movement during that interval doesn't move the cursor
1358             // and events carry a constant location (the warped-to position)
1359             // even though they have delta values.  For apps which warp the
1360             // cursor frequently (like after every mouse move), this makes
1361             // cursor movement horribly laggy and jerky, as only a fraction of
1362             // mouse move events have any effect.
1363             //
1364             // On some versions of OS X, it's sufficient to forcibly reassociate
1365             // the mouse and cursor position.  On others, it's necessary to set
1366             // the local events suppression interval to 0 for the warp.  That's
1367             // deprecated, but I'm not aware of any other way.  For good
1368             // measure, we do both.
1369             CGSetLocalEventsSuppressionInterval(0);
1370             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1371             CGSetLocalEventsSuppressionInterval(0.25);
1372             if (ret)
1373             {
1374                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1376                 CGAssociateMouseAndMouseCursorPosition(true);
1377             }
1378         }
1380         if (ret)
1381         {
1382             WineEventQueue* queue;
1384             // Discard all pending mouse move events.
1385             [eventQueuesLock lock];
1386             for (queue in eventQueues)
1387             {
1388                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1389                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1390                                        forWindow:nil];
1391                 [queue resetMouseEventPositions:pos];
1392             }
1393             [eventQueuesLock unlock];
1394         }
1396         return ret;
1397     }
1399     - (void) activateCursorClipping
1400     {
1401         if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1402         {
1403             CGEventTapEnable(cursorClippingEventTap, TRUE);
1404             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1405         }
1406     }
1408     - (void) deactivateCursorClipping
1409     {
1410         if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1411         {
1412             CGEventTapEnable(cursorClippingEventTap, FALSE);
1413             [warpRecords removeAllObjects];
1414             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1415         }
1416     }
1418     - (void) updateCursorClippingState
1419     {
1420         if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1421             [self activateCursorClipping];
1422         else
1423             [self deactivateCursorClipping];
1424     }
1426     - (void) updateWindowsForCursorClipping
1427     {
1428         WineWindow* window;
1429         for (window in [NSApp windows])
1430         {
1431             if ([window isKindOfClass:[WineWindow class]])
1432                 [window updateForCursorClipping];
1433         }
1434     }
1436     - (BOOL) startClippingCursor:(CGRect)rect
1437     {
1438         CGError err;
1440         if (!cursorClippingEventTap && ![self installEventTap])
1441             return FALSE;
1443         if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1444             CGEventTapIsEnabled(cursorClippingEventTap))
1445             return TRUE;
1447         err = CGAssociateMouseAndMouseCursorPosition(false);
1448         if (err != kCGErrorSuccess)
1449             return FALSE;
1451         clippingCursor = TRUE;
1452         cursorClipRect = rect;
1453         [self updateCursorClippingState];
1454         [self updateWindowsForCursorClipping];
1456         return TRUE;
1457     }
1459     - (BOOL) stopClippingCursor
1460     {
1461         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1462         if (err != kCGErrorSuccess)
1463             return FALSE;
1465         clippingCursor = FALSE;
1466         [self updateCursorClippingState];
1467         [self updateWindowsForCursorClipping];
1469         return TRUE;
1470     }
1472     - (BOOL) isKeyPressed:(uint16_t)keyCode
1473     {
1474         int bits = sizeof(pressedKeyCodes[0]) * 8;
1475         int index = keyCode / bits;
1476         uint32_t mask = 1 << (keyCode % bits);
1477         return (pressedKeyCodes[index] & mask) != 0;
1478     }
1480     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1481     {
1482         int bits = sizeof(pressedKeyCodes[0]) * 8;
1483         int index = keyCode / bits;
1484         uint32_t mask = 1 << (keyCode % bits);
1485         if (pressed)
1486             pressedKeyCodes[index] |= mask;
1487         else
1488             pressedKeyCodes[index] &= ~mask;
1489     }
1491     - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1492     {
1493         if (dragged)
1494             [windowsBeingDragged addObject:window];
1495         else
1496             [windowsBeingDragged removeObject:window];
1497         [self updateCursorClippingState];
1498     }
1500     - (void) handleMouseMove:(NSEvent*)anEvent
1501     {
1502         WineWindow* targetWindow;
1503         BOOL drag = [anEvent type] != NSMouseMoved;
1505         if ([windowsBeingDragged count])
1506             targetWindow = nil;
1507         else if (mouseCaptureWindow)
1508             targetWindow = mouseCaptureWindow;
1509         else if (drag)
1510             targetWindow = (WineWindow*)[anEvent window];
1511         else
1512         {
1513             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1514                event indicates its window is the main window, even if the cursor is
1515                over a different window.  Find the actual WineWindow that is under the
1516                cursor and post the event as being for that window. */
1517             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1518             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1519             NSInteger windowUnderNumber;
1521             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1522                                   belowWindowWithWindowNumber:0];
1523             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1524             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1525                 targetWindow = nil;
1526         }
1528         if ([targetWindow isKindOfClass:[WineWindow class]])
1529         {
1530             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1531             macdrv_event* event;
1532             BOOL absolute;
1534             // If we recently warped the cursor (other than in our cursor-clipping
1535             // event tap), discard mouse move events until we see an event which is
1536             // later than that time.
1537             if (lastSetCursorPositionTime)
1538             {
1539                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1540                     return;
1542                 lastSetCursorPositionTime = 0;
1543                 forceNextMouseMoveAbsolute = TRUE;
1544             }
1546             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1547             {
1548                 absolute = TRUE;
1549                 forceNextMouseMoveAbsolute = FALSE;
1550             }
1551             else
1552             {
1553                 // Send absolute move events if the cursor is in the interior of
1554                 // its range.  Only send relative moves if the cursor is pinned to
1555                 // the boundaries of where it can go.  We compute the position
1556                 // that's one additional point in the direction of movement.  If
1557                 // that is outside of the clipping rect or desktop region (the
1558                 // union of the screen frames), then we figure the cursor would
1559                 // have moved outside if it could but it was pinned.
1560                 CGPoint computedPoint = point;
1561                 CGFloat deltaX = [anEvent deltaX];
1562                 CGFloat deltaY = [anEvent deltaY];
1564                 if (deltaX > 0.001)
1565                     computedPoint.x++;
1566                 else if (deltaX < -0.001)
1567                     computedPoint.x--;
1569                 if (deltaY > 0.001)
1570                     computedPoint.y++;
1571                 else if (deltaY < -0.001)
1572                     computedPoint.y--;
1574                 // Assume cursor is pinned for now
1575                 absolute = FALSE;
1576                 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1577                 {
1578                     const CGRect* rects;
1579                     NSUInteger count, i;
1581                     // Caches screenFrameCGRects if necessary
1582                     [self primaryScreenHeight];
1584                     rects = [screenFrameCGRects bytes];
1585                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1587                     for (i = 0; i < count; i++)
1588                     {
1589                         if (CGRectContainsPoint(rects[i], computedPoint))
1590                         {
1591                             absolute = TRUE;
1592                             break;
1593                         }
1594                     }
1595                 }
1596             }
1598             if (absolute)
1599             {
1600                 if (clippingCursor)
1601                     [self clipCursorLocation:&point];
1602                 point = cgpoint_win_from_mac(point);
1604                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1605                 event->mouse_moved.x = floor(point.x);
1606                 event->mouse_moved.y = floor(point.y);
1608                 mouseMoveDeltaX = 0;
1609                 mouseMoveDeltaY = 0;
1610             }
1611             else
1612             {
1613                 double scale = retina_on ? 2 : 1;
1615                 /* Add event delta to accumulated delta error */
1616                 /* deltaY is already flipped */
1617                 mouseMoveDeltaX += [anEvent deltaX];
1618                 mouseMoveDeltaY += [anEvent deltaY];
1620                 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1621                 event->mouse_moved.x = mouseMoveDeltaX * scale;
1622                 event->mouse_moved.y = mouseMoveDeltaY * scale;
1624                 /* Keep the remainder after integer truncation. */
1625                 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1626                 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1627             }
1629             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1630             {
1631                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1632                 event->mouse_moved.drag = drag;
1634                 [targetWindow.queue postEvent:event];
1635             }
1637             macdrv_release_event(event);
1639             lastTargetWindow = targetWindow;
1640         }
1641         else
1642             lastTargetWindow = nil;
1644         [self updateCursor:FALSE];
1645     }
1647     - (void) handleMouseButton:(NSEvent*)theEvent
1648     {
1649         WineWindow* window = (WineWindow*)[theEvent window];
1650         NSEventType type = [theEvent type];
1651         WineWindow* windowBroughtForward = nil;
1652         BOOL process = FALSE;
1654         if ([window isKindOfClass:[WineWindow class]] &&
1655             type == NSLeftMouseDown &&
1656             (([theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask| NSAlternateKeyMask | NSCommandKeyMask)) != NSCommandKeyMask))
1657         {
1658             NSWindowButton windowButton;
1660             windowBroughtForward = window;
1662             /* Any left-click on our window anyplace other than the close or
1663                minimize buttons will bring it forward. */
1664             for (windowButton = NSWindowCloseButton;
1665                  windowButton <= NSWindowMiniaturizeButton;
1666                  windowButton++)
1667             {
1668                 NSButton* button = [window standardWindowButton:windowButton];
1669                 if (button)
1670                 {
1671                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1672                     if ([button mouse:point inRect:[button bounds]])
1673                     {
1674                         windowBroughtForward = nil;
1675                         break;
1676                     }
1677                 }
1678             }
1679         }
1681         if ([windowsBeingDragged count])
1682             window = nil;
1683         else if (mouseCaptureWindow)
1684             window = mouseCaptureWindow;
1686         if ([window isKindOfClass:[WineWindow class]])
1687         {
1688             BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1689             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1691             if (clippingCursor)
1692                 [self clipCursorLocation:&pt];
1694             if (pressed)
1695             {
1696                 if (mouseCaptureWindow)
1697                     process = TRUE;
1698                 else
1699                 {
1700                     // Test if the click was in the window's content area.
1701                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1702                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1703                     process = NSMouseInRect(nspoint, contentRect, NO);
1704                     if (process && [window styleMask] & NSResizableWindowMask)
1705                     {
1706                         // Ignore clicks in the grow box (resize widget).
1707                         HIPoint origin = { 0, 0 };
1708                         HIThemeGrowBoxDrawInfo info = { 0 };
1709                         HIRect bounds;
1710                         OSStatus status;
1712                         info.kind = kHIThemeGrowBoxKindNormal;
1713                         info.direction = kThemeGrowRight | kThemeGrowDown;
1714                         if ([window styleMask] & NSUtilityWindowMask)
1715                             info.size = kHIThemeGrowBoxSizeSmall;
1716                         else
1717                             info.size = kHIThemeGrowBoxSizeNormal;
1719                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1720                         if (status == noErr)
1721                         {
1722                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1723                                                         NSMinY(contentRect),
1724                                                         bounds.size.width,
1725                                                         bounds.size.height);
1726                             process = !NSMouseInRect(nspoint, growBox, NO);
1727                         }
1728                     }
1729                 }
1730                 if (process)
1731                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1732             }
1733             else
1734             {
1735                 NSEventType downType = type - 1;
1736                 NSUInteger downMask = NSEventMaskFromType(downType);
1737                 process = (unmatchedMouseDowns & downMask) != 0;
1738                 unmatchedMouseDowns &= ~downMask;
1739             }
1741             if (process)
1742             {
1743                 macdrv_event* event;
1745                 pt = cgpoint_win_from_mac(pt);
1747                 event = macdrv_create_event(MOUSE_BUTTON, window);
1748                 event->mouse_button.button = [theEvent buttonNumber];
1749                 event->mouse_button.pressed = pressed;
1750                 event->mouse_button.x = floor(pt.x);
1751                 event->mouse_button.y = floor(pt.y);
1752                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1754                 [window.queue postEvent:event];
1756                 macdrv_release_event(event);
1757             }
1758         }
1760         if (windowBroughtForward)
1761         {
1762             WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1763             NSInteger ancestorNumber = [ancestor windowNumber];
1764             NSInteger ancestorLevel = [ancestor level];
1766             for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1767             {
1768                 NSInteger windowNumber = [windowNumberObject integerValue];
1769                 if (windowNumber == ancestorNumber)
1770                     break;
1771                 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1772                 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1773                     [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1774                 {
1775                     [ancestor postBroughtForwardEvent];
1776                     break;
1777                 }
1778             }
1779             if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate)
1780                 [self windowGotFocus:windowBroughtForward];
1781         }
1783         // Since mouse button events deliver absolute cursor position, the
1784         // accumulating delta from move events is invalidated.  Make sure
1785         // next mouse move event starts over from an absolute baseline.
1786         // Also, it's at least possible that the title bar widgets (e.g. close
1787         // button, etc.) could enter an internal event loop on a mouse down that
1788         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1789         // dragged events and, after that, any notion of the cursor position
1790         // computed from accumulating deltas would be wrong.
1791         forceNextMouseMoveAbsolute = TRUE;
1792     }
1794     - (void) handleScrollWheel:(NSEvent*)theEvent
1795     {
1796         WineWindow* window;
1798         if (mouseCaptureWindow)
1799             window = mouseCaptureWindow;
1800         else
1801             window = (WineWindow*)[theEvent window];
1803         if ([window isKindOfClass:[WineWindow class]])
1804         {
1805             CGEventRef cgevent = [theEvent CGEvent];
1806             CGPoint pt = CGEventGetLocation(cgevent);
1807             BOOL process;
1809             if (clippingCursor)
1810                 [self clipCursorLocation:&pt];
1812             if (mouseCaptureWindow)
1813                 process = TRUE;
1814             else
1815             {
1816                 // Only process the event if it was in the window's content area.
1817                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1818                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1819                 process = NSMouseInRect(nspoint, contentRect, NO);
1820             }
1822             if (process)
1823             {
1824                 macdrv_event* event;
1825                 double x, y;
1826                 BOOL continuous = FALSE;
1828                 pt = cgpoint_win_from_mac(pt);
1830                 event = macdrv_create_event(MOUSE_SCROLL, window);
1831                 event->mouse_scroll.x = floor(pt.x);
1832                 event->mouse_scroll.y = floor(pt.y);
1833                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1835                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1836                 {
1837                     continuous = TRUE;
1839                     /* Continuous scroll wheel events come from high-precision scrolling
1840                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1841                        For these, we can get more precise data from the CGEvent API. */
1842                     /* Axis 1 is vertical, axis 2 is horizontal. */
1843                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1844                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1845                 }
1846                 else
1847                 {
1848                     double pixelsPerLine = 10;
1849                     CGEventSourceRef source;
1851                     /* The non-continuous values are in units of "lines", not pixels. */
1852                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1853                     {
1854                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1855                         CFRelease(source);
1856                     }
1858                     x = pixelsPerLine * [theEvent deltaX];
1859                     y = pixelsPerLine * [theEvent deltaY];
1860                 }
1862                 /* Mac: negative is right or down, positive is left or up.
1863                    Win32: negative is left or down, positive is right or up.
1864                    So, negate the X scroll value to translate. */
1865                 x = -x;
1867                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1868                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1869                    6 times the pixel value. */
1870                 x *= 6;
1871                 y *= 6;
1873                 if (use_precise_scrolling)
1874                 {
1875                     event->mouse_scroll.x_scroll = x;
1876                     event->mouse_scroll.y_scroll = y;
1878                     if (!continuous)
1879                     {
1880                         /* For non-continuous "clicky" wheels, if there was any motion, make
1881                            sure there was at least WHEEL_DELTA motion.  This is so, at slow
1882                            speeds where the system's acceleration curve is actually reducing the
1883                            scroll distance, the user is sure to get some action out of each click.
1884                            For example, this is important for rotating though weapons in a
1885                            first-person shooter. */
1886                         if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1887                             event->mouse_scroll.x_scroll = 120;
1888                         else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1889                             event->mouse_scroll.x_scroll = -120;
1891                         if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1892                             event->mouse_scroll.y_scroll = 120;
1893                         else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1894                             event->mouse_scroll.y_scroll = -120;
1895                     }
1896                 }
1897                 else
1898                 {
1899                     /* If it's been a while since the last scroll event or if the scrolling has
1900                        reversed direction, reset the accumulated scroll value. */
1901                     if ([theEvent timestamp] - lastScrollTime > 1)
1902                         accumScrollX = accumScrollY = 0;
1903                     else
1904                     {
1905                         /* The accumulated scroll value is in the opposite direction/sign of the last
1906                            scroll.  That's because it's the "debt" resulting from over-scrolling in
1907                            that direction.  We accumulate by adding in the scroll amount and then, if
1908                            it has the same sign as the scroll value, we subtract any whole or partial
1909                            WHEEL_DELTAs, leaving it 0 or the opposite sign.  So, the user switched
1910                            scroll direction if the accumulated debt and the new scroll value have the
1911                            same sign. */
1912                         if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1913                             accumScrollX = 0;
1914                         if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1915                             accumScrollY = 0;
1916                     }
1917                     lastScrollTime = [theEvent timestamp];
1919                     accumScrollX += x;
1920                     accumScrollY += y;
1922                     if (accumScrollX > 0 && x > 0)
1923                         event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1924                     if (accumScrollX < 0 && x < 0)
1925                         event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1926                     if (accumScrollY > 0 && y > 0)
1927                         event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1928                     if (accumScrollY < 0 && y < 0)
1929                         event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1931                     accumScrollX -= event->mouse_scroll.x_scroll;
1932                     accumScrollY -= event->mouse_scroll.y_scroll;
1933                 }
1935                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1936                     [window.queue postEvent:event];
1938                 macdrv_release_event(event);
1940                 // Since scroll wheel events deliver absolute cursor position, the
1941                 // accumulating delta from move events is invalidated.  Make sure next
1942                 // mouse move event starts over from an absolute baseline.
1943                 forceNextMouseMoveAbsolute = TRUE;
1944             }
1945         }
1946     }
1948     // Returns TRUE if the event was handled and caller should do nothing more
1949     // with it.  Returns FALSE if the caller should process it as normal and
1950     // then call -didSendEvent:.
1951     - (BOOL) handleEvent:(NSEvent*)anEvent
1952     {
1953         BOOL ret = FALSE;
1954         NSEventType type = [anEvent type];
1956         if (type == NSFlagsChanged)
1957             self.lastFlagsChanged = anEvent;
1958         else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1959                  type == NSRightMouseDragged || type == NSOtherMouseDragged)
1960         {
1961             [self handleMouseMove:anEvent];
1962             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1963         }
1964         else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1965                  type == NSRightMouseDown || type == NSRightMouseUp ||
1966                  type == NSOtherMouseDown || type == NSOtherMouseUp)
1967         {
1968             [self handleMouseButton:anEvent];
1969             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1970         }
1971         else if (type == NSScrollWheel)
1972         {
1973             [self handleScrollWheel:anEvent];
1974             ret = mouseCaptureWindow != nil;
1975         }
1976         else if (type == NSKeyUp)
1977         {
1978             uint16_t keyCode = [anEvent keyCode];
1979             if ([self isKeyPressed:keyCode])
1980             {
1981                 WineWindow* window = (WineWindow*)[anEvent window];
1982                 [self noteKey:keyCode pressed:FALSE];
1983                 if ([window isKindOfClass:[WineWindow class]])
1984                     [window postKeyEvent:anEvent];
1985             }
1986         }
1987         else if (type == NSAppKitDefined)
1988         {
1989             short subtype = [anEvent subtype];
1991             // These subtypes are not documented but they appear to mean
1992             // "a window is being dragged" and "a window is no longer being
1993             // dragged", respectively.
1994             if (subtype == 20 || subtype == 21)
1995             {
1996                 WineWindow* window = (WineWindow*)[anEvent window];
1997                 if ([window isKindOfClass:[WineWindow class]])
1998                 {
1999                     macdrv_event* event;
2000                     int eventType;
2002                     if (subtype == 20)
2003                     {
2004                         [windowsBeingDragged addObject:window];
2005                         eventType = WINDOW_DRAG_BEGIN;
2006                     }
2007                     else
2008                     {
2009                         [windowsBeingDragged removeObject:window];
2010                         eventType = WINDOW_DRAG_END;
2011                     }
2012                     [self updateCursorClippingState];
2014                     event = macdrv_create_event(eventType, window);
2015                     [window.queue postEvent:event];
2016                     macdrv_release_event(event);
2017                 }
2018             }
2019         }
2021         return ret;
2022     }
2024     - (void) didSendEvent:(NSEvent*)anEvent
2025     {
2026         NSEventType type = [anEvent type];
2028         if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
2029         {
2030             NSUInteger modifiers = [anEvent modifierFlags];
2031             if ((modifiers & NSCommandKeyMask) &&
2032                 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
2033             {
2034                 // Command-Tab and Command-Shift-Tab would normally be intercepted
2035                 // by the system to switch applications.  If we're seeing it, it's
2036                 // presumably because we've captured the displays, preventing
2037                 // normal application switching.  Do it manually.
2038                 [self handleCommandTab];
2039             }
2040         }
2041     }
2043     - (void) setupObservations
2044     {
2045         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2046         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
2047         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
2049         [nc addObserverForName:NSWindowDidBecomeKeyNotification
2050                         object:nil
2051                          queue:nil
2052                     usingBlock:^(NSNotification *note){
2053             NSWindow* window = [note object];
2054             [keyWindows removeObjectIdenticalTo:window];
2055             [keyWindows insertObject:window atIndex:0];
2056         }];
2058         [nc addObserverForName:NSWindowWillCloseNotification
2059                         object:nil
2060                          queue:[NSOperationQueue mainQueue]
2061                     usingBlock:^(NSNotification *note){
2062             NSWindow* window = [note object];
2063             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
2064                 return;
2065             [keyWindows removeObjectIdenticalTo:window];
2066             if (window == lastTargetWindow)
2067                 lastTargetWindow = nil;
2068             if (window == self.mouseCaptureWindow)
2069                 self.mouseCaptureWindow = nil;
2070             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
2071             {
2072                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
2073                     [self updateFullscreenWindows];
2074                 });
2075             }
2076             [windowsBeingDragged removeObject:window];
2077             [self updateCursorClippingState];
2078         }];
2080         [nc addObserver:self
2081                selector:@selector(keyboardSelectionDidChange)
2082                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
2083                  object:nil];
2085         /* The above notification isn't sent unless the NSTextInputContext
2086            class has initialized itself.  Poke it. */
2087         [NSTextInputContext self];
2089         [wsnc addObserver:self
2090                  selector:@selector(activeSpaceDidChange)
2091                      name:NSWorkspaceActiveSpaceDidChangeNotification
2092                    object:nil];
2094         [nc addObserver:self
2095                selector:@selector(releaseMouseCapture)
2096                    name:NSMenuDidBeginTrackingNotification
2097                  object:nil];
2099         [dnc        addObserver:self
2100                        selector:@selector(releaseMouseCapture)
2101                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2102                          object:nil
2103              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2105         [dnc addObserver:self
2106                 selector:@selector(enabledKeyboardInputSourcesChanged)
2107                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2108                   object:nil];
2109     }
2111     - (BOOL) inputSourceIsInputMethod
2112     {
2113         if (!inputSourceIsInputMethodValid)
2114         {
2115             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2116             if (inputSource)
2117             {
2118                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2119                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2120                 CFRelease(inputSource);
2121             }
2122             else
2123                 inputSourceIsInputMethod = FALSE;
2124             inputSourceIsInputMethodValid = TRUE;
2125         }
2127         return inputSourceIsInputMethod;
2128     }
2130     - (void) releaseMouseCapture
2131     {
2132         // This might be invoked on a background thread by the distributed
2133         // notification center.  Shunt it to the main thread.
2134         if (![NSThread isMainThread])
2135         {
2136             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2137             return;
2138         }
2140         if (mouseCaptureWindow)
2141         {
2142             macdrv_event* event;
2144             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2145             [mouseCaptureWindow.queue postEvent:event];
2146             macdrv_release_event(event);
2147         }
2148     }
2150     - (void) unminimizeWindowIfNoneVisible
2151     {
2152         if (![self frontWineWindow])
2153         {
2154             for (WineWindow* window in [NSApp windows])
2155             {
2156                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2157                 {
2158                     [window deminiaturize:self];
2159                     break;
2160                 }
2161             }
2162         }
2163     }
2165     - (void) setRetinaMode:(int)mode
2166     {
2167         retina_on = mode;
2169         if (clippingCursor)
2170         {
2171             double scale = mode ? 0.5 : 2.0;
2172             cursorClipRect.origin.x *= scale;
2173             cursorClipRect.origin.y *= scale;
2174             cursorClipRect.size.width *= scale;
2175             cursorClipRect.size.height *= scale;
2176         }
2178         for (WineWindow* window in [NSApp windows])
2179         {
2180             if ([window isKindOfClass:[WineWindow class]])
2181                 [window setRetinaMode:mode];
2182         }
2183     }
2186     /*
2187      * ---------- NSApplicationDelegate methods ----------
2188      */
2189     - (void)applicationDidBecomeActive:(NSNotification *)notification
2190     {
2191         NSNumber* displayID;
2192         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2194         latentDisplayModes = [[NSMutableDictionary alloc] init];
2195         for (displayID in modesToRealize)
2196         {
2197             CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2198             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2199         }
2201         [self updateCursorClippingState];
2203         [self updateFullscreenWindows];
2204         [self adjustWindowLevels:YES];
2206         if (beenActive)
2207             [self unminimizeWindowIfNoneVisible];
2208         beenActive = TRUE;
2210         // If a Wine process terminates abruptly while it has the display captured
2211         // and switched to a different resolution, Mac OS X will uncapture the
2212         // displays and switch their resolutions back.  However, the other Wine
2213         // processes won't have their notion of the desktop rect changed back.
2214         // This can lead them to refuse to draw or acknowledge clicks in certain
2215         // portions of their windows.
2216         //
2217         // To solve this, we synthesize a displays-changed event whenever we're
2218         // activated.  This will provoke a re-synchronization of Wine's notion of
2219         // the desktop rect with the actual state.
2220         [self sendDisplaysChanged:TRUE];
2222         // The cursor probably moved while we were inactive.  Accumulated mouse
2223         // movement deltas are invalidated.  Make sure the next mouse move event
2224         // starts over from an absolute baseline.
2225         forceNextMouseMoveAbsolute = TRUE;
2226     }
2228     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2229     {
2230         primaryScreenHeightValid = FALSE;
2231         [self sendDisplaysChanged:FALSE];
2232         [self adjustWindowLevels];
2234         // When the display configuration changes, the cursor position may jump.
2235         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2236         // mouse move event starts over from an absolute baseline.
2237         forceNextMouseMoveAbsolute = TRUE;
2238     }
2240     - (void)applicationDidResignActive:(NSNotification *)notification
2241     {
2242         macdrv_event* event;
2243         WineEventQueue* queue;
2245         [self updateCursorClippingState];
2247         [self invalidateGotFocusEvents];
2249         event = macdrv_create_event(APP_DEACTIVATED, nil);
2251         [eventQueuesLock lock];
2252         for (queue in eventQueues)
2253             [queue postEvent:event];
2254         [eventQueuesLock unlock];
2256         macdrv_release_event(event);
2258         [self releaseMouseCapture];
2259     }
2261     - (void) applicationDidUnhide:(NSNotification*)aNotification
2262     {
2263         [self adjustWindowLevels];
2264     }
2266     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2267     {
2268         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2269         // don't count as "visible windows" for this purpose.
2270         [self unminimizeWindowIfNoneVisible];
2271         return YES;
2272     }
2274     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2275     {
2276         NSApplicationTerminateReply ret = NSTerminateNow;
2277         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2278         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2279         macdrv_event* event;
2280         WineEventQueue* queue;
2282         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2283         event->deliver = 1;
2284         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2285         {
2286             case kAELogOut:
2287             case kAEReallyLogOut:
2288                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2289                 break;
2290             case kAEShowRestartDialog:
2291                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2292                 break;
2293             case kAEShowShutdownDialog:
2294                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2295                 break;
2296             default:
2297                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2298                 break;
2299         }
2301         [eventQueuesLock lock];
2303         if ([eventQueues count])
2304         {
2305             for (queue in eventQueues)
2306                 [queue postEvent:event];
2307             ret = NSTerminateLater;
2308         }
2310         [eventQueuesLock unlock];
2312         macdrv_release_event(event);
2314         return ret;
2315     }
2317     - (void)applicationWillResignActive:(NSNotification *)notification
2318     {
2319         [self adjustWindowLevels:NO];
2320     }
2322 /***********************************************************************
2323  *              PerformRequest
2325  * Run-loop-source perform callback.  Pull request blocks from the
2326  * array of queued requests and invoke them.
2327  */
2328 static void PerformRequest(void *info)
2330     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2331     WineApplicationController* controller = [WineApplicationController sharedController];
2333     for (;;)
2334     {
2335         __block dispatch_block_t block;
2337         dispatch_sync(controller->requestsManipQueue, ^{
2338             if ([controller->requests count])
2339             {
2340                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2341                 [controller->requests removeObjectAtIndex:0];
2342             }
2343             else
2344                 block = nil;
2345         });
2347         if (!block)
2348             break;
2350         block();
2351         [block release];
2353         [pool release];
2354         pool = [[NSAutoreleasePool alloc] init];
2355     }
2357     [pool release];
2360 /***********************************************************************
2361  *              OnMainThreadAsync
2363  * Run a block on the main thread asynchronously.
2364  */
2365 void OnMainThreadAsync(dispatch_block_t block)
2367     WineApplicationController* controller = [WineApplicationController sharedController];
2369     block = [block copy];
2370     dispatch_sync(controller->requestsManipQueue, ^{
2371         [controller->requests addObject:block];
2372     });
2373     [block release];
2374     CFRunLoopSourceSignal(controller->requestSource);
2375     CFRunLoopWakeUp(CFRunLoopGetMain());
2378 @end
2380 /***********************************************************************
2381  *              LogError
2382  */
2383 void LogError(const char* func, NSString* format, ...)
2385     va_list args;
2386     va_start(args, format);
2387     LogErrorv(func, format, args);
2388     va_end(args);
2391 /***********************************************************************
2392  *              LogErrorv
2393  */
2394 void LogErrorv(const char* func, NSString* format, va_list args)
2396     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2398     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2399     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2400     [message release];
2402     [pool release];
2405 /***********************************************************************
2406  *              macdrv_window_rejected_focus
2408  * Pass focus to the next window that hasn't already rejected this same
2409  * WINDOW_GOT_FOCUS event.
2410  */
2411 void macdrv_window_rejected_focus(const macdrv_event *event)
2413     OnMainThread(^{
2414         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2415     });
2418 /***********************************************************************
2419  *              macdrv_get_input_source_info
2421  * Returns the keyboard layout uchr data, keyboard type and input source.
2422  */
2423 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2425     OnMainThread(^{
2426         TISInputSourceRef inputSourceLayout;
2428         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2429         if (inputSourceLayout)
2430         {
2431             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2432                                 kTISPropertyUnicodeKeyLayoutData);
2433             *uchr = CFDataCreateCopy(NULL, data);
2434             CFRelease(inputSourceLayout);
2436             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2437             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2438             if (input_source)
2439                 *input_source = TISCopyCurrentKeyboardInputSource();
2440         }
2441     });
2444 /***********************************************************************
2445  *              macdrv_beep
2447  * Play the beep sound configured by the user in System Preferences.
2448  */
2449 void macdrv_beep(void)
2451     OnMainThreadAsync(^{
2452         NSBeep();
2453     });
2456 /***********************************************************************
2457  *              macdrv_set_display_mode
2458  */
2459 int macdrv_set_display_mode(const struct macdrv_display* display,
2460                             CGDisplayModeRef display_mode)
2462     __block int ret;
2464     OnMainThread(^{
2465         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2466     });
2468     return ret;
2471 /***********************************************************************
2472  *              macdrv_set_cursor
2474  * Set the cursor.
2476  * If name is non-NULL, it is a selector for a class method on NSCursor
2477  * identifying the cursor to set.  In that case, frames is ignored.  If
2478  * name is NULL, then frames is used.
2480  * frames is an array of dictionaries.  Each dictionary is a frame of
2481  * an animated cursor.  Under the key "image" is a CGImage for the
2482  * frame.  Under the key "duration" is a CFNumber time interval, in
2483  * seconds, for how long that frame is presented before proceeding to
2484  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2485  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2486  * This is the hot spot, measured in pixels down and to the right of the
2487  * top-left corner of the image.
2489  * If the array has exactly 1 element, the cursor is static, not
2490  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2491  */
2492 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2494     SEL sel;
2496     sel = NSSelectorFromString((NSString*)name);
2497     if (sel)
2498     {
2499         OnMainThreadAsync(^{
2500             WineApplicationController* controller = [WineApplicationController sharedController];
2501             [controller setCursorWithFrames:nil];
2502             controller.cursor = [NSCursor performSelector:sel];
2503             [controller unhideCursor];
2504         });
2505     }
2506     else
2507     {
2508         NSArray* nsframes = (NSArray*)frames;
2509         if ([nsframes count])
2510         {
2511             OnMainThreadAsync(^{
2512                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2513             });
2514         }
2515         else
2516         {
2517             OnMainThreadAsync(^{
2518                 WineApplicationController* controller = [WineApplicationController sharedController];
2519                 [controller setCursorWithFrames:nil];
2520                 [controller hideCursor];
2521             });
2522         }
2523     }
2526 /***********************************************************************
2527  *              macdrv_get_cursor_position
2529  * Obtains the current cursor position.  Returns zero on failure,
2530  * non-zero on success.
2531  */
2532 int macdrv_get_cursor_position(CGPoint *pos)
2534     OnMainThread(^{
2535         NSPoint location = [NSEvent mouseLocation];
2536         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2537         *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2538     });
2540     return TRUE;
2543 /***********************************************************************
2544  *              macdrv_set_cursor_position
2546  * Sets the cursor position without generating events.  Returns zero on
2547  * failure, non-zero on success.
2548  */
2549 int macdrv_set_cursor_position(CGPoint pos)
2551     __block int ret;
2553     OnMainThread(^{
2554         ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2555     });
2557     return ret;
2560 /***********************************************************************
2561  *              macdrv_clip_cursor
2563  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2564  * to or larger than the whole desktop region, the cursor is unclipped.
2565  * Returns zero on failure, non-zero on success.
2566  */
2567 int macdrv_clip_cursor(CGRect r)
2569     __block int ret;
2571     OnMainThread(^{
2572         WineApplicationController* controller = [WineApplicationController sharedController];
2573         BOOL clipping = FALSE;
2574         CGRect rect = r;
2576         if (!CGRectIsInfinite(rect))
2577             rect = cgrect_mac_from_win(rect);
2579         if (!CGRectIsInfinite(rect))
2580         {
2581             NSRect nsrect = NSRectFromCGRect(rect);
2582             NSScreen* screen;
2584             /* Convert the rectangle from top-down coords to bottom-up. */
2585             [controller flipRect:&nsrect];
2587             clipping = FALSE;
2588             for (screen in [NSScreen screens])
2589             {
2590                 if (!NSContainsRect(nsrect, [screen frame]))
2591                 {
2592                     clipping = TRUE;
2593                     break;
2594                 }
2595             }
2596         }
2598         if (clipping)
2599             ret = [controller startClippingCursor:rect];
2600         else
2601             ret = [controller stopClippingCursor];
2602     });
2604     return ret;
2607 /***********************************************************************
2608  *              macdrv_set_application_icon
2610  * Set the application icon.  The images array contains CGImages.  If
2611  * there are more than one, then they represent different sizes or
2612  * color depths from the icon resource.  If images is NULL or empty,
2613  * restores the default application image.
2614  */
2615 void macdrv_set_application_icon(CFArrayRef images)
2617     NSArray* imageArray = (NSArray*)images;
2619     OnMainThreadAsync(^{
2620         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2621     });
2624 /***********************************************************************
2625  *              macdrv_quit_reply
2626  */
2627 void macdrv_quit_reply(int reply)
2629     OnMainThread(^{
2630         [NSApp replyToApplicationShouldTerminate:reply];
2631     });
2634 /***********************************************************************
2635  *              macdrv_using_input_method
2636  */
2637 int macdrv_using_input_method(void)
2639     __block BOOL ret;
2641     OnMainThread(^{
2642         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2643     });
2645     return ret;
2648 /***********************************************************************
2649  *              macdrv_set_mouse_capture_window
2650  */
2651 void macdrv_set_mouse_capture_window(macdrv_window window)
2653     WineWindow* w = (WineWindow*)window;
2655     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2657     OnMainThread(^{
2658         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2659     });
2662 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2663 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2664 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2666 /***********************************************************************
2667  *              macdrv_create_input_source_list
2668  */
2669 CFArrayRef macdrv_create_input_source_list(void)
2671     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2673     OnMainThread(^{
2674         CFArrayRef input_list;
2675         CFDictionaryRef filter_dict;
2676         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2677         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2678         int i;
2680         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2681                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2682         input_list = TISCreateInputSourceList(filter_dict, false);
2684         for (i = 0; i < CFArrayGetCount(input_list); i++)
2685         {
2686             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2687             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2688             CFDictionaryRef entry;
2689             const void *input_keys[3] = { macdrv_input_source_input_key,
2690                                           macdrv_input_source_type_key,
2691                                           macdrv_input_source_lang_key };
2692             const void *input_values[3];
2694             input_values[0] = input;
2695             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2696             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2698             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2699                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2701             CFArrayAppendValue(ret, entry);
2702             CFRelease(entry);
2703         }
2704         CFRelease(input_list);
2705         CFRelease(filter_dict);
2706     });
2708     return ret;
2711 int macdrv_select_input_source(TISInputSourceRef input_source)
2713     __block int ret = FALSE;
2715     OnMainThread(^{
2716         ret = (TISSelectInputSource(input_source) == noErr);
2717     });
2719     return ret;
2722 void macdrv_set_cocoa_retina_mode(int new_mode)
2724     OnMainThread(^{
2725         [[WineApplicationController sharedController] setRetinaMode:new_mode];
2726     });