webservices: Accept zero write option in WsWriteType.
[wine.git] / dlls / winemac.drv / cocoa_app.m
blobdced704af997fc1679f039e6633d39836cfdfa3e
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 WineWindow* prev = nil;
524         WineWindow* window;
526         if ([NSApp isHidden]) return;
528         windowNumbers = [NSWindow windowNumbersWithOptions:0];
529         wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
531         // For the most part, we rely on the window server's ordering of the windows
532         // to be authoritative.  The one exception is if the "floating" property of
533         // one of the windows has been changed, it may be in the wrong level and thus
534         // in the order.  This method is what's supposed to fix that up.  So build
535         // a list of Wine windows sorted first by floating-ness and then by order
536         // as indicated by the window server.
537         for (windowNumber in windowNumbers)
538         {
539             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
540             if ([window isKindOfClass:[WineWindow class]])
541             {
542                 if (window.floating)
543                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
544                 else
545                     [wineWindows addObject:window];
546             }
547         }
549         NSDisableScreenUpdates();
551         // Go from back to front so that all windows in front of one which is
552         // elevated for full-screen are also elevated.
553         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
554                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
555             WineWindow* window = (WineWindow*)obj;
556             NSInteger origLevel = [window level];
557             NSInteger newLevel = [window minimumLevelForActive:active];
559             if (newLevel < maxLevel)
560                 newLevel = maxLevel;
561             else
562                 maxLevel = newLevel;
564             if (!window.floating && maxNonfloatingLevel < newLevel)
565                 maxNonfloatingLevel = newLevel;
567             if (newLevel != origLevel)
568             {
569                 [window setLevel:newLevel];
571                 // -setLevel: puts the window at the front of its new level.  If
572                 // we decreased the level, that's good (it was in front of that
573                 // level before, so it should still be now).  But if we increased
574                 // the level, the window should be toward the back (but still
575                 // ahead of the previous windows we did this to).
576                 if (origLevel < newLevel)
577                 {
578                     if (prev)
579                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
580                     else
581                         [window orderBack:nil];
582                 }
583             }
585             prev = window;
586         }];
588         NSEnableScreenUpdates();
590         [wineWindows release];
592         // The above took care of the visible windows on the current space.  That
593         // leaves windows on other spaces, minimized windows, and windows which
594         // are not ordered in.  We want to leave windows on other spaces alone
595         // so the space remains just as they left it (when viewed in Exposé or
596         // Mission Control, for example).  We'll adjust the window levels again
597         // after we switch to another space, anyway.  Windows which aren't
598         // ordered in will be handled when we order them in.  Minimized windows
599         // on the current space should be set to the level they would have gotten
600         // if they were at the front of the windows with the same floating-ness,
601         // because that's where they'll go if/when they are unminimized.  Again,
602         // for good measure we'll adjust window levels again when a window is
603         // unminimized, too.
604         for (window in [NSApp windows])
605         {
606             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
607                 [window isOnActiveSpace])
608             {
609                 NSInteger origLevel = [window level];
610                 NSInteger newLevel = [window minimumLevelForActive:YES];
611                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
613                 if (newLevel < maxLevelForType)
614                     newLevel = maxLevelForType;
616                 if (newLevel != origLevel)
617                     [window setLevel:newLevel];
618             }
619         }
620     }
622     - (void) adjustWindowLevels
623     {
624         [self adjustWindowLevels:[NSApp isActive]];
625     }
627     - (void) updateFullscreenWindows
628     {
629         if (capture_displays_for_fullscreen && [NSApp isActive])
630         {
631             BOOL anyFullscreen = FALSE;
632             NSNumber* windowNumber;
633             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
634             {
635                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
636                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
637                 {
638                     anyFullscreen = TRUE;
639                     break;
640                 }
641             }
643             if (anyFullscreen)
644             {
645                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
646                     displaysCapturedForFullscreen = TRUE;
647             }
648             else if (displaysCapturedForFullscreen)
649             {
650                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
651                     displaysCapturedForFullscreen = FALSE;
652             }
653         }
654     }
656     - (void) activeSpaceDidChange
657     {
658         [self updateFullscreenWindows];
659         [self adjustWindowLevels];
660     }
662     - (void) sendDisplaysChanged:(BOOL)activating
663     {
664         macdrv_event* event;
665         WineEventQueue* queue;
667         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
668         event->displays_changed.activating = activating;
670         [eventQueuesLock lock];
672         // If we're activating, then we just need one of our threads to get the
673         // event, so it can send it directly to the desktop window.  Otherwise,
674         // we need all of the threads to get it because we don't know which owns
675         // the desktop window and only that one will do anything with it.
676         if (activating) event->deliver = 1;
678         for (queue in eventQueues)
679             [queue postEvent:event];
680         [eventQueuesLock unlock];
682         macdrv_release_event(event);
683     }
685     // We can compare two modes directly using CFEqual, but that may require that
686     // they are identical to a level that we don't need.  In particular, when the
687     // OS switches between the integrated and discrete GPUs, the set of display
688     // modes can change in subtle ways.  We're interested in whether two modes
689     // match in their most salient features, even if they aren't identical.
690     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
691     {
692         NSString *encoding1, *encoding2;
693         uint32_t ioflags1, ioflags2, different;
694         double refresh1, refresh2;
696         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
697         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
699 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
700         if (CGDisplayModeGetPixelWidth != NULL &&
701             CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
702         if (CGDisplayModeGetPixelHeight != NULL &&
703             CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
704 #endif
706         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
707         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
708         if (![encoding1 isEqualToString:encoding2]) return FALSE;
710         ioflags1 = CGDisplayModeGetIOFlags(mode1);
711         ioflags2 = CGDisplayModeGetIOFlags(mode2);
712         different = ioflags1 ^ ioflags2;
713         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
714                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
715             return FALSE;
717         refresh1 = CGDisplayModeGetRefreshRate(mode1);
718         if (refresh1 == 0) refresh1 = 60;
719         refresh2 = CGDisplayModeGetRefreshRate(mode2);
720         if (refresh2 == 0) refresh2 = 60;
721         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
723         return TRUE;
724     }
726     - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
727     {
728         NSMutableArray* ret = [NSMutableArray array];
729         NSDictionary* options = nil;
731 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
732         if (&kCGDisplayShowDuplicateLowResolutionModes != NULL)
733             options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:TRUE]
734                                                   forKey:(NSString*)kCGDisplayShowDuplicateLowResolutionModes];
735 #endif
737         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
738         for (id candidateModeObject in modes)
739         {
740             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
741             if ([self mode:candidateMode matchesMode:mode])
742                 [ret addObject:candidateModeObject];
743         }
744         return ret;
745     }
747     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
748     {
749         BOOL ret = FALSE;
750         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
751         CGDisplayModeRef originalMode;
753         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
755         if (originalMode && [self mode:mode matchesMode:originalMode])
756         {
757             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
758             {
759                 CGRestorePermanentDisplayConfiguration();
760                 if (!displaysCapturedForFullscreen)
761                     CGReleaseAllDisplays();
762                 [originalDisplayModes removeAllObjects];
763                 ret = TRUE;
764             }
765             else // ... otherwise, try to restore just the one display
766             {
767                 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
768                 {
769                     mode = (CGDisplayModeRef)modeObject;
770                     if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
771                     {
772                         [originalDisplayModes removeObjectForKey:displayIDKey];
773                         ret = TRUE;
774                         break;
775                     }
776                 }
777             }
778         }
779         else
780         {
781             BOOL active = [NSApp isActive];
782             CGDisplayModeRef currentMode;
783             NSArray* modes;
785             currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
786             if (!currentMode)
787                 currentMode = CGDisplayCopyDisplayMode(displayID);
788             if (!currentMode) // Invalid display ID
789                 return FALSE;
791             if ([self mode:mode matchesMode:currentMode]) // Already there!
792             {
793                 CGDisplayModeRelease(currentMode);
794                 return TRUE;
795             }
797             CGDisplayModeRelease(currentMode);
798             currentMode = NULL;
800             modes = [self modesMatchingMode:mode forDisplay:displayID];
801             if (!modes.count)
802                 return FALSE;
804             if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
805                 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
806             {
807                 if (active)
808                 {
809                     // If we get here, we have the displays captured.  If we don't
810                     // know the original mode of the display, the current mode must
811                     // be the original.  We should re-query the current mode since
812                     // another process could have changed it between when we last
813                     // checked and when we captured the displays.
814                     if (!originalMode)
815                         originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
817                     if (originalMode)
818                     {
819                         for (id modeObject in modes)
820                         {
821                             mode = (CGDisplayModeRef)modeObject;
822                             if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
823                             {
824                                 ret = TRUE;
825                                 break;
826                             }
827                         }
828                     }
829                     if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
830                         [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
831                     else if (![originalDisplayModes count])
832                     {
833                         CGRestorePermanentDisplayConfiguration();
834                         if (!displaysCapturedForFullscreen)
835                             CGReleaseAllDisplays();
836                     }
838                     if (currentMode)
839                         CGDisplayModeRelease(currentMode);
840                 }
841                 else
842                 {
843                     [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
844                     ret = TRUE;
845                 }
846             }
847         }
849         if (ret)
850             [self adjustWindowLevels];
852         return ret;
853     }
855     - (BOOL) areDisplaysCaptured
856     {
857         return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
858     }
860     - (void) updateCursor:(BOOL)force
861     {
862         if (force || lastTargetWindow)
863         {
864             if (clientWantsCursorHidden && !cursorHidden)
865             {
866                 [NSCursor hide];
867                 cursorHidden = TRUE;
868             }
870             if (!cursorIsCurrent)
871             {
872                 [cursor set];
873                 cursorIsCurrent = TRUE;
874             }
876             if (!clientWantsCursorHidden && cursorHidden)
877             {
878                 [NSCursor unhide];
879                 cursorHidden = FALSE;
880             }
881         }
882         else
883         {
884             if (cursorIsCurrent)
885             {
886                 [[NSCursor arrowCursor] set];
887                 cursorIsCurrent = FALSE;
888             }
889             if (cursorHidden)
890             {
891                 [NSCursor unhide];
892                 cursorHidden = FALSE;
893             }
894         }
895     }
897     - (void) hideCursor
898     {
899         if (!clientWantsCursorHidden)
900         {
901             clientWantsCursorHidden = TRUE;
902             [self updateCursor:TRUE];
903         }
904     }
906     - (void) unhideCursor
907     {
908         if (clientWantsCursorHidden)
909         {
910             clientWantsCursorHidden = FALSE;
911             [self updateCursor:FALSE];
912         }
913     }
915     - (void) setCursor:(NSCursor*)newCursor
916     {
917         if (newCursor != cursor)
918         {
919             [cursor release];
920             cursor = [newCursor retain];
921             cursorIsCurrent = FALSE;
922             [self updateCursor:FALSE];
923         }
924     }
926     - (void) setCursor
927     {
928         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
929         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
930         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
931         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
932         CGPoint hotSpot;
934         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
935             hotSpot = CGPointZero;
936         self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
937         [image release];
938         [self unhideCursor];
939     }
941     - (void) nextCursorFrame:(NSTimer*)theTimer
942     {
943         NSDictionary* frame;
944         NSTimeInterval duration;
945         NSDate* date;
947         cursorFrame++;
948         if (cursorFrame >= [cursorFrames count])
949             cursorFrame = 0;
950         [self setCursor];
952         frame = [cursorFrames objectAtIndex:cursorFrame];
953         duration = [[frame objectForKey:@"duration"] doubleValue];
954         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
955         [cursorTimer setFireDate:date];
956     }
958     - (void) setCursorWithFrames:(NSArray*)frames
959     {
960         if (self.cursorFrames == frames)
961             return;
963         self.cursorFrames = frames;
964         cursorFrame = 0;
965         [cursorTimer invalidate];
966         self.cursorTimer = nil;
968         if ([frames count])
969         {
970             if ([frames count] > 1)
971             {
972                 NSDictionary* frame = [frames objectAtIndex:0];
973                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
974                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
975                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
976                                                              interval:1000000
977                                                                target:self
978                                                              selector:@selector(nextCursorFrame:)
979                                                              userInfo:nil
980                                                               repeats:YES] autorelease];
981                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
982             }
984             [self setCursor];
985         }
986     }
988     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
989     {
990         NSImage* nsimage = nil;
992         if ([images count])
993         {
994             NSSize bestSize = NSZeroSize;
995             id image;
997             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
999             for (image in images)
1000             {
1001                 CGImageRef cgimage = (CGImageRef)image;
1002                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1003                 if (imageRep)
1004                 {
1005                     NSSize size = [imageRep size];
1007                     [nsimage addRepresentation:imageRep];
1008                     [imageRep release];
1010                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1011                         bestSize = size;
1012                 }
1013             }
1015             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1016                 [nsimage setSize:bestSize];
1017             else
1018                 nsimage = nil;
1019         }
1021         self.applicationIcon = nsimage;
1022     }
1024     - (void) handleCommandTab
1025     {
1026         if ([NSApp isActive])
1027         {
1028             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1029             NSRunningApplication* app;
1030             NSRunningApplication* otherValidApp = nil;
1032             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1033             {
1034                 NSNumber* displayID;
1035                 for (displayID in originalDisplayModes)
1036                 {
1037                     CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1038                     [latentDisplayModes setObject:(id)mode forKey:displayID];
1039                     CGDisplayModeRelease(mode);
1040                 }
1042                 CGRestorePermanentDisplayConfiguration();
1043                 CGReleaseAllDisplays();
1044                 [originalDisplayModes removeAllObjects];
1045                 displaysCapturedForFullscreen = FALSE;
1046             }
1048             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1049             {
1050                 if (![app isEqual:thisApp] && !app.terminated &&
1051                     app.activationPolicy == NSApplicationActivationPolicyRegular)
1052                 {
1053                     if (!app.hidden)
1054                     {
1055                         // There's another visible app.  Just hide ourselves and let
1056                         // the system activate the other app.
1057                         [NSApp hide:self];
1058                         return;
1059                     }
1061                     if (!otherValidApp)
1062                         otherValidApp = app;
1063                 }
1064             }
1066             // Didn't find a visible GUI app.  Try the Finder or, if that's not
1067             // running, the first hidden GUI app.  If even that doesn't work, we
1068             // just fail to switch and remain the active app.
1069             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1070             if (!app) app = otherValidApp;
1071             [app unhide];
1072             [app activateWithOptions:0];
1073         }
1074     }
1076     /*
1077      * ---------- Cursor clipping methods ----------
1078      *
1079      * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1080      * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1081      * equivalent: CGAssociateMouseAndMouseCursorPosition(false).  For the
1082      * general case, we leverage that.  We disassociate mouse movements from
1083      * the cursor position and then move the cursor manually, keeping it within
1084      * the clipping rectangle.
1085      *
1086      * Moving the cursor manually isn't enough.  We need to modify the event
1087      * stream so that the events have the new location, too.  We need to do
1088      * this at a point before the events enter Cocoa, so that Cocoa will assign
1089      * the correct window to the event.  So, we install a Quartz event tap to
1090      * do that.
1091      *
1092      * Also, there's a complication when we move the cursor.  We use
1093      * CGWarpMouseCursorPosition().  That doesn't generate mouse movement
1094      * events, but the change of cursor position is incorporated into the
1095      * deltas of the next mouse move event.  When the mouse is disassociated
1096      * from the cursor position, we need the deltas to only reflect actual
1097      * device movement, not programmatic changes.  So, the event tap cancels
1098      * out the change caused by our calls to CGWarpMouseCursorPosition().
1099      */
1100     - (void) clipCursorLocation:(CGPoint*)location
1101     {
1102         if (location->x < CGRectGetMinX(cursorClipRect))
1103             location->x = CGRectGetMinX(cursorClipRect);
1104         if (location->y < CGRectGetMinY(cursorClipRect))
1105             location->y = CGRectGetMinY(cursorClipRect);
1106         if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1107             location->x = CGRectGetMaxX(cursorClipRect) - 1;
1108         if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1109             location->y = CGRectGetMaxY(cursorClipRect) - 1;
1110     }
1112     - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1113     {
1114         CGPoint oldLocation;
1116         if (currentLocation)
1117             oldLocation = *currentLocation;
1118         else
1119             oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1121         if (!CGPointEqualToPoint(oldLocation, *newLocation))
1122         {
1123             WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1124             CGError err;
1126             warpRecord.from = oldLocation;
1127             warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1129             /* Actually move the cursor. */
1130             err = CGWarpMouseCursorPosition(*newLocation);
1131             if (err != kCGErrorSuccess)
1132                 return FALSE;
1134             warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1135             *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1137             if (!CGPointEqualToPoint(oldLocation, *newLocation))
1138             {
1139                 warpRecord.to = *newLocation;
1140                 [warpRecords addObject:warpRecord];
1141             }
1142         }
1144         return TRUE;
1145     }
1147     - (BOOL) isMouseMoveEventType:(CGEventType)type
1148     {
1149         switch(type)
1150         {
1151         case kCGEventMouseMoved:
1152         case kCGEventLeftMouseDragged:
1153         case kCGEventRightMouseDragged:
1154         case kCGEventOtherMouseDragged:
1155             return TRUE;
1156         }
1158         return FALSE;
1159     }
1161     - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1162     {
1163         int warpsFinished = 0;
1164         for (WarpRecord* warpRecord in warpRecords)
1165         {
1166             if (warpRecord.timeAfter < eventTime ||
1167                 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1168                 warpsFinished++;
1169             else
1170                 break;
1171         }
1173         return warpsFinished;
1174     }
1176     - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1177                                 type:(CGEventType)type
1178                                event:(CGEventRef)event
1179     {
1180         CGEventTimestamp eventTime;
1181         CGPoint eventLocation, cursorLocation;
1183         if (type == kCGEventTapDisabledByUserInput)
1184             return event;
1185         if (type == kCGEventTapDisabledByTimeout)
1186         {
1187             CGEventTapEnable(cursorClippingEventTap, TRUE);
1188             return event;
1189         }
1191         if (!clippingCursor)
1192             return event;
1194         eventTime = CGEventGetTimestamp(event);
1195         lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1197         eventLocation = CGEventGetLocation(event);
1199         cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1201         if ([self isMouseMoveEventType:type])
1202         {
1203             double deltaX, deltaY;
1204             int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1205             int i;
1207             deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1208             deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1210             for (i = 0; i < warpsFinished; i++)
1211             {
1212                 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1213                 deltaX -= warpRecord.to.x - warpRecord.from.x;
1214                 deltaY -= warpRecord.to.y - warpRecord.from.y;
1215                 [warpRecords removeObjectAtIndex:0];
1216             }
1218             if (warpsFinished)
1219             {
1220                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1221                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1222             }
1224             synthesizedLocation.x += deltaX;
1225             synthesizedLocation.y += deltaY;
1226         }
1228         // If the event is destined for another process, don't clip it.  This may
1229         // happen if the user activates Exposé or Mission Control.  In that case,
1230         // our app does not resign active status, so clipping is still in effect,
1231         // but the cursor should not actually be clipped.
1232         //
1233         // In addition, the fact that mouse moves may have been delivered to a
1234         // different process means we have to treat the next one we receive as
1235         // absolute rather than relative.
1236         if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1237             [self clipCursorLocation:&synthesizedLocation];
1238         else
1239             lastSetCursorPositionTime = lastEventTapEventTime;
1241         [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1242         if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1243             CGEventSetLocation(event, synthesizedLocation);
1245         return event;
1246     }
1248     CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1249                                        CGEventRef event, void *refcon)
1250     {
1251         WineApplicationController* controller = refcon;
1252         return [controller eventTapWithProxy:proxy type:type event:event];
1253     }
1255     - (BOOL) installEventTap
1256     {
1257         ProcessSerialNumber psn;
1258         OSErr err;
1259         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
1260                            CGEventMaskBit(kCGEventLeftMouseUp)          |
1261                            CGEventMaskBit(kCGEventRightMouseDown)       |
1262                            CGEventMaskBit(kCGEventRightMouseUp)         |
1263                            CGEventMaskBit(kCGEventMouseMoved)           |
1264                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
1265                            CGEventMaskBit(kCGEventRightMouseDragged)    |
1266                            CGEventMaskBit(kCGEventOtherMouseDown)       |
1267                            CGEventMaskBit(kCGEventOtherMouseUp)         |
1268                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
1269                            CGEventMaskBit(kCGEventScrollWheel);
1270         CFRunLoopSourceRef source;
1271         void* appServices;
1272         OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1274         if (cursorClippingEventTap)
1275             return TRUE;
1277         // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1278         // framework with dlsym() because the Win32 function of the same name
1279         // obscures it.
1280         appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1281         if (!appServices)
1282             return FALSE;
1284         pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1285         if (!pGetCurrentProcess)
1286         {
1287             dlclose(appServices);
1288             return FALSE;
1289         }
1291         err = pGetCurrentProcess(&psn);
1292         dlclose(appServices);
1293         if (err != noErr)
1294             return FALSE;
1296         // We create an annotated session event tap rather than a process-specific
1297         // event tap because we need to programmatically move the cursor even when
1298         // mouse moves are directed to other processes.  We disable our tap when
1299         // other processes are active, but things like Exposé are handled by other
1300         // processes even when we remain active.
1301         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1302             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1303         if (!cursorClippingEventTap)
1304             return FALSE;
1306         CGEventTapEnable(cursorClippingEventTap, FALSE);
1308         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1309         if (!source)
1310         {
1311             CFRelease(cursorClippingEventTap);
1312             cursorClippingEventTap = NULL;
1313             return FALSE;
1314         }
1316         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1317         CFRelease(source);
1318         return TRUE;
1319     }
1321     - (BOOL) setCursorPosition:(CGPoint)pos
1322     {
1323         BOOL ret;
1325         if ([windowsBeingDragged count])
1326             ret = FALSE;
1327         else if (clippingCursor)
1328         {
1329             [self clipCursorLocation:&pos];
1331             ret = [self warpCursorTo:&pos from:NULL];
1332             synthesizedLocation = pos;
1333             if (ret)
1334             {
1335                 // We want to discard mouse-move events that have already been
1336                 // through the event tap, because it's too late to account for
1337                 // the setting of the cursor position with them.  However, the
1338                 // events that may be queued with times after that but before
1339                 // the above warp can still be used.  So, use the last event
1340                 // tap event time so that -sendEvent: doesn't discard them.
1341                 lastSetCursorPositionTime = lastEventTapEventTime;
1342             }
1343         }
1344         else
1345         {
1346             // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1347             // the mouse from the cursor position for 0.25 seconds.  This means
1348             // that mouse movement during that interval doesn't move the cursor
1349             // and events carry a constant location (the warped-to position)
1350             // even though they have delta values.  For apps which warp the
1351             // cursor frequently (like after every mouse move), this makes
1352             // cursor movement horribly laggy and jerky, as only a fraction of
1353             // mouse move events have any effect.
1354             //
1355             // On some versions of OS X, it's sufficient to forcibly reassociate
1356             // the mouse and cursor position.  On others, it's necessary to set
1357             // the local events suppression interval to 0 for the warp.  That's
1358             // deprecated, but I'm not aware of any other way.  For good
1359             // measure, we do both.
1360             CGSetLocalEventsSuppressionInterval(0);
1361             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1362             CGSetLocalEventsSuppressionInterval(0.25);
1363             if (ret)
1364             {
1365                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1367                 CGAssociateMouseAndMouseCursorPosition(true);
1368             }
1369         }
1371         if (ret)
1372         {
1373             WineEventQueue* queue;
1375             // Discard all pending mouse move events.
1376             [eventQueuesLock lock];
1377             for (queue in eventQueues)
1378             {
1379                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1380                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1381                                        forWindow:nil];
1382                 [queue resetMouseEventPositions:pos];
1383             }
1384             [eventQueuesLock unlock];
1385         }
1387         return ret;
1388     }
1390     - (void) activateCursorClipping
1391     {
1392         if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1393         {
1394             CGEventTapEnable(cursorClippingEventTap, TRUE);
1395             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1396         }
1397     }
1399     - (void) deactivateCursorClipping
1400     {
1401         if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1402         {
1403             CGEventTapEnable(cursorClippingEventTap, FALSE);
1404             [warpRecords removeAllObjects];
1405             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1406         }
1407     }
1409     - (void) updateCursorClippingState
1410     {
1411         if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1412             [self activateCursorClipping];
1413         else
1414             [self deactivateCursorClipping];
1415     }
1417     - (void) updateWindowsForCursorClipping
1418     {
1419         WineWindow* window;
1420         for (window in [NSApp windows])
1421         {
1422             if ([window isKindOfClass:[WineWindow class]])
1423                 [window updateForCursorClipping];
1424         }
1425     }
1427     - (BOOL) startClippingCursor:(CGRect)rect
1428     {
1429         CGError err;
1431         if (!cursorClippingEventTap && ![self installEventTap])
1432             return FALSE;
1434         if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1435             CGEventTapIsEnabled(cursorClippingEventTap))
1436             return TRUE;
1438         err = CGAssociateMouseAndMouseCursorPosition(false);
1439         if (err != kCGErrorSuccess)
1440             return FALSE;
1442         clippingCursor = TRUE;
1443         cursorClipRect = rect;
1444         [self updateCursorClippingState];
1445         [self updateWindowsForCursorClipping];
1447         return TRUE;
1448     }
1450     - (BOOL) stopClippingCursor
1451     {
1452         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1453         if (err != kCGErrorSuccess)
1454             return FALSE;
1456         clippingCursor = FALSE;
1457         [self updateCursorClippingState];
1458         [self updateWindowsForCursorClipping];
1460         return TRUE;
1461     }
1463     - (BOOL) isKeyPressed:(uint16_t)keyCode
1464     {
1465         int bits = sizeof(pressedKeyCodes[0]) * 8;
1466         int index = keyCode / bits;
1467         uint32_t mask = 1 << (keyCode % bits);
1468         return (pressedKeyCodes[index] & mask) != 0;
1469     }
1471     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1472     {
1473         int bits = sizeof(pressedKeyCodes[0]) * 8;
1474         int index = keyCode / bits;
1475         uint32_t mask = 1 << (keyCode % bits);
1476         if (pressed)
1477             pressedKeyCodes[index] |= mask;
1478         else
1479             pressedKeyCodes[index] &= ~mask;
1480     }
1482     - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1483     {
1484         if (dragged)
1485             [windowsBeingDragged addObject:window];
1486         else
1487             [windowsBeingDragged removeObject:window];
1488         [self updateCursorClippingState];
1489     }
1491     - (void) handleMouseMove:(NSEvent*)anEvent
1492     {
1493         WineWindow* targetWindow;
1494         BOOL drag = [anEvent type] != NSMouseMoved;
1496         if ([windowsBeingDragged count])
1497             targetWindow = nil;
1498         else if (mouseCaptureWindow)
1499             targetWindow = mouseCaptureWindow;
1500         else if (drag)
1501             targetWindow = (WineWindow*)[anEvent window];
1502         else
1503         {
1504             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1505                event indicates its window is the main window, even if the cursor is
1506                over a different window.  Find the actual WineWindow that is under the
1507                cursor and post the event as being for that window. */
1508             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1509             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1510             NSInteger windowUnderNumber;
1512             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1513                                   belowWindowWithWindowNumber:0];
1514             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1515             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1516                 targetWindow = nil;
1517         }
1519         if ([targetWindow isKindOfClass:[WineWindow class]])
1520         {
1521             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1522             macdrv_event* event;
1523             BOOL absolute;
1525             // If we recently warped the cursor (other than in our cursor-clipping
1526             // event tap), discard mouse move events until we see an event which is
1527             // later than that time.
1528             if (lastSetCursorPositionTime)
1529             {
1530                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1531                     return;
1533                 lastSetCursorPositionTime = 0;
1534                 forceNextMouseMoveAbsolute = TRUE;
1535             }
1537             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1538             {
1539                 absolute = TRUE;
1540                 forceNextMouseMoveAbsolute = FALSE;
1541             }
1542             else
1543             {
1544                 // Send absolute move events if the cursor is in the interior of
1545                 // its range.  Only send relative moves if the cursor is pinned to
1546                 // the boundaries of where it can go.  We compute the position
1547                 // that's one additional point in the direction of movement.  If
1548                 // that is outside of the clipping rect or desktop region (the
1549                 // union of the screen frames), then we figure the cursor would
1550                 // have moved outside if it could but it was pinned.
1551                 CGPoint computedPoint = point;
1552                 CGFloat deltaX = [anEvent deltaX];
1553                 CGFloat deltaY = [anEvent deltaY];
1555                 if (deltaX > 0.001)
1556                     computedPoint.x++;
1557                 else if (deltaX < -0.001)
1558                     computedPoint.x--;
1560                 if (deltaY > 0.001)
1561                     computedPoint.y++;
1562                 else if (deltaY < -0.001)
1563                     computedPoint.y--;
1565                 // Assume cursor is pinned for now
1566                 absolute = FALSE;
1567                 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1568                 {
1569                     const CGRect* rects;
1570                     NSUInteger count, i;
1572                     // Caches screenFrameCGRects if necessary
1573                     [self primaryScreenHeight];
1575                     rects = [screenFrameCGRects bytes];
1576                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1578                     for (i = 0; i < count; i++)
1579                     {
1580                         if (CGRectContainsPoint(rects[i], computedPoint))
1581                         {
1582                             absolute = TRUE;
1583                             break;
1584                         }
1585                     }
1586                 }
1587             }
1589             if (absolute)
1590             {
1591                 if (clippingCursor)
1592                     [self clipCursorLocation:&point];
1593                 point = cgpoint_win_from_mac(point);
1595                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1596                 event->mouse_moved.x = floor(point.x);
1597                 event->mouse_moved.y = floor(point.y);
1599                 mouseMoveDeltaX = 0;
1600                 mouseMoveDeltaY = 0;
1601             }
1602             else
1603             {
1604                 double scale = retina_on ? 2 : 1;
1606                 /* Add event delta to accumulated delta error */
1607                 /* deltaY is already flipped */
1608                 mouseMoveDeltaX += [anEvent deltaX];
1609                 mouseMoveDeltaY += [anEvent deltaY];
1611                 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1612                 event->mouse_moved.x = mouseMoveDeltaX * scale;
1613                 event->mouse_moved.y = mouseMoveDeltaY * scale;
1615                 /* Keep the remainder after integer truncation. */
1616                 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1617                 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1618             }
1620             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1621             {
1622                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1623                 event->mouse_moved.drag = drag;
1625                 [targetWindow.queue postEvent:event];
1626             }
1628             macdrv_release_event(event);
1630             lastTargetWindow = targetWindow;
1631         }
1632         else
1633             lastTargetWindow = nil;
1635         [self updateCursor:FALSE];
1636     }
1638     - (void) handleMouseButton:(NSEvent*)theEvent
1639     {
1640         WineWindow* window = (WineWindow*)[theEvent window];
1641         NSEventType type = [theEvent type];
1642         WineWindow* windowBroughtForward = nil;
1643         BOOL process = FALSE;
1645         if ([window isKindOfClass:[WineWindow class]] &&
1646             type == NSLeftMouseDown &&
1647             (([theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask| NSAlternateKeyMask | NSCommandKeyMask)) != NSCommandKeyMask))
1648         {
1649             NSWindowButton windowButton;
1651             windowBroughtForward = window;
1653             /* Any left-click on our window anyplace other than the close or
1654                minimize buttons will bring it forward. */
1655             for (windowButton = NSWindowCloseButton;
1656                  windowButton <= NSWindowMiniaturizeButton;
1657                  windowButton++)
1658             {
1659                 NSButton* button = [window standardWindowButton:windowButton];
1660                 if (button)
1661                 {
1662                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1663                     if ([button mouse:point inRect:[button bounds]])
1664                     {
1665                         windowBroughtForward = nil;
1666                         break;
1667                     }
1668                 }
1669             }
1670         }
1672         if ([windowsBeingDragged count])
1673             window = nil;
1674         else if (mouseCaptureWindow)
1675             window = mouseCaptureWindow;
1677         if ([window isKindOfClass:[WineWindow class]])
1678         {
1679             BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1680             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1682             if (clippingCursor)
1683                 [self clipCursorLocation:&pt];
1685             if (pressed)
1686             {
1687                 if (mouseCaptureWindow)
1688                     process = TRUE;
1689                 else
1690                 {
1691                     // Test if the click was in the window's content area.
1692                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1693                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1694                     process = NSMouseInRect(nspoint, contentRect, NO);
1695                     if (process && [window styleMask] & NSResizableWindowMask)
1696                     {
1697                         // Ignore clicks in the grow box (resize widget).
1698                         HIPoint origin = { 0, 0 };
1699                         HIThemeGrowBoxDrawInfo info = { 0 };
1700                         HIRect bounds;
1701                         OSStatus status;
1703                         info.kind = kHIThemeGrowBoxKindNormal;
1704                         info.direction = kThemeGrowRight | kThemeGrowDown;
1705                         if ([window styleMask] & NSUtilityWindowMask)
1706                             info.size = kHIThemeGrowBoxSizeSmall;
1707                         else
1708                             info.size = kHIThemeGrowBoxSizeNormal;
1710                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1711                         if (status == noErr)
1712                         {
1713                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1714                                                         NSMinY(contentRect),
1715                                                         bounds.size.width,
1716                                                         bounds.size.height);
1717                             process = !NSMouseInRect(nspoint, growBox, NO);
1718                         }
1719                     }
1720                 }
1721                 if (process)
1722                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1723             }
1724             else
1725             {
1726                 NSEventType downType = type - 1;
1727                 NSUInteger downMask = NSEventMaskFromType(downType);
1728                 process = (unmatchedMouseDowns & downMask) != 0;
1729                 unmatchedMouseDowns &= ~downMask;
1730             }
1732             if (process)
1733             {
1734                 macdrv_event* event;
1736                 pt = cgpoint_win_from_mac(pt);
1738                 event = macdrv_create_event(MOUSE_BUTTON, window);
1739                 event->mouse_button.button = [theEvent buttonNumber];
1740                 event->mouse_button.pressed = pressed;
1741                 event->mouse_button.x = floor(pt.x);
1742                 event->mouse_button.y = floor(pt.y);
1743                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1745                 [window.queue postEvent:event];
1747                 macdrv_release_event(event);
1748             }
1749         }
1751         if (windowBroughtForward)
1752         {
1753             WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1754             NSInteger ancestorNumber = [ancestor windowNumber];
1755             NSInteger ancestorLevel = [ancestor level];
1757             for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1758             {
1759                 NSInteger windowNumber = [windowNumberObject integerValue];
1760                 if (windowNumber == ancestorNumber)
1761                     break;
1762                 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1763                 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1764                     [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1765                 {
1766                     [ancestor postBroughtForwardEvent];
1767                     break;
1768                 }
1769             }
1770             if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate)
1771                 [self windowGotFocus:windowBroughtForward];
1772         }
1774         // Since mouse button events deliver absolute cursor position, the
1775         // accumulating delta from move events is invalidated.  Make sure
1776         // next mouse move event starts over from an absolute baseline.
1777         // Also, it's at least possible that the title bar widgets (e.g. close
1778         // button, etc.) could enter an internal event loop on a mouse down that
1779         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1780         // dragged events and, after that, any notion of the cursor position
1781         // computed from accumulating deltas would be wrong.
1782         forceNextMouseMoveAbsolute = TRUE;
1783     }
1785     - (void) handleScrollWheel:(NSEvent*)theEvent
1786     {
1787         WineWindow* window;
1789         if (mouseCaptureWindow)
1790             window = mouseCaptureWindow;
1791         else
1792             window = (WineWindow*)[theEvent window];
1794         if ([window isKindOfClass:[WineWindow class]])
1795         {
1796             CGEventRef cgevent = [theEvent CGEvent];
1797             CGPoint pt = CGEventGetLocation(cgevent);
1798             BOOL process;
1800             if (clippingCursor)
1801                 [self clipCursorLocation:&pt];
1803             if (mouseCaptureWindow)
1804                 process = TRUE;
1805             else
1806             {
1807                 // Only process the event if it was in the window's content area.
1808                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1809                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1810                 process = NSMouseInRect(nspoint, contentRect, NO);
1811             }
1813             if (process)
1814             {
1815                 macdrv_event* event;
1816                 double x, y;
1817                 BOOL continuous = FALSE;
1819                 pt = cgpoint_win_from_mac(pt);
1821                 event = macdrv_create_event(MOUSE_SCROLL, window);
1822                 event->mouse_scroll.x = floor(pt.x);
1823                 event->mouse_scroll.y = floor(pt.y);
1824                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1826                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1827                 {
1828                     continuous = TRUE;
1830                     /* Continuous scroll wheel events come from high-precision scrolling
1831                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1832                        For these, we can get more precise data from the CGEvent API. */
1833                     /* Axis 1 is vertical, axis 2 is horizontal. */
1834                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1835                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1836                 }
1837                 else
1838                 {
1839                     double pixelsPerLine = 10;
1840                     CGEventSourceRef source;
1842                     /* The non-continuous values are in units of "lines", not pixels. */
1843                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1844                     {
1845                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1846                         CFRelease(source);
1847                     }
1849                     x = pixelsPerLine * [theEvent deltaX];
1850                     y = pixelsPerLine * [theEvent deltaY];
1851                 }
1853                 /* Mac: negative is right or down, positive is left or up.
1854                    Win32: negative is left or down, positive is right or up.
1855                    So, negate the X scroll value to translate. */
1856                 x = -x;
1858                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1859                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1860                    6 times the pixel value. */
1861                 x *= 6;
1862                 y *= 6;
1864                 if (use_precise_scrolling)
1865                 {
1866                     event->mouse_scroll.x_scroll = x;
1867                     event->mouse_scroll.y_scroll = y;
1869                     if (!continuous)
1870                     {
1871                         /* For non-continuous "clicky" wheels, if there was any motion, make
1872                            sure there was at least WHEEL_DELTA motion.  This is so, at slow
1873                            speeds where the system's acceleration curve is actually reducing the
1874                            scroll distance, the user is sure to get some action out of each click.
1875                            For example, this is important for rotating though weapons in a
1876                            first-person shooter. */
1877                         if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1878                             event->mouse_scroll.x_scroll = 120;
1879                         else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1880                             event->mouse_scroll.x_scroll = -120;
1882                         if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1883                             event->mouse_scroll.y_scroll = 120;
1884                         else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1885                             event->mouse_scroll.y_scroll = -120;
1886                     }
1887                 }
1888                 else
1889                 {
1890                     /* If it's been a while since the last scroll event or if the scrolling has
1891                        reversed direction, reset the accumulated scroll value. */
1892                     if ([theEvent timestamp] - lastScrollTime > 1)
1893                         accumScrollX = accumScrollY = 0;
1894                     else
1895                     {
1896                         /* The accumulated scroll value is in the opposite direction/sign of the last
1897                            scroll.  That's because it's the "debt" resulting from over-scrolling in
1898                            that direction.  We accumulate by adding in the scroll amount and then, if
1899                            it has the same sign as the scroll value, we subtract any whole or partial
1900                            WHEEL_DELTAs, leaving it 0 or the opposite sign.  So, the user switched
1901                            scroll direction if the accumulated debt and the new scroll value have the
1902                            same sign. */
1903                         if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1904                             accumScrollX = 0;
1905                         if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1906                             accumScrollY = 0;
1907                     }
1908                     lastScrollTime = [theEvent timestamp];
1910                     accumScrollX += x;
1911                     accumScrollY += y;
1913                     if (accumScrollX > 0 && x > 0)
1914                         event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1915                     if (accumScrollX < 0 && x < 0)
1916                         event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1917                     if (accumScrollY > 0 && y > 0)
1918                         event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1919                     if (accumScrollY < 0 && y < 0)
1920                         event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1922                     accumScrollX -= event->mouse_scroll.x_scroll;
1923                     accumScrollY -= event->mouse_scroll.y_scroll;
1924                 }
1926                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1927                     [window.queue postEvent:event];
1929                 macdrv_release_event(event);
1931                 // Since scroll wheel events deliver absolute cursor position, the
1932                 // accumulating delta from move events is invalidated.  Make sure next
1933                 // mouse move event starts over from an absolute baseline.
1934                 forceNextMouseMoveAbsolute = TRUE;
1935             }
1936         }
1937     }
1939     // Returns TRUE if the event was handled and caller should do nothing more
1940     // with it.  Returns FALSE if the caller should process it as normal and
1941     // then call -didSendEvent:.
1942     - (BOOL) handleEvent:(NSEvent*)anEvent
1943     {
1944         BOOL ret = FALSE;
1945         NSEventType type = [anEvent type];
1947         if (type == NSFlagsChanged)
1948             self.lastFlagsChanged = anEvent;
1949         else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1950                  type == NSRightMouseDragged || type == NSOtherMouseDragged)
1951         {
1952             [self handleMouseMove:anEvent];
1953             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1954         }
1955         else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1956                  type == NSRightMouseDown || type == NSRightMouseUp ||
1957                  type == NSOtherMouseDown || type == NSOtherMouseUp)
1958         {
1959             [self handleMouseButton:anEvent];
1960             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1961         }
1962         else if (type == NSScrollWheel)
1963         {
1964             [self handleScrollWheel:anEvent];
1965             ret = mouseCaptureWindow != nil;
1966         }
1967         else if (type == NSKeyUp)
1968         {
1969             uint16_t keyCode = [anEvent keyCode];
1970             if ([self isKeyPressed:keyCode])
1971             {
1972                 WineWindow* window = (WineWindow*)[anEvent window];
1973                 [self noteKey:keyCode pressed:FALSE];
1974                 if ([window isKindOfClass:[WineWindow class]])
1975                     [window postKeyEvent:anEvent];
1976             }
1977         }
1978         else if (type == NSAppKitDefined)
1979         {
1980             short subtype = [anEvent subtype];
1982             // These subtypes are not documented but they appear to mean
1983             // "a window is being dragged" and "a window is no longer being
1984             // dragged", respectively.
1985             if (subtype == 20 || subtype == 21)
1986             {
1987                 WineWindow* window = (WineWindow*)[anEvent window];
1988                 if ([window isKindOfClass:[WineWindow class]])
1989                 {
1990                     macdrv_event* event;
1991                     int eventType;
1993                     if (subtype == 20)
1994                     {
1995                         [windowsBeingDragged addObject:window];
1996                         eventType = WINDOW_DRAG_BEGIN;
1997                     }
1998                     else
1999                     {
2000                         [windowsBeingDragged removeObject:window];
2001                         eventType = WINDOW_DRAG_END;
2002                     }
2003                     [self updateCursorClippingState];
2005                     event = macdrv_create_event(eventType, window);
2006                     [window.queue postEvent:event];
2007                     macdrv_release_event(event);
2008                 }
2009             }
2010         }
2012         return ret;
2013     }
2015     - (void) didSendEvent:(NSEvent*)anEvent
2016     {
2017         NSEventType type = [anEvent type];
2019         if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
2020         {
2021             NSUInteger modifiers = [anEvent modifierFlags];
2022             if ((modifiers & NSCommandKeyMask) &&
2023                 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
2024             {
2025                 // Command-Tab and Command-Shift-Tab would normally be intercepted
2026                 // by the system to switch applications.  If we're seeing it, it's
2027                 // presumably because we've captured the displays, preventing
2028                 // normal application switching.  Do it manually.
2029                 [self handleCommandTab];
2030             }
2031         }
2032     }
2034     - (void) setupObservations
2035     {
2036         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2037         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
2038         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
2040         [nc addObserverForName:NSWindowDidBecomeKeyNotification
2041                         object:nil
2042                          queue:nil
2043                     usingBlock:^(NSNotification *note){
2044             NSWindow* window = [note object];
2045             [keyWindows removeObjectIdenticalTo:window];
2046             [keyWindows insertObject:window atIndex:0];
2047         }];
2049         [nc addObserverForName:NSWindowWillCloseNotification
2050                         object:nil
2051                          queue:[NSOperationQueue mainQueue]
2052                     usingBlock:^(NSNotification *note){
2053             NSWindow* window = [note object];
2054             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
2055                 return;
2056             [keyWindows removeObjectIdenticalTo:window];
2057             if (window == lastTargetWindow)
2058                 lastTargetWindow = nil;
2059             if (window == self.mouseCaptureWindow)
2060                 self.mouseCaptureWindow = nil;
2061             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
2062             {
2063                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
2064                     [self updateFullscreenWindows];
2065                 });
2066             }
2067             [windowsBeingDragged removeObject:window];
2068             [self updateCursorClippingState];
2069         }];
2071         [nc addObserver:self
2072                selector:@selector(keyboardSelectionDidChange)
2073                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
2074                  object:nil];
2076         /* The above notification isn't sent unless the NSTextInputContext
2077            class has initialized itself.  Poke it. */
2078         [NSTextInputContext self];
2080         [wsnc addObserver:self
2081                  selector:@selector(activeSpaceDidChange)
2082                      name:NSWorkspaceActiveSpaceDidChangeNotification
2083                    object:nil];
2085         [nc addObserver:self
2086                selector:@selector(releaseMouseCapture)
2087                    name:NSMenuDidBeginTrackingNotification
2088                  object:nil];
2090         [dnc        addObserver:self
2091                        selector:@selector(releaseMouseCapture)
2092                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2093                          object:nil
2094              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2096         [dnc addObserver:self
2097                 selector:@selector(enabledKeyboardInputSourcesChanged)
2098                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2099                   object:nil];
2100     }
2102     - (BOOL) inputSourceIsInputMethod
2103     {
2104         if (!inputSourceIsInputMethodValid)
2105         {
2106             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2107             if (inputSource)
2108             {
2109                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2110                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2111                 CFRelease(inputSource);
2112             }
2113             else
2114                 inputSourceIsInputMethod = FALSE;
2115             inputSourceIsInputMethodValid = TRUE;
2116         }
2118         return inputSourceIsInputMethod;
2119     }
2121     - (void) releaseMouseCapture
2122     {
2123         // This might be invoked on a background thread by the distributed
2124         // notification center.  Shunt it to the main thread.
2125         if (![NSThread isMainThread])
2126         {
2127             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2128             return;
2129         }
2131         if (mouseCaptureWindow)
2132         {
2133             macdrv_event* event;
2135             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2136             [mouseCaptureWindow.queue postEvent:event];
2137             macdrv_release_event(event);
2138         }
2139     }
2141     - (void) unminimizeWindowIfNoneVisible
2142     {
2143         if (![self frontWineWindow])
2144         {
2145             for (WineWindow* window in [NSApp windows])
2146             {
2147                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2148                 {
2149                     [window deminiaturize:self];
2150                     break;
2151                 }
2152             }
2153         }
2154     }
2156     - (void) setRetinaMode:(int)mode
2157     {
2158         retina_on = mode;
2160         if (clippingCursor)
2161         {
2162             double scale = mode ? 0.5 : 2.0;
2163             cursorClipRect.origin.x *= scale;
2164             cursorClipRect.origin.y *= scale;
2165             cursorClipRect.size.width *= scale;
2166             cursorClipRect.size.height *= scale;
2167         }
2169         for (WineWindow* window in [NSApp windows])
2170         {
2171             if ([window isKindOfClass:[WineWindow class]])
2172                 [window setRetinaMode:mode];
2173         }
2174     }
2177     /*
2178      * ---------- NSApplicationDelegate methods ----------
2179      */
2180     - (void)applicationDidBecomeActive:(NSNotification *)notification
2181     {
2182         NSNumber* displayID;
2183         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2185         latentDisplayModes = [[NSMutableDictionary alloc] init];
2186         for (displayID in modesToRealize)
2187         {
2188             CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2189             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2190         }
2192         [self updateCursorClippingState];
2194         [self updateFullscreenWindows];
2195         [self adjustWindowLevels:YES];
2197         if (beenActive)
2198             [self unminimizeWindowIfNoneVisible];
2199         beenActive = TRUE;
2201         // If a Wine process terminates abruptly while it has the display captured
2202         // and switched to a different resolution, Mac OS X will uncapture the
2203         // displays and switch their resolutions back.  However, the other Wine
2204         // processes won't have their notion of the desktop rect changed back.
2205         // This can lead them to refuse to draw or acknowledge clicks in certain
2206         // portions of their windows.
2207         //
2208         // To solve this, we synthesize a displays-changed event whenever we're
2209         // activated.  This will provoke a re-synchronization of Wine's notion of
2210         // the desktop rect with the actual state.
2211         [self sendDisplaysChanged:TRUE];
2213         // The cursor probably moved while we were inactive.  Accumulated mouse
2214         // movement deltas are invalidated.  Make sure the next mouse move event
2215         // starts over from an absolute baseline.
2216         forceNextMouseMoveAbsolute = TRUE;
2217     }
2219     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2220     {
2221         primaryScreenHeightValid = FALSE;
2222         [self sendDisplaysChanged:FALSE];
2223         [self adjustWindowLevels];
2225         // When the display configuration changes, the cursor position may jump.
2226         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2227         // mouse move event starts over from an absolute baseline.
2228         forceNextMouseMoveAbsolute = TRUE;
2229     }
2231     - (void)applicationDidResignActive:(NSNotification *)notification
2232     {
2233         macdrv_event* event;
2234         WineEventQueue* queue;
2236         [self updateCursorClippingState];
2238         [self invalidateGotFocusEvents];
2240         event = macdrv_create_event(APP_DEACTIVATED, nil);
2242         [eventQueuesLock lock];
2243         for (queue in eventQueues)
2244             [queue postEvent:event];
2245         [eventQueuesLock unlock];
2247         macdrv_release_event(event);
2249         [self releaseMouseCapture];
2250     }
2252     - (void) applicationDidUnhide:(NSNotification*)aNotification
2253     {
2254         [self adjustWindowLevels];
2255     }
2257     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2258     {
2259         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2260         // don't count as "visible windows" for this purpose.
2261         [self unminimizeWindowIfNoneVisible];
2262         return YES;
2263     }
2265     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2266     {
2267         NSApplicationTerminateReply ret = NSTerminateNow;
2268         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2269         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2270         macdrv_event* event;
2271         WineEventQueue* queue;
2273         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2274         event->deliver = 1;
2275         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2276         {
2277             case kAELogOut:
2278             case kAEReallyLogOut:
2279                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2280                 break;
2281             case kAEShowRestartDialog:
2282                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2283                 break;
2284             case kAEShowShutdownDialog:
2285                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2286                 break;
2287             default:
2288                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2289                 break;
2290         }
2292         [eventQueuesLock lock];
2294         if ([eventQueues count])
2295         {
2296             for (queue in eventQueues)
2297                 [queue postEvent:event];
2298             ret = NSTerminateLater;
2299         }
2301         [eventQueuesLock unlock];
2303         macdrv_release_event(event);
2305         return ret;
2306     }
2308     - (void)applicationWillResignActive:(NSNotification *)notification
2309     {
2310         [self adjustWindowLevels:NO];
2311     }
2313 /***********************************************************************
2314  *              PerformRequest
2316  * Run-loop-source perform callback.  Pull request blocks from the
2317  * array of queued requests and invoke them.
2318  */
2319 static void PerformRequest(void *info)
2321     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2322     WineApplicationController* controller = [WineApplicationController sharedController];
2324     for (;;)
2325     {
2326         __block dispatch_block_t block;
2328         dispatch_sync(controller->requestsManipQueue, ^{
2329             if ([controller->requests count])
2330             {
2331                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2332                 [controller->requests removeObjectAtIndex:0];
2333             }
2334             else
2335                 block = nil;
2336         });
2338         if (!block)
2339             break;
2341         block();
2342         [block release];
2344         [pool release];
2345         pool = [[NSAutoreleasePool alloc] init];
2346     }
2348     [pool release];
2351 /***********************************************************************
2352  *              OnMainThreadAsync
2354  * Run a block on the main thread asynchronously.
2355  */
2356 void OnMainThreadAsync(dispatch_block_t block)
2358     WineApplicationController* controller = [WineApplicationController sharedController];
2360     block = [block copy];
2361     dispatch_sync(controller->requestsManipQueue, ^{
2362         [controller->requests addObject:block];
2363     });
2364     [block release];
2365     CFRunLoopSourceSignal(controller->requestSource);
2366     CFRunLoopWakeUp(CFRunLoopGetMain());
2369 @end
2371 /***********************************************************************
2372  *              LogError
2373  */
2374 void LogError(const char* func, NSString* format, ...)
2376     va_list args;
2377     va_start(args, format);
2378     LogErrorv(func, format, args);
2379     va_end(args);
2382 /***********************************************************************
2383  *              LogErrorv
2384  */
2385 void LogErrorv(const char* func, NSString* format, va_list args)
2387     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2389     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2390     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2391     [message release];
2393     [pool release];
2396 /***********************************************************************
2397  *              macdrv_window_rejected_focus
2399  * Pass focus to the next window that hasn't already rejected this same
2400  * WINDOW_GOT_FOCUS event.
2401  */
2402 void macdrv_window_rejected_focus(const macdrv_event *event)
2404     OnMainThread(^{
2405         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2406     });
2409 /***********************************************************************
2410  *              macdrv_get_input_source_info
2412  * Returns the keyboard layout uchr data, keyboard type and input source.
2413  */
2414 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2416     OnMainThread(^{
2417         TISInputSourceRef inputSourceLayout;
2419         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2420         if (inputSourceLayout)
2421         {
2422             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2423                                 kTISPropertyUnicodeKeyLayoutData);
2424             *uchr = CFDataCreateCopy(NULL, data);
2425             CFRelease(inputSourceLayout);
2427             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2428             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2429             *input_source = TISCopyCurrentKeyboardInputSource();
2430         }
2431     });
2434 /***********************************************************************
2435  *              macdrv_beep
2437  * Play the beep sound configured by the user in System Preferences.
2438  */
2439 void macdrv_beep(void)
2441     OnMainThreadAsync(^{
2442         NSBeep();
2443     });
2446 /***********************************************************************
2447  *              macdrv_set_display_mode
2448  */
2449 int macdrv_set_display_mode(const struct macdrv_display* display,
2450                             CGDisplayModeRef display_mode)
2452     __block int ret;
2454     OnMainThread(^{
2455         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2456     });
2458     return ret;
2461 /***********************************************************************
2462  *              macdrv_set_cursor
2464  * Set the cursor.
2466  * If name is non-NULL, it is a selector for a class method on NSCursor
2467  * identifying the cursor to set.  In that case, frames is ignored.  If
2468  * name is NULL, then frames is used.
2470  * frames is an array of dictionaries.  Each dictionary is a frame of
2471  * an animated cursor.  Under the key "image" is a CGImage for the
2472  * frame.  Under the key "duration" is a CFNumber time interval, in
2473  * seconds, for how long that frame is presented before proceeding to
2474  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2475  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2476  * This is the hot spot, measured in pixels down and to the right of the
2477  * top-left corner of the image.
2479  * If the array has exactly 1 element, the cursor is static, not
2480  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2481  */
2482 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2484     SEL sel;
2486     sel = NSSelectorFromString((NSString*)name);
2487     if (sel)
2488     {
2489         OnMainThreadAsync(^{
2490             WineApplicationController* controller = [WineApplicationController sharedController];
2491             [controller setCursorWithFrames:nil];
2492             controller.cursor = [NSCursor performSelector:sel];
2493             [controller unhideCursor];
2494         });
2495     }
2496     else
2497     {
2498         NSArray* nsframes = (NSArray*)frames;
2499         if ([nsframes count])
2500         {
2501             OnMainThreadAsync(^{
2502                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2503             });
2504         }
2505         else
2506         {
2507             OnMainThreadAsync(^{
2508                 WineApplicationController* controller = [WineApplicationController sharedController];
2509                 [controller setCursorWithFrames:nil];
2510                 [controller hideCursor];
2511             });
2512         }
2513     }
2516 /***********************************************************************
2517  *              macdrv_get_cursor_position
2519  * Obtains the current cursor position.  Returns zero on failure,
2520  * non-zero on success.
2521  */
2522 int macdrv_get_cursor_position(CGPoint *pos)
2524     OnMainThread(^{
2525         NSPoint location = [NSEvent mouseLocation];
2526         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2527         *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2528     });
2530     return TRUE;
2533 /***********************************************************************
2534  *              macdrv_set_cursor_position
2536  * Sets the cursor position without generating events.  Returns zero on
2537  * failure, non-zero on success.
2538  */
2539 int macdrv_set_cursor_position(CGPoint pos)
2541     __block int ret;
2543     OnMainThread(^{
2544         ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2545     });
2547     return ret;
2550 /***********************************************************************
2551  *              macdrv_clip_cursor
2553  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2554  * to or larger than the whole desktop region, the cursor is unclipped.
2555  * Returns zero on failure, non-zero on success.
2556  */
2557 int macdrv_clip_cursor(CGRect r)
2559     __block int ret;
2561     OnMainThread(^{
2562         WineApplicationController* controller = [WineApplicationController sharedController];
2563         BOOL clipping = FALSE;
2564         CGRect rect = r;
2566         if (!CGRectIsInfinite(rect))
2567             rect = cgrect_mac_from_win(rect);
2569         if (!CGRectIsInfinite(rect))
2570         {
2571             NSRect nsrect = NSRectFromCGRect(rect);
2572             NSScreen* screen;
2574             /* Convert the rectangle from top-down coords to bottom-up. */
2575             [controller flipRect:&nsrect];
2577             clipping = FALSE;
2578             for (screen in [NSScreen screens])
2579             {
2580                 if (!NSContainsRect(nsrect, [screen frame]))
2581                 {
2582                     clipping = TRUE;
2583                     break;
2584                 }
2585             }
2586         }
2588         if (clipping)
2589             ret = [controller startClippingCursor:rect];
2590         else
2591             ret = [controller stopClippingCursor];
2592     });
2594     return ret;
2597 /***********************************************************************
2598  *              macdrv_set_application_icon
2600  * Set the application icon.  The images array contains CGImages.  If
2601  * there are more than one, then they represent different sizes or
2602  * color depths from the icon resource.  If images is NULL or empty,
2603  * restores the default application image.
2604  */
2605 void macdrv_set_application_icon(CFArrayRef images)
2607     NSArray* imageArray = (NSArray*)images;
2609     OnMainThreadAsync(^{
2610         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2611     });
2614 /***********************************************************************
2615  *              macdrv_quit_reply
2616  */
2617 void macdrv_quit_reply(int reply)
2619     OnMainThread(^{
2620         [NSApp replyToApplicationShouldTerminate:reply];
2621     });
2624 /***********************************************************************
2625  *              macdrv_using_input_method
2626  */
2627 int macdrv_using_input_method(void)
2629     __block BOOL ret;
2631     OnMainThread(^{
2632         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2633     });
2635     return ret;
2638 /***********************************************************************
2639  *              macdrv_set_mouse_capture_window
2640  */
2641 void macdrv_set_mouse_capture_window(macdrv_window window)
2643     WineWindow* w = (WineWindow*)window;
2645     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2647     OnMainThread(^{
2648         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2649     });
2652 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2653 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2654 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2656 /***********************************************************************
2657  *              macdrv_create_input_source_list
2658  */
2659 CFArrayRef macdrv_create_input_source_list(void)
2661     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2663     OnMainThread(^{
2664         CFArrayRef input_list;
2665         CFDictionaryRef filter_dict;
2666         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2667         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2668         int i;
2670         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2671                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2672         input_list = TISCreateInputSourceList(filter_dict, false);
2674         for (i = 0; i < CFArrayGetCount(input_list); i++)
2675         {
2676             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2677             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2678             CFDictionaryRef entry;
2679             const void *input_keys[3] = { macdrv_input_source_input_key,
2680                                           macdrv_input_source_type_key,
2681                                           macdrv_input_source_lang_key };
2682             const void *input_values[3];
2684             input_values[0] = input;
2685             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2686             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2688             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2689                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2691             CFArrayAppendValue(ret, entry);
2692             CFRelease(entry);
2693         }
2694         CFRelease(input_list);
2695         CFRelease(filter_dict);
2696     });
2698     return ret;
2701 int macdrv_select_input_source(TISInputSourceRef input_source)
2703     __block int ret = FALSE;
2705     OnMainThread(^{
2706         ret = (TISSelectInputSource(input_source) == noErr);
2707     });
2709     return ret;
2712 void macdrv_set_cocoa_retina_mode(int new_mode)
2714     OnMainThread(^{
2715         [[WineApplicationController sharedController] setRetinaMode:new_mode];
2716     });