api-ms-win-core-comm-l1-1-0: Add dll.
[wine.git] / dlls / winemac.drv / cocoa_app.m
blobefbd6573847218c96237582ec6ef9b1e31e7146c
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 #if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
36 @interface NSWindow (WineAutoTabbingExtensions)
38     + (void) setAllowsAutomaticWindowTabbing:(BOOL)allows;
40 @end
41 #endif
44 /***********************************************************************
45  *              WineLocalizedString
46  *
47  * Look up a localized string by its ID in the dictionary.
48  */
49 static NSString* WineLocalizedString(unsigned int stringID)
51     NSNumber* key = [NSNumber numberWithUnsignedInt:stringID];
52     return [(NSDictionary*)localized_strings objectForKey:key];
56 @implementation WineApplication
58 @synthesize wineController;
60     - (void) sendEvent:(NSEvent*)anEvent
61     {
62         if (![wineController handleEvent:anEvent])
63         {
64             [super sendEvent:anEvent];
65             [wineController didSendEvent:anEvent];
66         }
67     }
69     - (void) setWineController:(WineApplicationController*)newController
70     {
71         wineController = newController;
72         [self setDelegate:wineController];
73     }
75 @end
78 @interface WarpRecord : NSObject
80     CGEventTimestamp timeBefore, timeAfter;
81     CGPoint from, to;
84 @property (nonatomic) CGEventTimestamp timeBefore;
85 @property (nonatomic) CGEventTimestamp timeAfter;
86 @property (nonatomic) CGPoint from;
87 @property (nonatomic) CGPoint to;
89 @end
92 @implementation WarpRecord
94 @synthesize timeBefore, timeAfter, from, to;
96 @end;
99 @interface WineApplicationController ()
101 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
102 @property (copy, nonatomic) NSArray* cursorFrames;
103 @property (retain, nonatomic) NSTimer* cursorTimer;
104 @property (retain, nonatomic) NSCursor* cursor;
105 @property (retain, nonatomic) NSImage* applicationIcon;
106 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
107 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
109     - (void) setupObservations;
110     - (void) applicationDidBecomeActive:(NSNotification *)notification;
112     static void PerformRequest(void *info);
114 @end
117 @implementation WineApplicationController
119     @synthesize keyboardType, lastFlagsChanged;
120     @synthesize applicationIcon;
121     @synthesize cursorFrames, cursorTimer, cursor;
122     @synthesize mouseCaptureWindow;
124     @synthesize clippingCursor;
126     + (void) initialize
127     {
128         if (self == [WineApplicationController class])
129         {
130             NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
131                                       @"", @"NSQuotedKeystrokeBinding",
132                                       @"", @"NSRepeatCountBinding",
133                                       [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
134                                       nil];
135             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
137             if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
138                 [NSWindow setAllowsAutomaticWindowTabbing:NO];
139         }
140     }
142     + (WineApplicationController*) sharedController
143     {
144         static WineApplicationController* sharedController;
145         static dispatch_once_t once;
147         dispatch_once(&once, ^{
148             sharedController = [[self alloc] init];
149         });
151         return sharedController;
152     }
154     - (id) init
155     {
156         self = [super init];
157         if (self != nil)
158         {
159             CFRunLoopSourceContext context = { 0 };
160             context.perform = PerformRequest;
161             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
162             if (!requestSource)
163             {
164                 [self release];
165                 return nil;
166             }
167             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
168             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
170             requests =  [[NSMutableArray alloc] init];
171             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
173             eventQueues = [[NSMutableArray alloc] init];
174             eventQueuesLock = [[NSLock alloc] init];
176             keyWindows = [[NSMutableArray alloc] init];
178             originalDisplayModes = [[NSMutableDictionary alloc] init];
179             latentDisplayModes = [[NSMutableDictionary alloc] init];
181             warpRecords = [[NSMutableArray alloc] init];
183             windowsBeingDragged = [[NSMutableSet alloc] init];
185             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
186                 !keyWindows || !originalDisplayModes || !latentDisplayModes || !warpRecords)
187             {
188                 [self release];
189                 return nil;
190             }
192             [self setupObservations];
194             keyboardType = LMGetKbdType();
196             if ([NSApp isActive])
197                 [self applicationDidBecomeActive:nil];
198         }
199         return self;
200     }
202     - (void) dealloc
203     {
204         [windowsBeingDragged release];
205         [cursor release];
206         [screenFrameCGRects release];
207         [applicationIcon release];
208         [warpRecords release];
209         [cursorTimer release];
210         [cursorFrames release];
211         [latentDisplayModes release];
212         [originalDisplayModes release];
213         [keyWindows release];
214         [eventQueues release];
215         [eventQueuesLock release];
216         if (requestsManipQueue) dispatch_release(requestsManipQueue);
217         [requests release];
218         if (requestSource)
219         {
220             CFRunLoopSourceInvalidate(requestSource);
221             CFRelease(requestSource);
222         }
223         [super dealloc];
224     }
226     - (void) transformProcessToForeground
227     {
228         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
229         {
230             NSMenu* mainMenu;
231             NSMenu* submenu;
232             NSString* bundleName;
233             NSString* title;
234             NSMenuItem* item;
236             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
237             [NSApp activateIgnoringOtherApps:YES];
238 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
239             if (!enable_app_nap && [NSProcessInfo instancesRespondToSelector:@selector(beginActivityWithOptions:reason:)])
240             {
241                 [[[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep
242                                                                 reason:@"Running Windows program"] retain]; // intentional leak
243             }
244 #endif
246             mainMenu = [[[NSMenu alloc] init] autorelease];
248             // Application menu
249             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
250             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
252             if ([bundleName length])
253                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
254             else
255                 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
256             item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
258             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
259                                       action:@selector(hideOtherApplications:)
260                                keyEquivalent:@"h"];
261             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
263             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
264                                       action:@selector(unhideAllApplications:)
265                                keyEquivalent:@""];
267             [submenu addItem:[NSMenuItem separatorItem]];
269             if ([bundleName length])
270                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
271             else
272                 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
273             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
274             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
275             item = [[[NSMenuItem alloc] init] autorelease];
276             [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
277             [item setSubmenu:submenu];
278             [mainMenu addItem:item];
280             // Window menu
281             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
282             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
283                                action:@selector(performMiniaturize:)
284                         keyEquivalent:@""];
285             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
286                                action:@selector(performZoom:)
287                         keyEquivalent:@""];
288             if ([NSWindow instancesRespondToSelector:@selector(toggleFullScreen:)])
289             {
290                 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
291                                           action:@selector(toggleFullScreen:)
292                                    keyEquivalent:@"f"];
293                 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask];
294             }
295             [submenu addItem:[NSMenuItem separatorItem]];
296             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
297                                action:@selector(arrangeInFront:)
298                         keyEquivalent:@""];
299             item = [[[NSMenuItem alloc] init] autorelease];
300             [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
301             [item setSubmenu:submenu];
302             [mainMenu addItem:item];
304             [NSApp setMainMenu:mainMenu];
305             [NSApp setWindowsMenu:submenu];
307             [NSApp setApplicationIconImage:self.applicationIcon];
308         }
309     }
311     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
312     {
313         PerformRequest(NULL);
315         do
316         {
317             if (processEvents)
318             {
319                 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
320                 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
321                                                     untilDate:timeout
322                                                        inMode:NSDefaultRunLoopMode
323                                                       dequeue:YES];
324                 if (event)
325                     [NSApp sendEvent:event];
326                 [pool release];
327             }
328             else
329                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
330         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
332         return *done;
333     }
335     - (BOOL) registerEventQueue:(WineEventQueue*)queue
336     {
337         [eventQueuesLock lock];
338         [eventQueues addObject:queue];
339         [eventQueuesLock unlock];
340         return TRUE;
341     }
343     - (void) unregisterEventQueue:(WineEventQueue*)queue
344     {
345         [eventQueuesLock lock];
346         [eventQueues removeObjectIdenticalTo:queue];
347         [eventQueuesLock unlock];
348     }
350     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
351     {
352         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
353     }
355     - (double) ticksForEventTime:(NSTimeInterval)eventTime
356     {
357         return (eventTime + eventTimeAdjustment) * 1000;
358     }
360     /* Invalidate old focus offers across all queues. */
361     - (void) invalidateGotFocusEvents
362     {
363         WineEventQueue* queue;
365         windowFocusSerial++;
367         [eventQueuesLock lock];
368         for (queue in eventQueues)
369         {
370             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
371                                    forWindow:nil];
372         }
373         [eventQueuesLock unlock];
374     }
376     - (void) windowGotFocus:(WineWindow*)window
377     {
378         macdrv_event* event;
380         [self invalidateGotFocusEvents];
382         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
383         event->window_got_focus.serial = windowFocusSerial;
384         if (triedWindows)
385             event->window_got_focus.tried_windows = [triedWindows retain];
386         else
387             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
388         [window.queue postEvent:event];
389         macdrv_release_event(event);
390     }
392     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
393     {
394         if (event->window_got_focus.serial == windowFocusSerial)
395         {
396             NSMutableArray* windows = [keyWindows mutableCopy];
397             NSNumber* windowNumber;
398             WineWindow* window;
400             for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
401             {
402                 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
403                 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
404                     ![windows containsObject:window])
405                     [windows addObject:window];
406             }
408             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
409             [triedWindows addObject:(WineWindow*)event->window];
410             for (window in windows)
411             {
412                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
413                 {
414                     [window makeKeyWindow];
415                     break;
416                 }
417             }
418             triedWindows = nil;
419             [windows release];
420         }
421     }
423     static BOOL EqualInputSource(TISInputSourceRef source1, TISInputSourceRef source2)
424     {
425         if (!source1 && !source2)
426             return TRUE;
427         if (!source1 || !source2)
428             return FALSE;
429         return CFEqual(source1, source2);
430     }
432     - (void) keyboardSelectionDidChange:(BOOL)force
433     {
434         TISInputSourceRef inputSource, inputSourceLayout;
436         if (!force)
437         {
438             NSTextInputContext* context = [NSTextInputContext currentInputContext];
439             if (!context || ![context client])
440                 return;
441         }
443         inputSource = TISCopyCurrentKeyboardInputSource();
444         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
445         if (!force && EqualInputSource(inputSource, lastKeyboardInputSource) &&
446             EqualInputSource(inputSourceLayout, lastKeyboardLayoutInputSource))
447         {
448             if (inputSource) CFRelease(inputSource);
449             if (inputSourceLayout) CFRelease(inputSourceLayout);
450             return;
451         }
453         if (lastKeyboardInputSource)
454             CFRelease(lastKeyboardInputSource);
455         lastKeyboardInputSource = inputSource;
456         if (lastKeyboardLayoutInputSource)
457             CFRelease(lastKeyboardLayoutInputSource);
458         lastKeyboardLayoutInputSource = inputSourceLayout;
460         inputSourceIsInputMethodValid = FALSE;
462         if (inputSourceLayout)
463         {
464             CFDataRef uchr;
465             uchr = TISGetInputSourceProperty(inputSourceLayout,
466                     kTISPropertyUnicodeKeyLayoutData);
467             if (uchr)
468             {
469                 macdrv_event* event;
470                 WineEventQueue* queue;
472                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
473                 event->keyboard_changed.keyboard_type = self.keyboardType;
474                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
475                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
476                 event->keyboard_changed.input_source = (TISInputSourceRef)CFRetain(inputSource);
478                 if (event->keyboard_changed.uchr)
479                 {
480                     [eventQueuesLock lock];
482                     for (queue in eventQueues)
483                         [queue postEvent:event];
485                     [eventQueuesLock unlock];
486                 }
488                 macdrv_release_event(event);
489             }
490         }
491     }
493     - (void) keyboardSelectionDidChange
494     {
495         [self keyboardSelectionDidChange:NO];
496     }
498     - (void) setKeyboardType:(CGEventSourceKeyboardType)newType
499     {
500         if (newType != keyboardType)
501         {
502             keyboardType = newType;
503             [self keyboardSelectionDidChange:YES];
504         }
505     }
507     - (void) enabledKeyboardInputSourcesChanged
508     {
509         macdrv_layout_list_needs_update = TRUE;
510     }
512     - (CGFloat) primaryScreenHeight
513     {
514         if (!primaryScreenHeightValid)
515         {
516             NSArray* screens = [NSScreen screens];
517             NSUInteger count = [screens count];
518             if (count)
519             {
520                 NSUInteger size;
521                 CGRect* rect;
522                 NSScreen* screen;
524                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
525                 primaryScreenHeightValid = TRUE;
527                 size = count * sizeof(CGRect);
528                 if (!screenFrameCGRects)
529                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
530                 else
531                     [screenFrameCGRects setLength:size];
533                 rect = [screenFrameCGRects mutableBytes];
534                 for (screen in screens)
535                 {
536                     CGRect temp = NSRectToCGRect([screen frame]);
537                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
538                     *rect++ = temp;
539                 }
540             }
541             else
542                 return 1280; /* arbitrary value */
543         }
545         return primaryScreenHeight;
546     }
548     - (NSPoint) flippedMouseLocation:(NSPoint)point
549     {
550         /* This relies on the fact that Cocoa's mouse location points are
551            actually off by one (precisely because they were flipped from
552            Quartz screen coordinates using this same technique). */
553         point.y = [self primaryScreenHeight] - point.y;
554         return point;
555     }
557     - (void) flipRect:(NSRect*)rect
558     {
559         // We don't use -primaryScreenHeight here so there's no chance of having
560         // out-of-date cached info.  This method is called infrequently enough
561         // that getting the screen height each time is not prohibitively expensive.
562         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
563     }
565     - (WineWindow*) frontWineWindow
566     {
567         NSNumber* windowNumber;
568         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
569         {
570             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
571             if ([window isKindOfClass:[WineWindow class]] && [window screen])
572                 return (WineWindow*)window;
573         }
575         return nil;
576     }
578     - (void) adjustWindowLevels:(BOOL)active
579     {
580         NSArray* windowNumbers;
581         NSMutableArray* wineWindows;
582         NSNumber* windowNumber;
583         NSUInteger nextFloatingIndex = 0;
584         __block NSInteger maxLevel = NSIntegerMin;
585         __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
586         __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
587         __block WineWindow* prev = nil;
588         WineWindow* window;
590         if ([NSApp isHidden]) return;
592         windowNumbers = [NSWindow windowNumbersWithOptions:0];
593         wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
595         // For the most part, we rely on the window server's ordering of the windows
596         // to be authoritative.  The one exception is if the "floating" property of
597         // one of the windows has been changed, it may be in the wrong level and thus
598         // in the order.  This method is what's supposed to fix that up.  So build
599         // a list of Wine windows sorted first by floating-ness and then by order
600         // as indicated by the window server.
601         for (windowNumber in windowNumbers)
602         {
603             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
604             if ([window isKindOfClass:[WineWindow class]])
605             {
606                 if (window.floating)
607                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
608                 else
609                     [wineWindows addObject:window];
610             }
611         }
613         NSDisableScreenUpdates();
615         // Go from back to front so that all windows in front of one which is
616         // elevated for full-screen are also elevated.
617         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
618                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
619             WineWindow* window = (WineWindow*)obj;
620             NSInteger origLevel = [window level];
621             NSInteger newLevel = [window minimumLevelForActive:active];
623             if (window.floating)
624             {
625                 if (minFloatingLevel <= maxNonfloatingLevel)
626                     minFloatingLevel = maxNonfloatingLevel + 1;
627                 if (newLevel < minFloatingLevel)
628                     newLevel = minFloatingLevel;
629             }
631             if (newLevel < maxLevel)
632                 newLevel = maxLevel;
633             else
634                 maxLevel = newLevel;
636             if (!window.floating && maxNonfloatingLevel < newLevel)
637                 maxNonfloatingLevel = newLevel;
639             if (newLevel != origLevel)
640             {
641                 [window setLevel:newLevel];
643                 // -setLevel: puts the window at the front of its new level.  If
644                 // we decreased the level, that's good (it was in front of that
645                 // level before, so it should still be now).  But if we increased
646                 // the level, the window should be toward the back (but still
647                 // ahead of the previous windows we did this to).
648                 if (origLevel < newLevel)
649                 {
650                     if (prev)
651                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
652                     else
653                         [window orderBack:nil];
654                 }
655             }
657             prev = window;
658         }];
660         NSEnableScreenUpdates();
662         [wineWindows release];
664         // The above took care of the visible windows on the current space.  That
665         // leaves windows on other spaces, minimized windows, and windows which
666         // are not ordered in.  We want to leave windows on other spaces alone
667         // so the space remains just as they left it (when viewed in Exposé or
668         // Mission Control, for example).  We'll adjust the window levels again
669         // after we switch to another space, anyway.  Windows which aren't
670         // ordered in will be handled when we order them in.  Minimized windows
671         // on the current space should be set to the level they would have gotten
672         // if they were at the front of the windows with the same floating-ness,
673         // because that's where they'll go if/when they are unminimized.  Again,
674         // for good measure we'll adjust window levels again when a window is
675         // unminimized, too.
676         for (window in [NSApp windows])
677         {
678             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
679                 [window isOnActiveSpace])
680             {
681                 NSInteger origLevel = [window level];
682                 NSInteger newLevel = [window minimumLevelForActive:YES];
683                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
685                 if (newLevel < maxLevelForType)
686                     newLevel = maxLevelForType;
688                 if (newLevel != origLevel)
689                     [window setLevel:newLevel];
690             }
691         }
692     }
694     - (void) adjustWindowLevels
695     {
696         [self adjustWindowLevels:[NSApp isActive]];
697     }
699     - (void) updateFullscreenWindows
700     {
701         if (capture_displays_for_fullscreen && [NSApp isActive])
702         {
703             BOOL anyFullscreen = FALSE;
704             NSNumber* windowNumber;
705             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
706             {
707                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
708                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
709                 {
710                     anyFullscreen = TRUE;
711                     break;
712                 }
713             }
715             if (anyFullscreen)
716             {
717                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
718                     displaysCapturedForFullscreen = TRUE;
719             }
720             else if (displaysCapturedForFullscreen)
721             {
722                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
723                     displaysCapturedForFullscreen = FALSE;
724             }
725         }
726     }
728     - (void) activeSpaceDidChange
729     {
730         [self updateFullscreenWindows];
731         [self adjustWindowLevels];
732     }
734     - (void) sendDisplaysChanged:(BOOL)activating
735     {
736         macdrv_event* event;
737         WineEventQueue* queue;
739         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
740         event->displays_changed.activating = activating;
742         [eventQueuesLock lock];
744         // If we're activating, then we just need one of our threads to get the
745         // event, so it can send it directly to the desktop window.  Otherwise,
746         // we need all of the threads to get it because we don't know which owns
747         // the desktop window and only that one will do anything with it.
748         if (activating) event->deliver = 1;
750         for (queue in eventQueues)
751             [queue postEvent:event];
752         [eventQueuesLock unlock];
754         macdrv_release_event(event);
755     }
757     // We can compare two modes directly using CFEqual, but that may require that
758     // they are identical to a level that we don't need.  In particular, when the
759     // OS switches between the integrated and discrete GPUs, the set of display
760     // modes can change in subtle ways.  We're interested in whether two modes
761     // match in their most salient features, even if they aren't identical.
762     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
763     {
764         NSString *encoding1, *encoding2;
765         uint32_t ioflags1, ioflags2, different;
766         double refresh1, refresh2;
768         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
769         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
771 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
772         if (CGDisplayModeGetPixelWidth != NULL &&
773             CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
774         if (CGDisplayModeGetPixelHeight != NULL &&
775             CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
776 #endif
778         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
779         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
780         if (![encoding1 isEqualToString:encoding2]) return FALSE;
782         ioflags1 = CGDisplayModeGetIOFlags(mode1);
783         ioflags2 = CGDisplayModeGetIOFlags(mode2);
784         different = ioflags1 ^ ioflags2;
785         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
786                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
787             return FALSE;
789         refresh1 = CGDisplayModeGetRefreshRate(mode1);
790         if (refresh1 == 0) refresh1 = 60;
791         refresh2 = CGDisplayModeGetRefreshRate(mode2);
792         if (refresh2 == 0) refresh2 = 60;
793         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
795         return TRUE;
796     }
798     - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
799     {
800         NSMutableArray* ret = [NSMutableArray array];
801         NSDictionary* options = nil;
803 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
804         if (&kCGDisplayShowDuplicateLowResolutionModes != NULL)
805             options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:TRUE]
806                                                   forKey:(NSString*)kCGDisplayShowDuplicateLowResolutionModes];
807 #endif
809         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
810         for (id candidateModeObject in modes)
811         {
812             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
813             if ([self mode:candidateMode matchesMode:mode])
814                 [ret addObject:candidateModeObject];
815         }
816         return ret;
817     }
819     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
820     {
821         BOOL ret = FALSE;
822         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
823         CGDisplayModeRef originalMode;
825         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
827         if (originalMode && [self mode:mode matchesMode:originalMode])
828         {
829             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
830             {
831                 CGRestorePermanentDisplayConfiguration();
832                 if (!displaysCapturedForFullscreen)
833                     CGReleaseAllDisplays();
834                 [originalDisplayModes removeAllObjects];
835                 ret = TRUE;
836             }
837             else // ... otherwise, try to restore just the one display
838             {
839                 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
840                 {
841                     mode = (CGDisplayModeRef)modeObject;
842                     if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
843                     {
844                         [originalDisplayModes removeObjectForKey:displayIDKey];
845                         ret = TRUE;
846                         break;
847                     }
848                 }
849             }
850         }
851         else
852         {
853             CGDisplayModeRef currentMode;
854             NSArray* modes;
856             currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
857             if (!currentMode)
858                 currentMode = CGDisplayCopyDisplayMode(displayID);
859             if (!currentMode) // Invalid display ID
860                 return FALSE;
862             if ([self mode:mode matchesMode:currentMode]) // Already there!
863             {
864                 CGDisplayModeRelease(currentMode);
865                 return TRUE;
866             }
868             CGDisplayModeRelease(currentMode);
869             currentMode = NULL;
871             modes = [self modesMatchingMode:mode forDisplay:displayID];
872             if (!modes.count)
873                 return FALSE;
875             [self transformProcessToForeground];
877             BOOL active = [NSApp isActive];
879             if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
880                 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
881             {
882                 if (active)
883                 {
884                     // If we get here, we have the displays captured.  If we don't
885                     // know the original mode of the display, the current mode must
886                     // be the original.  We should re-query the current mode since
887                     // another process could have changed it between when we last
888                     // checked and when we captured the displays.
889                     if (!originalMode)
890                         originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
892                     if (originalMode)
893                     {
894                         for (id modeObject in modes)
895                         {
896                             mode = (CGDisplayModeRef)modeObject;
897                             if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
898                             {
899                                 ret = TRUE;
900                                 break;
901                             }
902                         }
903                     }
904                     if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
905                         [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
906                     else if (![originalDisplayModes count])
907                     {
908                         CGRestorePermanentDisplayConfiguration();
909                         if (!displaysCapturedForFullscreen)
910                             CGReleaseAllDisplays();
911                     }
913                     if (currentMode)
914                         CGDisplayModeRelease(currentMode);
915                 }
916                 else
917                 {
918                     [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
919                     ret = TRUE;
920                 }
921             }
922         }
924         if (ret)
925             [self adjustWindowLevels];
927         return ret;
928     }
930     - (BOOL) areDisplaysCaptured
931     {
932         return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
933     }
935     - (void) updateCursor:(BOOL)force
936     {
937         if (force || lastTargetWindow)
938         {
939             if (clientWantsCursorHidden && !cursorHidden)
940             {
941                 [NSCursor hide];
942                 cursorHidden = TRUE;
943             }
945             if (!cursorIsCurrent)
946             {
947                 [cursor set];
948                 cursorIsCurrent = TRUE;
949             }
951             if (!clientWantsCursorHidden && cursorHidden)
952             {
953                 [NSCursor unhide];
954                 cursorHidden = FALSE;
955             }
956         }
957         else
958         {
959             if (cursorIsCurrent)
960             {
961                 [[NSCursor arrowCursor] set];
962                 cursorIsCurrent = FALSE;
963             }
964             if (cursorHidden)
965             {
966                 [NSCursor unhide];
967                 cursorHidden = FALSE;
968             }
969         }
970     }
972     - (void) hideCursor
973     {
974         if (!clientWantsCursorHidden)
975         {
976             clientWantsCursorHidden = TRUE;
977             [self updateCursor:TRUE];
978         }
979     }
981     - (void) unhideCursor
982     {
983         if (clientWantsCursorHidden)
984         {
985             clientWantsCursorHidden = FALSE;
986             [self updateCursor:FALSE];
987         }
988     }
990     - (void) setCursor:(NSCursor*)newCursor
991     {
992         if (newCursor != cursor)
993         {
994             [cursor release];
995             cursor = [newCursor retain];
996             cursorIsCurrent = FALSE;
997             [self updateCursor:FALSE];
998         }
999     }
1001     - (void) setCursor
1002     {
1003         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
1004         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
1005         CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
1006         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
1007         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
1008         CGPoint hotSpot;
1010         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
1011             hotSpot = CGPointZero;
1012         hotSpot = cgpoint_mac_from_win(hotSpot);
1013         self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1014         [image release];
1015         [self unhideCursor];
1016     }
1018     - (void) nextCursorFrame:(NSTimer*)theTimer
1019     {
1020         NSDictionary* frame;
1021         NSTimeInterval duration;
1022         NSDate* date;
1024         cursorFrame++;
1025         if (cursorFrame >= [cursorFrames count])
1026             cursorFrame = 0;
1027         [self setCursor];
1029         frame = [cursorFrames objectAtIndex:cursorFrame];
1030         duration = [[frame objectForKey:@"duration"] doubleValue];
1031         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1032         [cursorTimer setFireDate:date];
1033     }
1035     - (void) setCursorWithFrames:(NSArray*)frames
1036     {
1037         if (self.cursorFrames == frames)
1038             return;
1040         self.cursorFrames = frames;
1041         cursorFrame = 0;
1042         [cursorTimer invalidate];
1043         self.cursorTimer = nil;
1045         if ([frames count])
1046         {
1047             if ([frames count] > 1)
1048             {
1049                 NSDictionary* frame = [frames objectAtIndex:0];
1050                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
1051                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1052                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1053                                                              interval:1000000
1054                                                                target:self
1055                                                              selector:@selector(nextCursorFrame:)
1056                                                              userInfo:nil
1057                                                               repeats:YES] autorelease];
1058                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1059             }
1061             [self setCursor];
1062         }
1063     }
1065     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1066     {
1067         NSImage* nsimage = nil;
1069         if ([images count])
1070         {
1071             NSSize bestSize = NSZeroSize;
1072             id image;
1074             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1076             for (image in images)
1077             {
1078                 CGImageRef cgimage = (CGImageRef)image;
1079                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1080                 if (imageRep)
1081                 {
1082                     NSSize size = [imageRep size];
1084                     [nsimage addRepresentation:imageRep];
1085                     [imageRep release];
1087                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1088                         bestSize = size;
1089                 }
1090             }
1092             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1093                 [nsimage setSize:bestSize];
1094             else
1095                 nsimage = nil;
1096         }
1098         self.applicationIcon = nsimage;
1099     }
1101     - (void) handleCommandTab
1102     {
1103         if ([NSApp isActive])
1104         {
1105             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1106             NSRunningApplication* app;
1107             NSRunningApplication* otherValidApp = nil;
1109             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1110             {
1111                 NSNumber* displayID;
1112                 for (displayID in originalDisplayModes)
1113                 {
1114                     CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1115                     [latentDisplayModes setObject:(id)mode forKey:displayID];
1116                     CGDisplayModeRelease(mode);
1117                 }
1119                 CGRestorePermanentDisplayConfiguration();
1120                 CGReleaseAllDisplays();
1121                 [originalDisplayModes removeAllObjects];
1122                 displaysCapturedForFullscreen = FALSE;
1123             }
1125             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1126             {
1127                 if (![app isEqual:thisApp] && !app.terminated &&
1128                     app.activationPolicy == NSApplicationActivationPolicyRegular)
1129                 {
1130                     if (!app.hidden)
1131                     {
1132                         // There's another visible app.  Just hide ourselves and let
1133                         // the system activate the other app.
1134                         [NSApp hide:self];
1135                         return;
1136                     }
1138                     if (!otherValidApp)
1139                         otherValidApp = app;
1140                 }
1141             }
1143             // Didn't find a visible GUI app.  Try the Finder or, if that's not
1144             // running, the first hidden GUI app.  If even that doesn't work, we
1145             // just fail to switch and remain the active app.
1146             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1147             if (!app) app = otherValidApp;
1148             [app unhide];
1149             [app activateWithOptions:0];
1150         }
1151     }
1153     /*
1154      * ---------- Cursor clipping methods ----------
1155      *
1156      * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1157      * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1158      * equivalent: CGAssociateMouseAndMouseCursorPosition(false).  For the
1159      * general case, we leverage that.  We disassociate mouse movements from
1160      * the cursor position and then move the cursor manually, keeping it within
1161      * the clipping rectangle.
1162      *
1163      * Moving the cursor manually isn't enough.  We need to modify the event
1164      * stream so that the events have the new location, too.  We need to do
1165      * this at a point before the events enter Cocoa, so that Cocoa will assign
1166      * the correct window to the event.  So, we install a Quartz event tap to
1167      * do that.
1168      *
1169      * Also, there's a complication when we move the cursor.  We use
1170      * CGWarpMouseCursorPosition().  That doesn't generate mouse movement
1171      * events, but the change of cursor position is incorporated into the
1172      * deltas of the next mouse move event.  When the mouse is disassociated
1173      * from the cursor position, we need the deltas to only reflect actual
1174      * device movement, not programmatic changes.  So, the event tap cancels
1175      * out the change caused by our calls to CGWarpMouseCursorPosition().
1176      */
1177     - (void) clipCursorLocation:(CGPoint*)location
1178     {
1179         if (location->x < CGRectGetMinX(cursorClipRect))
1180             location->x = CGRectGetMinX(cursorClipRect);
1181         if (location->y < CGRectGetMinY(cursorClipRect))
1182             location->y = CGRectGetMinY(cursorClipRect);
1183         if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1184             location->x = CGRectGetMaxX(cursorClipRect) - 1;
1185         if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1186             location->y = CGRectGetMaxY(cursorClipRect) - 1;
1187     }
1189     - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1190     {
1191         CGPoint oldLocation;
1193         if (currentLocation)
1194             oldLocation = *currentLocation;
1195         else
1196             oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1198         if (!CGPointEqualToPoint(oldLocation, *newLocation))
1199         {
1200             WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1201             CGError err;
1203             warpRecord.from = oldLocation;
1204             warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1206             /* Actually move the cursor. */
1207             err = CGWarpMouseCursorPosition(*newLocation);
1208             if (err != kCGErrorSuccess)
1209                 return FALSE;
1211             warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1212             *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1214             if (!CGPointEqualToPoint(oldLocation, *newLocation))
1215             {
1216                 warpRecord.to = *newLocation;
1217                 [warpRecords addObject:warpRecord];
1218             }
1219         }
1221         return TRUE;
1222     }
1224     - (BOOL) isMouseMoveEventType:(CGEventType)type
1225     {
1226         switch(type)
1227         {
1228         case kCGEventMouseMoved:
1229         case kCGEventLeftMouseDragged:
1230         case kCGEventRightMouseDragged:
1231         case kCGEventOtherMouseDragged:
1232             return TRUE;
1233         }
1235         return FALSE;
1236     }
1238     - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1239     {
1240         int warpsFinished = 0;
1241         for (WarpRecord* warpRecord in warpRecords)
1242         {
1243             if (warpRecord.timeAfter < eventTime ||
1244                 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1245                 warpsFinished++;
1246             else
1247                 break;
1248         }
1250         return warpsFinished;
1251     }
1253     - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1254                                 type:(CGEventType)type
1255                                event:(CGEventRef)event
1256     {
1257         CGEventTimestamp eventTime;
1258         CGPoint eventLocation, cursorLocation;
1260         if (type == kCGEventTapDisabledByUserInput)
1261             return event;
1262         if (type == kCGEventTapDisabledByTimeout)
1263         {
1264             CGEventTapEnable(cursorClippingEventTap, TRUE);
1265             return event;
1266         }
1268         if (!clippingCursor)
1269             return event;
1271         eventTime = CGEventGetTimestamp(event);
1272         lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1274         eventLocation = CGEventGetLocation(event);
1276         cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1278         if ([self isMouseMoveEventType:type])
1279         {
1280             double deltaX, deltaY;
1281             int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1282             int i;
1284             deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1285             deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1287             for (i = 0; i < warpsFinished; i++)
1288             {
1289                 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1290                 deltaX -= warpRecord.to.x - warpRecord.from.x;
1291                 deltaY -= warpRecord.to.y - warpRecord.from.y;
1292                 [warpRecords removeObjectAtIndex:0];
1293             }
1295             if (warpsFinished)
1296             {
1297                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1298                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1299             }
1301             synthesizedLocation.x += deltaX;
1302             synthesizedLocation.y += deltaY;
1303         }
1305         // If the event is destined for another process, don't clip it.  This may
1306         // happen if the user activates Exposé or Mission Control.  In that case,
1307         // our app does not resign active status, so clipping is still in effect,
1308         // but the cursor should not actually be clipped.
1309         //
1310         // In addition, the fact that mouse moves may have been delivered to a
1311         // different process means we have to treat the next one we receive as
1312         // absolute rather than relative.
1313         if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1314             [self clipCursorLocation:&synthesizedLocation];
1315         else
1316             lastSetCursorPositionTime = lastEventTapEventTime;
1318         [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1319         if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1320             CGEventSetLocation(event, synthesizedLocation);
1322         return event;
1323     }
1325     CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1326                                        CGEventRef event, void *refcon)
1327     {
1328         WineApplicationController* controller = refcon;
1329         return [controller eventTapWithProxy:proxy type:type event:event];
1330     }
1332     - (BOOL) installEventTap
1333     {
1334         ProcessSerialNumber psn;
1335         OSErr err;
1336         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
1337                            CGEventMaskBit(kCGEventLeftMouseUp)          |
1338                            CGEventMaskBit(kCGEventRightMouseDown)       |
1339                            CGEventMaskBit(kCGEventRightMouseUp)         |
1340                            CGEventMaskBit(kCGEventMouseMoved)           |
1341                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
1342                            CGEventMaskBit(kCGEventRightMouseDragged)    |
1343                            CGEventMaskBit(kCGEventOtherMouseDown)       |
1344                            CGEventMaskBit(kCGEventOtherMouseUp)         |
1345                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
1346                            CGEventMaskBit(kCGEventScrollWheel);
1347         CFRunLoopSourceRef source;
1348         void* appServices;
1349         OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1351         if (cursorClippingEventTap)
1352             return TRUE;
1354         // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1355         // framework with dlsym() because the Win32 function of the same name
1356         // obscures it.
1357         appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1358         if (!appServices)
1359             return FALSE;
1361         pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1362         if (!pGetCurrentProcess)
1363         {
1364             dlclose(appServices);
1365             return FALSE;
1366         }
1368         err = pGetCurrentProcess(&psn);
1369         dlclose(appServices);
1370         if (err != noErr)
1371             return FALSE;
1373         // We create an annotated session event tap rather than a process-specific
1374         // event tap because we need to programmatically move the cursor even when
1375         // mouse moves are directed to other processes.  We disable our tap when
1376         // other processes are active, but things like Exposé are handled by other
1377         // processes even when we remain active.
1378         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1379             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1380         if (!cursorClippingEventTap)
1381             return FALSE;
1383         CGEventTapEnable(cursorClippingEventTap, FALSE);
1385         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1386         if (!source)
1387         {
1388             CFRelease(cursorClippingEventTap);
1389             cursorClippingEventTap = NULL;
1390             return FALSE;
1391         }
1393         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1394         CFRelease(source);
1395         return TRUE;
1396     }
1398     - (BOOL) setCursorPosition:(CGPoint)pos
1399     {
1400         BOOL ret;
1402         if ([windowsBeingDragged count])
1403             ret = FALSE;
1404         else if (clippingCursor)
1405         {
1406             [self clipCursorLocation:&pos];
1408             ret = [self warpCursorTo:&pos from:NULL];
1409             synthesizedLocation = pos;
1410             if (ret)
1411             {
1412                 // We want to discard mouse-move events that have already been
1413                 // through the event tap, because it's too late to account for
1414                 // the setting of the cursor position with them.  However, the
1415                 // events that may be queued with times after that but before
1416                 // the above warp can still be used.  So, use the last event
1417                 // tap event time so that -sendEvent: doesn't discard them.
1418                 lastSetCursorPositionTime = lastEventTapEventTime;
1419             }
1420         }
1421         else
1422         {
1423             // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1424             // the mouse from the cursor position for 0.25 seconds.  This means
1425             // that mouse movement during that interval doesn't move the cursor
1426             // and events carry a constant location (the warped-to position)
1427             // even though they have delta values.  For apps which warp the
1428             // cursor frequently (like after every mouse move), this makes
1429             // cursor movement horribly laggy and jerky, as only a fraction of
1430             // mouse move events have any effect.
1431             //
1432             // On some versions of OS X, it's sufficient to forcibly reassociate
1433             // the mouse and cursor position.  On others, it's necessary to set
1434             // the local events suppression interval to 0 for the warp.  That's
1435             // deprecated, but I'm not aware of any other way.  For good
1436             // measure, we do both.
1437             CGSetLocalEventsSuppressionInterval(0);
1438             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1439             CGSetLocalEventsSuppressionInterval(0.25);
1440             if (ret)
1441             {
1442                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1444                 CGAssociateMouseAndMouseCursorPosition(true);
1445             }
1446         }
1448         if (ret)
1449         {
1450             WineEventQueue* queue;
1452             // Discard all pending mouse move events.
1453             [eventQueuesLock lock];
1454             for (queue in eventQueues)
1455             {
1456                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1457                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1458                                        forWindow:nil];
1459                 [queue resetMouseEventPositions:pos];
1460             }
1461             [eventQueuesLock unlock];
1462         }
1464         return ret;
1465     }
1467     - (void) activateCursorClipping
1468     {
1469         if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1470         {
1471             CGEventTapEnable(cursorClippingEventTap, TRUE);
1472             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1473         }
1474     }
1476     - (void) deactivateCursorClipping
1477     {
1478         if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1479         {
1480             CGEventTapEnable(cursorClippingEventTap, FALSE);
1481             [warpRecords removeAllObjects];
1482             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1483         }
1484     }
1486     - (void) updateCursorClippingState
1487     {
1488         if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1489             [self activateCursorClipping];
1490         else
1491             [self deactivateCursorClipping];
1492     }
1494     - (void) updateWindowsForCursorClipping
1495     {
1496         WineWindow* window;
1497         for (window in [NSApp windows])
1498         {
1499             if ([window isKindOfClass:[WineWindow class]])
1500                 [window updateForCursorClipping];
1501         }
1502     }
1504     - (BOOL) startClippingCursor:(CGRect)rect
1505     {
1506         CGError err;
1508         if (!cursorClippingEventTap && ![self installEventTap])
1509             return FALSE;
1511         if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1512             CGEventTapIsEnabled(cursorClippingEventTap))
1513             return TRUE;
1515         err = CGAssociateMouseAndMouseCursorPosition(false);
1516         if (err != kCGErrorSuccess)
1517             return FALSE;
1519         clippingCursor = TRUE;
1520         cursorClipRect = rect;
1521         [self updateCursorClippingState];
1522         [self updateWindowsForCursorClipping];
1524         return TRUE;
1525     }
1527     - (BOOL) stopClippingCursor
1528     {
1529         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1530         if (err != kCGErrorSuccess)
1531             return FALSE;
1533         clippingCursor = FALSE;
1534         [self updateCursorClippingState];
1535         [self updateWindowsForCursorClipping];
1537         return TRUE;
1538     }
1540     - (BOOL) isKeyPressed:(uint16_t)keyCode
1541     {
1542         int bits = sizeof(pressedKeyCodes[0]) * 8;
1543         int index = keyCode / bits;
1544         uint32_t mask = 1 << (keyCode % bits);
1545         return (pressedKeyCodes[index] & mask) != 0;
1546     }
1548     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1549     {
1550         int bits = sizeof(pressedKeyCodes[0]) * 8;
1551         int index = keyCode / bits;
1552         uint32_t mask = 1 << (keyCode % bits);
1553         if (pressed)
1554             pressedKeyCodes[index] |= mask;
1555         else
1556             pressedKeyCodes[index] &= ~mask;
1557     }
1559     - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1560     {
1561         if (dragged)
1562             [windowsBeingDragged addObject:window];
1563         else
1564             [windowsBeingDragged removeObject:window];
1565         [self updateCursorClippingState];
1566     }
1568     - (void) windowWillOrderOut:(WineWindow*)window
1569     {
1570         if ([windowsBeingDragged containsObject:window])
1571         {
1572             [self window:window isBeingDragged:NO];
1574             macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1575             [window.queue postEvent:event];
1576             macdrv_release_event(event);
1577         }
1578     }
1580     - (void) handleMouseMove:(NSEvent*)anEvent
1581     {
1582         WineWindow* targetWindow;
1583         BOOL drag = [anEvent type] != NSMouseMoved;
1585         if ([windowsBeingDragged count])
1586             targetWindow = nil;
1587         else if (mouseCaptureWindow)
1588             targetWindow = mouseCaptureWindow;
1589         else if (drag)
1590             targetWindow = (WineWindow*)[anEvent window];
1591         else
1592         {
1593             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1594                event indicates its window is the main window, even if the cursor is
1595                over a different window.  Find the actual WineWindow that is under the
1596                cursor and post the event as being for that window. */
1597             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1598             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1599             NSInteger windowUnderNumber;
1601             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1602                                   belowWindowWithWindowNumber:0];
1603             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1604             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1605                 targetWindow = nil;
1606         }
1608         if ([targetWindow isKindOfClass:[WineWindow class]])
1609         {
1610             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1611             macdrv_event* event;
1612             BOOL absolute;
1614             // If we recently warped the cursor (other than in our cursor-clipping
1615             // event tap), discard mouse move events until we see an event which is
1616             // later than that time.
1617             if (lastSetCursorPositionTime)
1618             {
1619                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1620                     return;
1622                 lastSetCursorPositionTime = 0;
1623                 forceNextMouseMoveAbsolute = TRUE;
1624             }
1626             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1627             {
1628                 absolute = TRUE;
1629                 forceNextMouseMoveAbsolute = FALSE;
1630             }
1631             else
1632             {
1633                 // Send absolute move events if the cursor is in the interior of
1634                 // its range.  Only send relative moves if the cursor is pinned to
1635                 // the boundaries of where it can go.  We compute the position
1636                 // that's one additional point in the direction of movement.  If
1637                 // that is outside of the clipping rect or desktop region (the
1638                 // union of the screen frames), then we figure the cursor would
1639                 // have moved outside if it could but it was pinned.
1640                 CGPoint computedPoint = point;
1641                 CGFloat deltaX = [anEvent deltaX];
1642                 CGFloat deltaY = [anEvent deltaY];
1644                 if (deltaX > 0.001)
1645                     computedPoint.x++;
1646                 else if (deltaX < -0.001)
1647                     computedPoint.x--;
1649                 if (deltaY > 0.001)
1650                     computedPoint.y++;
1651                 else if (deltaY < -0.001)
1652                     computedPoint.y--;
1654                 // Assume cursor is pinned for now
1655                 absolute = FALSE;
1656                 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1657                 {
1658                     const CGRect* rects;
1659                     NSUInteger count, i;
1661                     // Caches screenFrameCGRects if necessary
1662                     [self primaryScreenHeight];
1664                     rects = [screenFrameCGRects bytes];
1665                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1667                     for (i = 0; i < count; i++)
1668                     {
1669                         if (CGRectContainsPoint(rects[i], computedPoint))
1670                         {
1671                             absolute = TRUE;
1672                             break;
1673                         }
1674                     }
1675                 }
1676             }
1678             if (absolute)
1679             {
1680                 if (clippingCursor)
1681                     [self clipCursorLocation:&point];
1682                 point = cgpoint_win_from_mac(point);
1684                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1685                 event->mouse_moved.x = floor(point.x);
1686                 event->mouse_moved.y = floor(point.y);
1688                 mouseMoveDeltaX = 0;
1689                 mouseMoveDeltaY = 0;
1690             }
1691             else
1692             {
1693                 double scale = retina_on ? 2 : 1;
1695                 /* Add event delta to accumulated delta error */
1696                 /* deltaY is already flipped */
1697                 mouseMoveDeltaX += [anEvent deltaX];
1698                 mouseMoveDeltaY += [anEvent deltaY];
1700                 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1701                 event->mouse_moved.x = mouseMoveDeltaX * scale;
1702                 event->mouse_moved.y = mouseMoveDeltaY * scale;
1704                 /* Keep the remainder after integer truncation. */
1705                 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1706                 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1707             }
1709             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1710             {
1711                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1712                 event->mouse_moved.drag = drag;
1714                 [targetWindow.queue postEvent:event];
1715             }
1717             macdrv_release_event(event);
1719             lastTargetWindow = targetWindow;
1720         }
1721         else
1722             lastTargetWindow = nil;
1724         [self updateCursor:FALSE];
1725     }
1727     - (void) handleMouseButton:(NSEvent*)theEvent
1728     {
1729         WineWindow* window = (WineWindow*)[theEvent window];
1730         NSEventType type = [theEvent type];
1731         WineWindow* windowBroughtForward = nil;
1732         BOOL process = FALSE;
1734         if ([window isKindOfClass:[WineWindow class]] &&
1735             type == NSLeftMouseDown &&
1736             ![theEvent wine_commandKeyDown])
1737         {
1738             NSWindowButton windowButton;
1740             windowBroughtForward = window;
1742             /* Any left-click on our window anyplace other than the close or
1743                minimize buttons will bring it forward. */
1744             for (windowButton = NSWindowCloseButton;
1745                  windowButton <= NSWindowMiniaturizeButton;
1746                  windowButton++)
1747             {
1748                 NSButton* button = [window standardWindowButton:windowButton];
1749                 if (button)
1750                 {
1751                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1752                     if ([button mouse:point inRect:[button bounds]])
1753                     {
1754                         windowBroughtForward = nil;
1755                         break;
1756                     }
1757                 }
1758             }
1759         }
1761         if ([windowsBeingDragged count])
1762             window = nil;
1763         else if (mouseCaptureWindow)
1764             window = mouseCaptureWindow;
1766         if ([window isKindOfClass:[WineWindow class]])
1767         {
1768             BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1769             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1771             if (clippingCursor)
1772                 [self clipCursorLocation:&pt];
1774             if (pressed)
1775             {
1776                 if (mouseCaptureWindow)
1777                     process = TRUE;
1778                 else
1779                 {
1780                     // Test if the click was in the window's content area.
1781                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1782                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1783                     process = NSMouseInRect(nspoint, contentRect, NO);
1784                     if (process && [window styleMask] & NSResizableWindowMask)
1785                     {
1786                         // Ignore clicks in the grow box (resize widget).
1787                         HIPoint origin = { 0, 0 };
1788                         HIThemeGrowBoxDrawInfo info = { 0 };
1789                         HIRect bounds;
1790                         OSStatus status;
1792                         info.kind = kHIThemeGrowBoxKindNormal;
1793                         info.direction = kThemeGrowRight | kThemeGrowDown;
1794                         if ([window styleMask] & NSUtilityWindowMask)
1795                             info.size = kHIThemeGrowBoxSizeSmall;
1796                         else
1797                             info.size = kHIThemeGrowBoxSizeNormal;
1799                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1800                         if (status == noErr)
1801                         {
1802                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1803                                                         NSMinY(contentRect),
1804                                                         bounds.size.width,
1805                                                         bounds.size.height);
1806                             process = !NSMouseInRect(nspoint, growBox, NO);
1807                         }
1808                     }
1809                 }
1810                 if (process)
1811                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1812             }
1813             else
1814             {
1815                 NSEventType downType = type - 1;
1816                 NSUInteger downMask = NSEventMaskFromType(downType);
1817                 process = (unmatchedMouseDowns & downMask) != 0;
1818                 unmatchedMouseDowns &= ~downMask;
1819             }
1821             if (process)
1822             {
1823                 macdrv_event* event;
1825                 pt = cgpoint_win_from_mac(pt);
1827                 event = macdrv_create_event(MOUSE_BUTTON, window);
1828                 event->mouse_button.button = [theEvent buttonNumber];
1829                 event->mouse_button.pressed = pressed;
1830                 event->mouse_button.x = floor(pt.x);
1831                 event->mouse_button.y = floor(pt.y);
1832                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1834                 [window.queue postEvent:event];
1836                 macdrv_release_event(event);
1837             }
1838         }
1840         if (windowBroughtForward)
1841         {
1842             WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1843             NSInteger ancestorNumber = [ancestor windowNumber];
1844             NSInteger ancestorLevel = [ancestor level];
1846             for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1847             {
1848                 NSInteger windowNumber = [windowNumberObject integerValue];
1849                 if (windowNumber == ancestorNumber)
1850                     break;
1851                 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1852                 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1853                     [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1854                 {
1855                     [ancestor postBroughtForwardEvent];
1856                     break;
1857                 }
1858             }
1859             if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate)
1860                 [self windowGotFocus:windowBroughtForward];
1861         }
1863         // Since mouse button events deliver absolute cursor position, the
1864         // accumulating delta from move events is invalidated.  Make sure
1865         // next mouse move event starts over from an absolute baseline.
1866         // Also, it's at least possible that the title bar widgets (e.g. close
1867         // button, etc.) could enter an internal event loop on a mouse down that
1868         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1869         // dragged events and, after that, any notion of the cursor position
1870         // computed from accumulating deltas would be wrong.
1871         forceNextMouseMoveAbsolute = TRUE;
1872     }
1874     - (void) handleScrollWheel:(NSEvent*)theEvent
1875     {
1876         WineWindow* window;
1878         if (mouseCaptureWindow)
1879             window = mouseCaptureWindow;
1880         else
1881             window = (WineWindow*)[theEvent window];
1883         if ([window isKindOfClass:[WineWindow class]])
1884         {
1885             CGEventRef cgevent = [theEvent CGEvent];
1886             CGPoint pt = CGEventGetLocation(cgevent);
1887             BOOL process;
1889             if (clippingCursor)
1890                 [self clipCursorLocation:&pt];
1892             if (mouseCaptureWindow)
1893                 process = TRUE;
1894             else
1895             {
1896                 // Only process the event if it was in the window's content area.
1897                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1898                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1899                 process = NSMouseInRect(nspoint, contentRect, NO);
1900             }
1902             if (process)
1903             {
1904                 macdrv_event* event;
1905                 double x, y;
1906                 BOOL continuous = FALSE;
1908                 pt = cgpoint_win_from_mac(pt);
1910                 event = macdrv_create_event(MOUSE_SCROLL, window);
1911                 event->mouse_scroll.x = floor(pt.x);
1912                 event->mouse_scroll.y = floor(pt.y);
1913                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1915                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1916                 {
1917                     continuous = TRUE;
1919                     /* Continuous scroll wheel events come from high-precision scrolling
1920                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1921                        For these, we can get more precise data from the CGEvent API. */
1922                     /* Axis 1 is vertical, axis 2 is horizontal. */
1923                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1924                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1925                 }
1926                 else
1927                 {
1928                     double pixelsPerLine = 10;
1929                     CGEventSourceRef source;
1931                     /* The non-continuous values are in units of "lines", not pixels. */
1932                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1933                     {
1934                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1935                         CFRelease(source);
1936                     }
1938                     x = pixelsPerLine * [theEvent deltaX];
1939                     y = pixelsPerLine * [theEvent deltaY];
1940                 }
1942                 /* Mac: negative is right or down, positive is left or up.
1943                    Win32: negative is left or down, positive is right or up.
1944                    So, negate the X scroll value to translate. */
1945                 x = -x;
1947                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1948                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1949                    6 times the pixel value. */
1950                 x *= 6;
1951                 y *= 6;
1953                 if (use_precise_scrolling)
1954                 {
1955                     event->mouse_scroll.x_scroll = x;
1956                     event->mouse_scroll.y_scroll = y;
1958                     if (!continuous)
1959                     {
1960                         /* For non-continuous "clicky" wheels, if there was any motion, make
1961                            sure there was at least WHEEL_DELTA motion.  This is so, at slow
1962                            speeds where the system's acceleration curve is actually reducing the
1963                            scroll distance, the user is sure to get some action out of each click.
1964                            For example, this is important for rotating though weapons in a
1965                            first-person shooter. */
1966                         if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1967                             event->mouse_scroll.x_scroll = 120;
1968                         else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1969                             event->mouse_scroll.x_scroll = -120;
1971                         if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1972                             event->mouse_scroll.y_scroll = 120;
1973                         else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1974                             event->mouse_scroll.y_scroll = -120;
1975                     }
1976                 }
1977                 else
1978                 {
1979                     /* If it's been a while since the last scroll event or if the scrolling has
1980                        reversed direction, reset the accumulated scroll value. */
1981                     if ([theEvent timestamp] - lastScrollTime > 1)
1982                         accumScrollX = accumScrollY = 0;
1983                     else
1984                     {
1985                         /* The accumulated scroll value is in the opposite direction/sign of the last
1986                            scroll.  That's because it's the "debt" resulting from over-scrolling in
1987                            that direction.  We accumulate by adding in the scroll amount and then, if
1988                            it has the same sign as the scroll value, we subtract any whole or partial
1989                            WHEEL_DELTAs, leaving it 0 or the opposite sign.  So, the user switched
1990                            scroll direction if the accumulated debt and the new scroll value have the
1991                            same sign. */
1992                         if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1993                             accumScrollX = 0;
1994                         if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1995                             accumScrollY = 0;
1996                     }
1997                     lastScrollTime = [theEvent timestamp];
1999                     accumScrollX += x;
2000                     accumScrollY += y;
2002                     if (accumScrollX > 0 && x > 0)
2003                         event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
2004                     if (accumScrollX < 0 && x < 0)
2005                         event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
2006                     if (accumScrollY > 0 && y > 0)
2007                         event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
2008                     if (accumScrollY < 0 && y < 0)
2009                         event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
2011                     accumScrollX -= event->mouse_scroll.x_scroll;
2012                     accumScrollY -= event->mouse_scroll.y_scroll;
2013                 }
2015                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
2016                     [window.queue postEvent:event];
2018                 macdrv_release_event(event);
2020                 // Since scroll wheel events deliver absolute cursor position, the
2021                 // accumulating delta from move events is invalidated.  Make sure next
2022                 // mouse move event starts over from an absolute baseline.
2023                 forceNextMouseMoveAbsolute = TRUE;
2024             }
2025         }
2026     }
2028     // Returns TRUE if the event was handled and caller should do nothing more
2029     // with it.  Returns FALSE if the caller should process it as normal and
2030     // then call -didSendEvent:.
2031     - (BOOL) handleEvent:(NSEvent*)anEvent
2032     {
2033         BOOL ret = FALSE;
2034         NSEventType type = [anEvent type];
2036         if (type == NSFlagsChanged)
2037             self.lastFlagsChanged = anEvent;
2038         else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
2039                  type == NSRightMouseDragged || type == NSOtherMouseDragged)
2040         {
2041             [self handleMouseMove:anEvent];
2042             ret = mouseCaptureWindow && ![windowsBeingDragged count];
2043         }
2044         else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
2045                  type == NSRightMouseDown || type == NSRightMouseUp ||
2046                  type == NSOtherMouseDown || type == NSOtherMouseUp)
2047         {
2048             [self handleMouseButton:anEvent];
2049             ret = mouseCaptureWindow && ![windowsBeingDragged count];
2050         }
2051         else if (type == NSScrollWheel)
2052         {
2053             [self handleScrollWheel:anEvent];
2054             ret = mouseCaptureWindow != nil;
2055         }
2056         else if (type == NSKeyDown)
2057         {
2058             // -[NSApplication sendEvent:] seems to consume presses of the Help
2059             // key (Insert key on PC keyboards), so we have to bypass it and
2060             // send the event directly to the window.
2061             if (anEvent.keyCode == kVK_Help)
2062             {
2063                 [anEvent.window sendEvent:anEvent];
2064                 ret = TRUE;
2065             }
2066         }
2067         else if (type == NSKeyUp)
2068         {
2069             uint16_t keyCode = [anEvent keyCode];
2070             if ([self isKeyPressed:keyCode])
2071             {
2072                 WineWindow* window = (WineWindow*)[anEvent window];
2073                 [self noteKey:keyCode pressed:FALSE];
2074                 if ([window isKindOfClass:[WineWindow class]])
2075                     [window postKeyEvent:anEvent];
2076             }
2077         }
2078         else if (type == NSAppKitDefined)
2079         {
2080             short subtype = [anEvent subtype];
2082             // These subtypes are not documented but they appear to mean
2083             // "a window is being dragged" and "a window is no longer being
2084             // dragged", respectively.
2085             if (subtype == 20 || subtype == 21)
2086             {
2087                 WineWindow* window = (WineWindow*)[anEvent window];
2088                 if ([window isKindOfClass:[WineWindow class]])
2089                 {
2090                     macdrv_event* event;
2091                     int eventType;
2093                     if (subtype == 20)
2094                     {
2095                         [windowsBeingDragged addObject:window];
2096                         eventType = WINDOW_DRAG_BEGIN;
2097                     }
2098                     else
2099                     {
2100                         [windowsBeingDragged removeObject:window];
2101                         eventType = WINDOW_DRAG_END;
2102                     }
2103                     [self updateCursorClippingState];
2105                     event = macdrv_create_event(eventType, window);
2106                     if (eventType == WINDOW_DRAG_BEGIN)
2107                         event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
2108                     [window.queue postEvent:event];
2109                     macdrv_release_event(event);
2110                 }
2111             }
2112         }
2114         return ret;
2115     }
2117     - (void) didSendEvent:(NSEvent*)anEvent
2118     {
2119         NSEventType type = [anEvent type];
2121         if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
2122         {
2123             NSUInteger modifiers = [anEvent modifierFlags];
2124             if ((modifiers & NSCommandKeyMask) &&
2125                 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
2126             {
2127                 // Command-Tab and Command-Shift-Tab would normally be intercepted
2128                 // by the system to switch applications.  If we're seeing it, it's
2129                 // presumably because we've captured the displays, preventing
2130                 // normal application switching.  Do it manually.
2131                 [self handleCommandTab];
2132             }
2133         }
2134     }
2136     - (void) setupObservations
2137     {
2138         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2139         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
2140         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
2142         [nc addObserverForName:NSWindowDidBecomeKeyNotification
2143                         object:nil
2144                          queue:nil
2145                     usingBlock:^(NSNotification *note){
2146             NSWindow* window = [note object];
2147             [keyWindows removeObjectIdenticalTo:window];
2148             [keyWindows insertObject:window atIndex:0];
2149         }];
2151         [nc addObserverForName:NSWindowWillCloseNotification
2152                         object:nil
2153                          queue:[NSOperationQueue mainQueue]
2154                     usingBlock:^(NSNotification *note){
2155             NSWindow* window = [note object];
2156             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
2157                 return;
2158             [keyWindows removeObjectIdenticalTo:window];
2159             if (window == lastTargetWindow)
2160                 lastTargetWindow = nil;
2161             if (window == self.mouseCaptureWindow)
2162                 self.mouseCaptureWindow = nil;
2163             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
2164             {
2165                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
2166                     [self updateFullscreenWindows];
2167                 });
2168             }
2169             [windowsBeingDragged removeObject:window];
2170             [self updateCursorClippingState];
2171         }];
2173         [nc addObserver:self
2174                selector:@selector(keyboardSelectionDidChange)
2175                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
2176                  object:nil];
2178         /* The above notification isn't sent unless the NSTextInputContext
2179            class has initialized itself.  Poke it. */
2180         [NSTextInputContext self];
2182         [wsnc addObserver:self
2183                  selector:@selector(activeSpaceDidChange)
2184                      name:NSWorkspaceActiveSpaceDidChangeNotification
2185                    object:nil];
2187         [nc addObserver:self
2188                selector:@selector(releaseMouseCapture)
2189                    name:NSMenuDidBeginTrackingNotification
2190                  object:nil];
2192         [dnc        addObserver:self
2193                        selector:@selector(releaseMouseCapture)
2194                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2195                          object:nil
2196              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2198         [dnc addObserver:self
2199                 selector:@selector(enabledKeyboardInputSourcesChanged)
2200                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2201                   object:nil];
2202     }
2204     - (BOOL) inputSourceIsInputMethod
2205     {
2206         if (!inputSourceIsInputMethodValid)
2207         {
2208             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2209             if (inputSource)
2210             {
2211                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2212                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2213                 CFRelease(inputSource);
2214             }
2215             else
2216                 inputSourceIsInputMethod = FALSE;
2217             inputSourceIsInputMethodValid = TRUE;
2218         }
2220         return inputSourceIsInputMethod;
2221     }
2223     - (void) releaseMouseCapture
2224     {
2225         // This might be invoked on a background thread by the distributed
2226         // notification center.  Shunt it to the main thread.
2227         if (![NSThread isMainThread])
2228         {
2229             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2230             return;
2231         }
2233         if (mouseCaptureWindow)
2234         {
2235             macdrv_event* event;
2237             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2238             [mouseCaptureWindow.queue postEvent:event];
2239             macdrv_release_event(event);
2240         }
2241     }
2243     - (void) unminimizeWindowIfNoneVisible
2244     {
2245         if (![self frontWineWindow])
2246         {
2247             for (WineWindow* window in [NSApp windows])
2248             {
2249                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2250                 {
2251                     [window deminiaturize:self];
2252                     break;
2253                 }
2254             }
2255         }
2256     }
2258     - (void) setRetinaMode:(int)mode
2259     {
2260         retina_on = mode;
2262         if (clippingCursor)
2263         {
2264             double scale = mode ? 0.5 : 2.0;
2265             cursorClipRect.origin.x *= scale;
2266             cursorClipRect.origin.y *= scale;
2267             cursorClipRect.size.width *= scale;
2268             cursorClipRect.size.height *= scale;
2269         }
2271         for (WineWindow* window in [NSApp windows])
2272         {
2273             if ([window isKindOfClass:[WineWindow class]])
2274                 [window setRetinaMode:mode];
2275         }
2276     }
2279     /*
2280      * ---------- NSApplicationDelegate methods ----------
2281      */
2282     - (void)applicationDidBecomeActive:(NSNotification *)notification
2283     {
2284         NSNumber* displayID;
2285         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2287         latentDisplayModes = [[NSMutableDictionary alloc] init];
2288         for (displayID in modesToRealize)
2289         {
2290             CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2291             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2292         }
2294         [self updateCursorClippingState];
2296         [self updateFullscreenWindows];
2297         [self adjustWindowLevels:YES];
2299         if (beenActive)
2300             [self unminimizeWindowIfNoneVisible];
2301         beenActive = TRUE;
2303         // If a Wine process terminates abruptly while it has the display captured
2304         // and switched to a different resolution, Mac OS X will uncapture the
2305         // displays and switch their resolutions back.  However, the other Wine
2306         // processes won't have their notion of the desktop rect changed back.
2307         // This can lead them to refuse to draw or acknowledge clicks in certain
2308         // portions of their windows.
2309         //
2310         // To solve this, we synthesize a displays-changed event whenever we're
2311         // activated.  This will provoke a re-synchronization of Wine's notion of
2312         // the desktop rect with the actual state.
2313         [self sendDisplaysChanged:TRUE];
2315         // The cursor probably moved while we were inactive.  Accumulated mouse
2316         // movement deltas are invalidated.  Make sure the next mouse move event
2317         // starts over from an absolute baseline.
2318         forceNextMouseMoveAbsolute = TRUE;
2319     }
2321     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2322     {
2323         primaryScreenHeightValid = FALSE;
2324         [self sendDisplaysChanged:FALSE];
2325         [self adjustWindowLevels];
2327         // When the display configuration changes, the cursor position may jump.
2328         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2329         // mouse move event starts over from an absolute baseline.
2330         forceNextMouseMoveAbsolute = TRUE;
2331     }
2333     - (void)applicationDidResignActive:(NSNotification *)notification
2334     {
2335         macdrv_event* event;
2336         WineEventQueue* queue;
2338         [self updateCursorClippingState];
2340         [self invalidateGotFocusEvents];
2342         event = macdrv_create_event(APP_DEACTIVATED, nil);
2344         [eventQueuesLock lock];
2345         for (queue in eventQueues)
2346             [queue postEvent:event];
2347         [eventQueuesLock unlock];
2349         macdrv_release_event(event);
2351         [self releaseMouseCapture];
2352     }
2354     - (void) applicationDidUnhide:(NSNotification*)aNotification
2355     {
2356         [self adjustWindowLevels];
2357     }
2359     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2360     {
2361         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2362         // don't count as "visible windows" for this purpose.
2363         [self unminimizeWindowIfNoneVisible];
2364         return YES;
2365     }
2367     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2368     {
2369         NSApplicationTerminateReply ret = NSTerminateNow;
2370         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2371         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2372         macdrv_event* event;
2373         WineEventQueue* queue;
2375         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2376         event->deliver = 1;
2377         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2378         {
2379             case kAELogOut:
2380             case kAEReallyLogOut:
2381                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2382                 break;
2383             case kAEShowRestartDialog:
2384                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2385                 break;
2386             case kAEShowShutdownDialog:
2387                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2388                 break;
2389             default:
2390                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2391                 break;
2392         }
2394         [eventQueuesLock lock];
2396         if ([eventQueues count])
2397         {
2398             for (queue in eventQueues)
2399                 [queue postEvent:event];
2400             ret = NSTerminateLater;
2401         }
2403         [eventQueuesLock unlock];
2405         macdrv_release_event(event);
2407         return ret;
2408     }
2410     - (void)applicationWillBecomeActive:(NSNotification *)notification
2411     {
2412         macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2413         event->deliver = 1;
2415         [eventQueuesLock lock];
2416         for (WineEventQueue* queue in eventQueues)
2417             [queue postEvent:event];
2418         [eventQueuesLock unlock];
2420         macdrv_release_event(event);
2421     }
2423     - (void)applicationWillResignActive:(NSNotification *)notification
2424     {
2425         [self adjustWindowLevels:NO];
2426     }
2428 /***********************************************************************
2429  *              PerformRequest
2431  * Run-loop-source perform callback.  Pull request blocks from the
2432  * array of queued requests and invoke them.
2433  */
2434 static void PerformRequest(void *info)
2436     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2437     WineApplicationController* controller = [WineApplicationController sharedController];
2439     for (;;)
2440     {
2441         __block dispatch_block_t block;
2443         dispatch_sync(controller->requestsManipQueue, ^{
2444             if ([controller->requests count])
2445             {
2446                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2447                 [controller->requests removeObjectAtIndex:0];
2448             }
2449             else
2450                 block = nil;
2451         });
2453         if (!block)
2454             break;
2456         block();
2457         [block release];
2459         [pool release];
2460         pool = [[NSAutoreleasePool alloc] init];
2461     }
2463     [pool release];
2466 /***********************************************************************
2467  *              OnMainThreadAsync
2469  * Run a block on the main thread asynchronously.
2470  */
2471 void OnMainThreadAsync(dispatch_block_t block)
2473     WineApplicationController* controller = [WineApplicationController sharedController];
2475     block = [block copy];
2476     dispatch_sync(controller->requestsManipQueue, ^{
2477         [controller->requests addObject:block];
2478     });
2479     [block release];
2480     CFRunLoopSourceSignal(controller->requestSource);
2481     CFRunLoopWakeUp(CFRunLoopGetMain());
2484 @end
2486 /***********************************************************************
2487  *              LogError
2488  */
2489 void LogError(const char* func, NSString* format, ...)
2491     va_list args;
2492     va_start(args, format);
2493     LogErrorv(func, format, args);
2494     va_end(args);
2497 /***********************************************************************
2498  *              LogErrorv
2499  */
2500 void LogErrorv(const char* func, NSString* format, va_list args)
2502     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2504     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2505     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2506     [message release];
2508     [pool release];
2511 /***********************************************************************
2512  *              macdrv_window_rejected_focus
2514  * Pass focus to the next window that hasn't already rejected this same
2515  * WINDOW_GOT_FOCUS event.
2516  */
2517 void macdrv_window_rejected_focus(const macdrv_event *event)
2519     OnMainThread(^{
2520         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2521     });
2524 /***********************************************************************
2525  *              macdrv_get_input_source_info
2527  * Returns the keyboard layout uchr data, keyboard type and input source.
2528  */
2529 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2531     OnMainThread(^{
2532         TISInputSourceRef inputSourceLayout;
2534         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2535         if (inputSourceLayout)
2536         {
2537             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2538                                 kTISPropertyUnicodeKeyLayoutData);
2539             *uchr = CFDataCreateCopy(NULL, data);
2540             CFRelease(inputSourceLayout);
2542             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2543             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2544             if (input_source)
2545                 *input_source = TISCopyCurrentKeyboardInputSource();
2546         }
2547     });
2550 /***********************************************************************
2551  *              macdrv_beep
2553  * Play the beep sound configured by the user in System Preferences.
2554  */
2555 void macdrv_beep(void)
2557     OnMainThreadAsync(^{
2558         NSBeep();
2559     });
2562 /***********************************************************************
2563  *              macdrv_set_display_mode
2564  */
2565 int macdrv_set_display_mode(const struct macdrv_display* display,
2566                             CGDisplayModeRef display_mode)
2568     __block int ret;
2570     OnMainThread(^{
2571         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2572     });
2574     return ret;
2577 /***********************************************************************
2578  *              macdrv_set_cursor
2580  * Set the cursor.
2582  * If name is non-NULL, it is a selector for a class method on NSCursor
2583  * identifying the cursor to set.  In that case, frames is ignored.  If
2584  * name is NULL, then frames is used.
2586  * frames is an array of dictionaries.  Each dictionary is a frame of
2587  * an animated cursor.  Under the key "image" is a CGImage for the
2588  * frame.  Under the key "duration" is a CFNumber time interval, in
2589  * seconds, for how long that frame is presented before proceeding to
2590  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2591  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2592  * This is the hot spot, measured in pixels down and to the right of the
2593  * top-left corner of the image.
2595  * If the array has exactly 1 element, the cursor is static, not
2596  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2597  */
2598 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2600     SEL sel;
2602     sel = NSSelectorFromString((NSString*)name);
2603     if (sel)
2604     {
2605         OnMainThreadAsync(^{
2606             WineApplicationController* controller = [WineApplicationController sharedController];
2607             [controller setCursorWithFrames:nil];
2608             controller.cursor = [NSCursor performSelector:sel];
2609             [controller unhideCursor];
2610         });
2611     }
2612     else
2613     {
2614         NSArray* nsframes = (NSArray*)frames;
2615         if ([nsframes count])
2616         {
2617             OnMainThreadAsync(^{
2618                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2619             });
2620         }
2621         else
2622         {
2623             OnMainThreadAsync(^{
2624                 WineApplicationController* controller = [WineApplicationController sharedController];
2625                 [controller setCursorWithFrames:nil];
2626                 [controller hideCursor];
2627             });
2628         }
2629     }
2632 /***********************************************************************
2633  *              macdrv_get_cursor_position
2635  * Obtains the current cursor position.  Returns zero on failure,
2636  * non-zero on success.
2637  */
2638 int macdrv_get_cursor_position(CGPoint *pos)
2640     OnMainThread(^{
2641         NSPoint location = [NSEvent mouseLocation];
2642         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2643         *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2644     });
2646     return TRUE;
2649 /***********************************************************************
2650  *              macdrv_set_cursor_position
2652  * Sets the cursor position without generating events.  Returns zero on
2653  * failure, non-zero on success.
2654  */
2655 int macdrv_set_cursor_position(CGPoint pos)
2657     __block int ret;
2659     OnMainThread(^{
2660         ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2661     });
2663     return ret;
2666 /***********************************************************************
2667  *              macdrv_clip_cursor
2669  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2670  * to or larger than the whole desktop region, the cursor is unclipped.
2671  * Returns zero on failure, non-zero on success.
2672  */
2673 int macdrv_clip_cursor(CGRect r)
2675     __block int ret;
2677     OnMainThread(^{
2678         WineApplicationController* controller = [WineApplicationController sharedController];
2679         BOOL clipping = FALSE;
2680         CGRect rect = r;
2682         if (!CGRectIsInfinite(rect))
2683             rect = cgrect_mac_from_win(rect);
2685         if (!CGRectIsInfinite(rect))
2686         {
2687             NSRect nsrect = NSRectFromCGRect(rect);
2688             NSScreen* screen;
2690             /* Convert the rectangle from top-down coords to bottom-up. */
2691             [controller flipRect:&nsrect];
2693             clipping = FALSE;
2694             for (screen in [NSScreen screens])
2695             {
2696                 if (!NSContainsRect(nsrect, [screen frame]))
2697                 {
2698                     clipping = TRUE;
2699                     break;
2700                 }
2701             }
2702         }
2704         if (clipping)
2705             ret = [controller startClippingCursor:rect];
2706         else
2707             ret = [controller stopClippingCursor];
2708     });
2710     return ret;
2713 /***********************************************************************
2714  *              macdrv_set_application_icon
2716  * Set the application icon.  The images array contains CGImages.  If
2717  * there are more than one, then they represent different sizes or
2718  * color depths from the icon resource.  If images is NULL or empty,
2719  * restores the default application image.
2720  */
2721 void macdrv_set_application_icon(CFArrayRef images)
2723     NSArray* imageArray = (NSArray*)images;
2725     OnMainThreadAsync(^{
2726         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2727     });
2730 /***********************************************************************
2731  *              macdrv_quit_reply
2732  */
2733 void macdrv_quit_reply(int reply)
2735     OnMainThread(^{
2736         [NSApp replyToApplicationShouldTerminate:reply];
2737     });
2740 /***********************************************************************
2741  *              macdrv_using_input_method
2742  */
2743 int macdrv_using_input_method(void)
2745     __block BOOL ret;
2747     OnMainThread(^{
2748         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2749     });
2751     return ret;
2754 /***********************************************************************
2755  *              macdrv_set_mouse_capture_window
2756  */
2757 void macdrv_set_mouse_capture_window(macdrv_window window)
2759     WineWindow* w = (WineWindow*)window;
2761     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2763     OnMainThread(^{
2764         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2765     });
2768 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2769 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2770 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2772 /***********************************************************************
2773  *              macdrv_create_input_source_list
2774  */
2775 CFArrayRef macdrv_create_input_source_list(void)
2777     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2779     OnMainThread(^{
2780         CFArrayRef input_list;
2781         CFDictionaryRef filter_dict;
2782         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2783         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2784         int i;
2786         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2787                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2788         input_list = TISCreateInputSourceList(filter_dict, false);
2790         for (i = 0; i < CFArrayGetCount(input_list); i++)
2791         {
2792             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2793             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2794             CFDictionaryRef entry;
2795             const void *input_keys[3] = { macdrv_input_source_input_key,
2796                                           macdrv_input_source_type_key,
2797                                           macdrv_input_source_lang_key };
2798             const void *input_values[3];
2800             input_values[0] = input;
2801             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2802             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2804             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2805                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2807             CFArrayAppendValue(ret, entry);
2808             CFRelease(entry);
2809         }
2810         CFRelease(input_list);
2811         CFRelease(filter_dict);
2812     });
2814     return ret;
2817 int macdrv_select_input_source(TISInputSourceRef input_source)
2819     __block int ret = FALSE;
2821     OnMainThread(^{
2822         ret = (TISSelectInputSource(input_source) == noErr);
2823     });
2825     return ret;
2828 void macdrv_set_cocoa_retina_mode(int new_mode)
2830     OnMainThread(^{
2831         [[WineApplicationController sharedController] setRetinaMode:new_mode];
2832     });