server: Support unbound console input device.
[wine.git] / dlls / winemac.drv / cocoa_app.m
blobe296d4b4af00dde8fa550ed03d1a0ea1b0d950e5
1 /*
2  * MACDRV Cocoa application class
3  *
4  * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
21 #import <Carbon/Carbon.h>
23 #import "cocoa_app.h"
24 #import "cocoa_event.h"
25 #import "cocoa_window.h"
28 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
31 int macdrv_err_on;
34 #if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
35 @interface NSWindow (WineAutoTabbingExtensions)
37     + (void) setAllowsAutomaticWindowTabbing:(BOOL)allows;
39 @end
40 #endif
43 /***********************************************************************
44  *              WineLocalizedString
45  *
46  * Look up a localized string by its ID in the dictionary.
47  */
48 static NSString* WineLocalizedString(unsigned int stringID)
50     NSNumber* key = [NSNumber numberWithUnsignedInt:stringID];
51     return [(NSDictionary*)localized_strings objectForKey:key];
55 @implementation WineApplication
57 @synthesize wineController;
59     - (void) sendEvent:(NSEvent*)anEvent
60     {
61         if (![wineController handleEvent:anEvent])
62         {
63             [super sendEvent:anEvent];
64             [wineController didSendEvent:anEvent];
65         }
66     }
68     - (void) setWineController:(WineApplicationController*)newController
69     {
70         wineController = newController;
71         [self setDelegate:wineController];
72     }
74 @end
77 @interface WarpRecord : NSObject
79     CGEventTimestamp timeBefore, timeAfter;
80     CGPoint from, to;
83 @property (nonatomic) CGEventTimestamp timeBefore;
84 @property (nonatomic) CGEventTimestamp timeAfter;
85 @property (nonatomic) CGPoint from;
86 @property (nonatomic) CGPoint to;
88 @end
91 @implementation WarpRecord
93 @synthesize timeBefore, timeAfter, from, to;
95 @end;
98 @interface WineApplicationController ()
100 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
101 @property (copy, nonatomic) NSArray* cursorFrames;
102 @property (retain, nonatomic) NSTimer* cursorTimer;
103 @property (retain, nonatomic) NSCursor* cursor;
104 @property (retain, nonatomic) NSImage* applicationIcon;
105 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
106 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
108     - (void) setupObservations;
109     - (void) applicationDidBecomeActive:(NSNotification *)notification;
111     static void PerformRequest(void *info);
113 @end
116 @implementation WineApplicationController
118     @synthesize keyboardType, lastFlagsChanged;
119     @synthesize applicationIcon;
120     @synthesize cursorFrames, cursorTimer, cursor;
121     @synthesize mouseCaptureWindow;
123     @synthesize clippingCursor;
125     + (void) initialize
126     {
127         if (self == [WineApplicationController class])
128         {
129             NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
130                                       @"", @"NSQuotedKeystrokeBinding",
131                                       @"", @"NSRepeatCountBinding",
132                                       [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
133                                       nil];
134             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
136             if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
137                 [NSWindow setAllowsAutomaticWindowTabbing:NO];
138         }
139     }
141     + (WineApplicationController*) sharedController
142     {
143         static WineApplicationController* sharedController;
144         static dispatch_once_t once;
146         dispatch_once(&once, ^{
147             sharedController = [[self alloc] init];
148         });
150         return sharedController;
151     }
153     - (id) init
154     {
155         self = [super init];
156         if (self != nil)
157         {
158             CFRunLoopSourceContext context = { 0 };
159             context.perform = PerformRequest;
160             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
161             if (!requestSource)
162             {
163                 [self release];
164                 return nil;
165             }
166             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
167             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
169             requests =  [[NSMutableArray alloc] init];
170             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
172             eventQueues = [[NSMutableArray alloc] init];
173             eventQueuesLock = [[NSLock alloc] init];
175             keyWindows = [[NSMutableArray alloc] init];
177             originalDisplayModes = [[NSMutableDictionary alloc] init];
178             latentDisplayModes = [[NSMutableDictionary alloc] init];
180             warpRecords = [[NSMutableArray alloc] init];
182             windowsBeingDragged = [[NSMutableSet alloc] init];
184             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
185                 !keyWindows || !originalDisplayModes || !latentDisplayModes || !warpRecords)
186             {
187                 [self release];
188                 return nil;
189             }
191             [self setupObservations];
193             keyboardType = LMGetKbdType();
195             if ([NSApp isActive])
196                 [self applicationDidBecomeActive:nil];
197         }
198         return self;
199     }
201     - (void) dealloc
202     {
203         [windowsBeingDragged release];
204         [cursor release];
205         [screenFrameCGRects release];
206         [applicationIcon release];
207         [warpRecords release];
208         [cursorTimer release];
209         [cursorFrames release];
210         [latentDisplayModes release];
211         [originalDisplayModes release];
212         [keyWindows release];
213         [eventQueues release];
214         [eventQueuesLock release];
215         if (requestsManipQueue) dispatch_release(requestsManipQueue);
216         [requests release];
217         if (requestSource)
218         {
219             CFRunLoopSourceInvalidate(requestSource);
220             CFRelease(requestSource);
221         }
222         [super dealloc];
223     }
225     - (void) transformProcessToForeground
226     {
227         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
228         {
229             NSMenu* mainMenu;
230             NSMenu* submenu;
231             NSString* bundleName;
232             NSString* title;
233             NSMenuItem* item;
235             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
236             [NSApp activateIgnoringOtherApps:YES];
237 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
238             if (!enable_app_nap && [NSProcessInfo instancesRespondToSelector:@selector(beginActivityWithOptions:reason:)])
239             {
240                 [[[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep
241                                                                 reason:@"Running Windows program"] retain]; // intentional leak
242             }
243 #endif
245             mainMenu = [[[NSMenu alloc] init] autorelease];
247             // Application menu
248             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
249             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
251             if ([bundleName length])
252                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
253             else
254                 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
255             item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
257             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
258                                       action:@selector(hideOtherApplications:)
259                                keyEquivalent:@"h"];
260             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
262             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
263                                       action:@selector(unhideAllApplications:)
264                                keyEquivalent:@""];
266             [submenu addItem:[NSMenuItem separatorItem]];
268             if ([bundleName length])
269                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
270             else
271                 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
272             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
273             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
274             item = [[[NSMenuItem alloc] init] autorelease];
275             [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
276             [item setSubmenu:submenu];
277             [mainMenu addItem:item];
279             // Window menu
280             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
281             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
282                                action:@selector(performMiniaturize:)
283                         keyEquivalent:@""];
284             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
285                                action:@selector(performZoom:)
286                         keyEquivalent:@""];
287             if ([NSWindow instancesRespondToSelector:@selector(toggleFullScreen:)])
288             {
289                 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
290                                           action:@selector(toggleFullScreen:)
291                                    keyEquivalent:@"f"];
292                 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand |
293                                                    NSEventModifierFlagOption |
294                                                    NSEventModifierFlagControl];
295             }
296             [submenu addItem:[NSMenuItem separatorItem]];
297             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
298                                action:@selector(arrangeInFront:)
299                         keyEquivalent:@""];
300             item = [[[NSMenuItem alloc] init] autorelease];
301             [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
302             [item setSubmenu:submenu];
303             [mainMenu addItem:item];
305             [NSApp setMainMenu:mainMenu];
306             [NSApp setWindowsMenu:submenu];
308             [NSApp setApplicationIconImage:self.applicationIcon];
309         }
310     }
312     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
313     {
314         PerformRequest(NULL);
316         do
317         {
318             if (processEvents)
319             {
320                 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
321                 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
322                                                     untilDate:timeout
323                                                        inMode:NSDefaultRunLoopMode
324                                                       dequeue:YES];
325                 if (event)
326                     [NSApp sendEvent:event];
327                 [pool release];
328             }
329             else
330                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
331         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
333         return *done;
334     }
336     - (BOOL) registerEventQueue:(WineEventQueue*)queue
337     {
338         [eventQueuesLock lock];
339         [eventQueues addObject:queue];
340         [eventQueuesLock unlock];
341         return TRUE;
342     }
344     - (void) unregisterEventQueue:(WineEventQueue*)queue
345     {
346         [eventQueuesLock lock];
347         [eventQueues removeObjectIdenticalTo:queue];
348         [eventQueuesLock unlock];
349     }
351     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
352     {
353         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
354     }
356     - (double) ticksForEventTime:(NSTimeInterval)eventTime
357     {
358         return (eventTime + eventTimeAdjustment) * 1000;
359     }
361     /* Invalidate old focus offers across all queues. */
362     - (void) invalidateGotFocusEvents
363     {
364         WineEventQueue* queue;
366         windowFocusSerial++;
368         [eventQueuesLock lock];
369         for (queue in eventQueues)
370         {
371             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
372                                    forWindow:nil];
373         }
374         [eventQueuesLock unlock];
375     }
377     - (void) windowGotFocus:(WineWindow*)window
378     {
379         macdrv_event* event;
381         [self invalidateGotFocusEvents];
383         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
384         event->window_got_focus.serial = windowFocusSerial;
385         if (triedWindows)
386             event->window_got_focus.tried_windows = [triedWindows retain];
387         else
388             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
389         [window.queue postEvent:event];
390         macdrv_release_event(event);
391     }
393     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
394     {
395         if (event->window_got_focus.serial == windowFocusSerial)
396         {
397             NSMutableArray* windows = [keyWindows mutableCopy];
398             NSNumber* windowNumber;
399             WineWindow* window;
401             for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
402             {
403                 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
404                 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
405                     ![windows containsObject:window])
406                     [windows addObject:window];
407             }
409             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
410             [triedWindows addObject:(WineWindow*)event->window];
411             for (window in windows)
412             {
413                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
414                 {
415                     [window makeKeyWindow];
416                     break;
417                 }
418             }
419             triedWindows = nil;
420             [windows release];
421         }
422     }
424     static BOOL EqualInputSource(TISInputSourceRef source1, TISInputSourceRef source2)
425     {
426         if (!source1 && !source2)
427             return TRUE;
428         if (!source1 || !source2)
429             return FALSE;
430         return CFEqual(source1, source2);
431     }
433     - (void) keyboardSelectionDidChange:(BOOL)force
434     {
435         TISInputSourceRef inputSource, inputSourceLayout;
437         if (!force)
438         {
439             NSTextInputContext* context = [NSTextInputContext currentInputContext];
440             if (!context || ![context client])
441                 return;
442         }
444         inputSource = TISCopyCurrentKeyboardInputSource();
445         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
446         if (!force && EqualInputSource(inputSource, lastKeyboardInputSource) &&
447             EqualInputSource(inputSourceLayout, lastKeyboardLayoutInputSource))
448         {
449             if (inputSource) CFRelease(inputSource);
450             if (inputSourceLayout) CFRelease(inputSourceLayout);
451             return;
452         }
454         if (lastKeyboardInputSource)
455             CFRelease(lastKeyboardInputSource);
456         lastKeyboardInputSource = inputSource;
457         if (lastKeyboardLayoutInputSource)
458             CFRelease(lastKeyboardLayoutInputSource);
459         lastKeyboardLayoutInputSource = inputSourceLayout;
461         inputSourceIsInputMethodValid = FALSE;
463         if (inputSourceLayout)
464         {
465             CFDataRef uchr;
466             uchr = TISGetInputSourceProperty(inputSourceLayout,
467                     kTISPropertyUnicodeKeyLayoutData);
468             if (uchr)
469             {
470                 macdrv_event* event;
471                 WineEventQueue* queue;
473                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
474                 event->keyboard_changed.keyboard_type = self.keyboardType;
475                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
476                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
477                 event->keyboard_changed.input_source = (TISInputSourceRef)CFRetain(inputSource);
479                 if (event->keyboard_changed.uchr)
480                 {
481                     [eventQueuesLock lock];
483                     for (queue in eventQueues)
484                         [queue postEvent:event];
486                     [eventQueuesLock unlock];
487                 }
489                 macdrv_release_event(event);
490             }
491         }
492     }
494     - (void) keyboardSelectionDidChange
495     {
496         [self keyboardSelectionDidChange:NO];
497     }
499     - (void) setKeyboardType:(CGEventSourceKeyboardType)newType
500     {
501         if (newType != keyboardType)
502         {
503             keyboardType = newType;
504             [self keyboardSelectionDidChange:YES];
505         }
506     }
508     - (void) enabledKeyboardInputSourcesChanged
509     {
510         macdrv_layout_list_needs_update = TRUE;
511     }
513     - (CGFloat) primaryScreenHeight
514     {
515         if (!primaryScreenHeightValid)
516         {
517             NSArray* screens = [NSScreen screens];
518             NSUInteger count = [screens count];
519             if (count)
520             {
521                 NSUInteger size;
522                 CGRect* rect;
523                 NSScreen* screen;
525                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
526                 primaryScreenHeightValid = TRUE;
528                 size = count * sizeof(CGRect);
529                 if (!screenFrameCGRects)
530                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
531                 else
532                     [screenFrameCGRects setLength:size];
534                 rect = [screenFrameCGRects mutableBytes];
535                 for (screen in screens)
536                 {
537                     CGRect temp = NSRectToCGRect([screen frame]);
538                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
539                     *rect++ = temp;
540                 }
541             }
542             else
543                 return 1280; /* arbitrary value */
544         }
546         return primaryScreenHeight;
547     }
549     - (NSPoint) flippedMouseLocation:(NSPoint)point
550     {
551         /* This relies on the fact that Cocoa's mouse location points are
552            actually off by one (precisely because they were flipped from
553            Quartz screen coordinates using this same technique). */
554         point.y = [self primaryScreenHeight] - point.y;
555         return point;
556     }
558     - (void) flipRect:(NSRect*)rect
559     {
560         // We don't use -primaryScreenHeight here so there's no chance of having
561         // out-of-date cached info.  This method is called infrequently enough
562         // that getting the screen height each time is not prohibitively expensive.
563         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
564     }
566     - (WineWindow*) frontWineWindow
567     {
568         NSNumber* windowNumber;
569         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
570         {
571             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
572             if ([window isKindOfClass:[WineWindow class]] && [window screen])
573                 return (WineWindow*)window;
574         }
576         return nil;
577     }
579     - (void) adjustWindowLevels:(BOOL)active
580     {
581         NSArray* windowNumbers;
582         NSMutableArray* wineWindows;
583         NSNumber* windowNumber;
584         NSUInteger nextFloatingIndex = 0;
585         __block NSInteger maxLevel = NSIntegerMin;
586         __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
587         __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
588         __block WineWindow* prev = nil;
589         WineWindow* window;
591         if ([NSApp isHidden]) return;
593         windowNumbers = [NSWindow windowNumbersWithOptions:0];
594         wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
596         // For the most part, we rely on the window server's ordering of the windows
597         // to be authoritative.  The one exception is if the "floating" property of
598         // one of the windows has been changed, it may be in the wrong level and thus
599         // in the order.  This method is what's supposed to fix that up.  So build
600         // a list of Wine windows sorted first by floating-ness and then by order
601         // as indicated by the window server.
602         for (windowNumber in windowNumbers)
603         {
604             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
605             if ([window isKindOfClass:[WineWindow class]])
606             {
607                 if (window.floating)
608                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
609                 else
610                     [wineWindows addObject:window];
611             }
612         }
614         NSDisableScreenUpdates();
616         // Go from back to front so that all windows in front of one which is
617         // elevated for full-screen are also elevated.
618         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
619                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
620             WineWindow* window = (WineWindow*)obj;
621             NSInteger origLevel = [window level];
622             NSInteger newLevel = [window minimumLevelForActive:active];
624             if (window.floating)
625             {
626                 if (minFloatingLevel <= maxNonfloatingLevel)
627                     minFloatingLevel = maxNonfloatingLevel + 1;
628                 if (newLevel < minFloatingLevel)
629                     newLevel = minFloatingLevel;
630             }
632             if (newLevel < maxLevel)
633                 newLevel = maxLevel;
634             else
635                 maxLevel = newLevel;
637             if (!window.floating && maxNonfloatingLevel < newLevel)
638                 maxNonfloatingLevel = newLevel;
640             if (newLevel != origLevel)
641             {
642                 [window setLevel:newLevel];
644                 // -setLevel: puts the window at the front of its new level.  If
645                 // we decreased the level, that's good (it was in front of that
646                 // level before, so it should still be now).  But if we increased
647                 // the level, the window should be toward the back (but still
648                 // ahead of the previous windows we did this to).
649                 if (origLevel < newLevel)
650                 {
651                     if (prev)
652                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
653                     else
654                         [window orderBack:nil];
655                 }
656             }
658             prev = window;
659         }];
661         NSEnableScreenUpdates();
663         [wineWindows release];
665         // The above took care of the visible windows on the current space.  That
666         // leaves windows on other spaces, minimized windows, and windows which
667         // are not ordered in.  We want to leave windows on other spaces alone
668         // so the space remains just as they left it (when viewed in Exposé or
669         // Mission Control, for example).  We'll adjust the window levels again
670         // after we switch to another space, anyway.  Windows which aren't
671         // ordered in will be handled when we order them in.  Minimized windows
672         // on the current space should be set to the level they would have gotten
673         // if they were at the front of the windows with the same floating-ness,
674         // because that's where they'll go if/when they are unminimized.  Again,
675         // for good measure we'll adjust window levels again when a window is
676         // unminimized, too.
677         for (window in [NSApp windows])
678         {
679             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
680                 [window isOnActiveSpace])
681             {
682                 NSInteger origLevel = [window level];
683                 NSInteger newLevel = [window minimumLevelForActive:YES];
684                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
686                 if (newLevel < maxLevelForType)
687                     newLevel = maxLevelForType;
689                 if (newLevel != origLevel)
690                     [window setLevel:newLevel];
691             }
692         }
693     }
695     - (void) adjustWindowLevels
696     {
697         [self adjustWindowLevels:[NSApp isActive]];
698     }
700     - (void) updateFullscreenWindows
701     {
702         if (capture_displays_for_fullscreen && [NSApp isActive])
703         {
704             BOOL anyFullscreen = FALSE;
705             NSNumber* windowNumber;
706             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
707             {
708                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
709                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
710                 {
711                     anyFullscreen = TRUE;
712                     break;
713                 }
714             }
716             if (anyFullscreen)
717             {
718                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
719                     displaysCapturedForFullscreen = TRUE;
720             }
721             else if (displaysCapturedForFullscreen)
722             {
723                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
724                     displaysCapturedForFullscreen = FALSE;
725             }
726         }
727     }
729     - (void) activeSpaceDidChange
730     {
731         [self updateFullscreenWindows];
732         [self adjustWindowLevels];
733     }
735     - (void) sendDisplaysChanged:(BOOL)activating
736     {
737         macdrv_event* event;
738         WineEventQueue* queue;
740         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
741         event->displays_changed.activating = activating;
743         [eventQueuesLock lock];
745         // If we're activating, then we just need one of our threads to get the
746         // event, so it can send it directly to the desktop window.  Otherwise,
747         // we need all of the threads to get it because we don't know which owns
748         // the desktop window and only that one will do anything with it.
749         if (activating) event->deliver = 1;
751         for (queue in eventQueues)
752             [queue postEvent:event];
753         [eventQueuesLock unlock];
755         macdrv_release_event(event);
756     }
758     // We can compare two modes directly using CFEqual, but that may require that
759     // they are identical to a level that we don't need.  In particular, when the
760     // OS switches between the integrated and discrete GPUs, the set of display
761     // modes can change in subtle ways.  We're interested in whether two modes
762     // match in their most salient features, even if they aren't identical.
763     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
764     {
765         NSString *encoding1, *encoding2;
766         uint32_t ioflags1, ioflags2, different;
767         double refresh1, refresh2;
769         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
770         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
772 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
773         if (&CGDisplayModeGetPixelWidth != NULL &&
774             CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
775         if (&CGDisplayModeGetPixelHeight != NULL &&
776             CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
777 #endif
779         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
780         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
781         if (![encoding1 isEqualToString:encoding2]) return FALSE;
783         ioflags1 = CGDisplayModeGetIOFlags(mode1);
784         ioflags2 = CGDisplayModeGetIOFlags(mode2);
785         different = ioflags1 ^ ioflags2;
786         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
787                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
788             return FALSE;
790         refresh1 = CGDisplayModeGetRefreshRate(mode1);
791         if (refresh1 == 0) refresh1 = 60;
792         refresh2 = CGDisplayModeGetRefreshRate(mode2);
793         if (refresh2 == 0) refresh2 = 60;
794         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
796         return TRUE;
797     }
799     - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
800     {
801         NSMutableArray* ret = [NSMutableArray array];
802         NSDictionary* options = nil;
804 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
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         default:
1234             return FALSE;
1235         }
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         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
1335                            CGEventMaskBit(kCGEventLeftMouseUp)          |
1336                            CGEventMaskBit(kCGEventRightMouseDown)       |
1337                            CGEventMaskBit(kCGEventRightMouseUp)         |
1338                            CGEventMaskBit(kCGEventMouseMoved)           |
1339                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
1340                            CGEventMaskBit(kCGEventRightMouseDragged)    |
1341                            CGEventMaskBit(kCGEventOtherMouseDown)       |
1342                            CGEventMaskBit(kCGEventOtherMouseUp)         |
1343                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
1344                            CGEventMaskBit(kCGEventScrollWheel);
1345         CFRunLoopSourceRef source;
1347         if (cursorClippingEventTap)
1348             return TRUE;
1350         // We create an annotated session event tap rather than a process-specific
1351         // event tap because we need to programmatically move the cursor even when
1352         // mouse moves are directed to other processes.  We disable our tap when
1353         // other processes are active, but things like Exposé are handled by other
1354         // processes even when we remain active.
1355         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1356             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1357         if (!cursorClippingEventTap)
1358             return FALSE;
1360         CGEventTapEnable(cursorClippingEventTap, FALSE);
1362         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1363         if (!source)
1364         {
1365             CFRelease(cursorClippingEventTap);
1366             cursorClippingEventTap = NULL;
1367             return FALSE;
1368         }
1370         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1371         CFRelease(source);
1372         return TRUE;
1373     }
1375     - (BOOL) setCursorPosition:(CGPoint)pos
1376     {
1377         BOOL ret;
1379         if ([windowsBeingDragged count])
1380             ret = FALSE;
1381         else if (clippingCursor)
1382         {
1383             [self clipCursorLocation:&pos];
1385             ret = [self warpCursorTo:&pos from:NULL];
1386             synthesizedLocation = pos;
1387             if (ret)
1388             {
1389                 // We want to discard mouse-move events that have already been
1390                 // through the event tap, because it's too late to account for
1391                 // the setting of the cursor position with them.  However, the
1392                 // events that may be queued with times after that but before
1393                 // the above warp can still be used.  So, use the last event
1394                 // tap event time so that -sendEvent: doesn't discard them.
1395                 lastSetCursorPositionTime = lastEventTapEventTime;
1396             }
1397         }
1398         else
1399         {
1400             // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1401             // the mouse from the cursor position for 0.25 seconds.  This means
1402             // that mouse movement during that interval doesn't move the cursor
1403             // and events carry a constant location (the warped-to position)
1404             // even though they have delta values.  For apps which warp the
1405             // cursor frequently (like after every mouse move), this makes
1406             // cursor movement horribly laggy and jerky, as only a fraction of
1407             // mouse move events have any effect.
1408             //
1409             // On some versions of OS X, it's sufficient to forcibly reassociate
1410             // the mouse and cursor position.  On others, it's necessary to set
1411             // the local events suppression interval to 0 for the warp.  That's
1412             // deprecated, but I'm not aware of any other way.  For good
1413             // measure, we do both.
1414             CGSetLocalEventsSuppressionInterval(0);
1415             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1416             CGSetLocalEventsSuppressionInterval(0.25);
1417             if (ret)
1418             {
1419                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1421                 CGAssociateMouseAndMouseCursorPosition(true);
1422             }
1423         }
1425         if (ret)
1426         {
1427             WineEventQueue* queue;
1429             // Discard all pending mouse move events.
1430             [eventQueuesLock lock];
1431             for (queue in eventQueues)
1432             {
1433                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1434                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1435                                        forWindow:nil];
1436                 [queue resetMouseEventPositions:pos];
1437             }
1438             [eventQueuesLock unlock];
1439         }
1441         return ret;
1442     }
1444     - (void) activateCursorClipping
1445     {
1446         if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1447         {
1448             CGEventTapEnable(cursorClippingEventTap, TRUE);
1449             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1450         }
1451     }
1453     - (void) deactivateCursorClipping
1454     {
1455         if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1456         {
1457             CGEventTapEnable(cursorClippingEventTap, FALSE);
1458             [warpRecords removeAllObjects];
1459             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1460         }
1461     }
1463     - (void) updateCursorClippingState
1464     {
1465         if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1466             [self activateCursorClipping];
1467         else
1468             [self deactivateCursorClipping];
1469     }
1471     - (void) updateWindowsForCursorClipping
1472     {
1473         WineWindow* window;
1474         for (window in [NSApp windows])
1475         {
1476             if ([window isKindOfClass:[WineWindow class]])
1477                 [window updateForCursorClipping];
1478         }
1479     }
1481     - (BOOL) startClippingCursor:(CGRect)rect
1482     {
1483         CGError err;
1485         if (!cursorClippingEventTap && ![self installEventTap])
1486             return FALSE;
1488         if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1489             CGEventTapIsEnabled(cursorClippingEventTap))
1490             return TRUE;
1492         err = CGAssociateMouseAndMouseCursorPosition(false);
1493         if (err != kCGErrorSuccess)
1494             return FALSE;
1496         clippingCursor = TRUE;
1497         cursorClipRect = rect;
1498         [self updateCursorClippingState];
1499         [self updateWindowsForCursorClipping];
1501         return TRUE;
1502     }
1504     - (BOOL) stopClippingCursor
1505     {
1506         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1507         if (err != kCGErrorSuccess)
1508             return FALSE;
1510         clippingCursor = FALSE;
1511         [self updateCursorClippingState];
1512         [self updateWindowsForCursorClipping];
1514         return TRUE;
1515     }
1517     - (BOOL) isKeyPressed:(uint16_t)keyCode
1518     {
1519         int bits = sizeof(pressedKeyCodes[0]) * 8;
1520         int index = keyCode / bits;
1521         uint32_t mask = 1 << (keyCode % bits);
1522         return (pressedKeyCodes[index] & mask) != 0;
1523     }
1525     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1526     {
1527         int bits = sizeof(pressedKeyCodes[0]) * 8;
1528         int index = keyCode / bits;
1529         uint32_t mask = 1 << (keyCode % bits);
1530         if (pressed)
1531             pressedKeyCodes[index] |= mask;
1532         else
1533             pressedKeyCodes[index] &= ~mask;
1534     }
1536     - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1537     {
1538         if (dragged)
1539             [windowsBeingDragged addObject:window];
1540         else
1541             [windowsBeingDragged removeObject:window];
1542         [self updateCursorClippingState];
1543     }
1545     - (void) windowWillOrderOut:(WineWindow*)window
1546     {
1547         if ([windowsBeingDragged containsObject:window])
1548         {
1549             [self window:window isBeingDragged:NO];
1551             macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1552             [window.queue postEvent:event];
1553             macdrv_release_event(event);
1554         }
1555     }
1557     - (void) handleWindowDrag:(NSEvent*)anEvent begin:(BOOL)begin
1558     {
1559         WineWindow* window = (WineWindow*)[anEvent window];
1560         if ([window isKindOfClass:[WineWindow class]])
1561         {
1562             macdrv_event* event;
1563             int eventType;
1565             if (begin)
1566             {
1567                 [windowsBeingDragged addObject:window];
1568                 eventType = WINDOW_DRAG_BEGIN;
1569             }
1570             else
1571             {
1572                 [windowsBeingDragged removeObject:window];
1573                 eventType = WINDOW_DRAG_END;
1574             }
1575             [self updateCursorClippingState];
1577             event = macdrv_create_event(eventType, window);
1578             if (eventType == WINDOW_DRAG_BEGIN)
1579                 event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
1580             [window.queue postEvent:event];
1581             macdrv_release_event(event);
1582         }
1583     }
1585     - (void) handleMouseMove:(NSEvent*)anEvent
1586     {
1587         WineWindow* targetWindow;
1588         BOOL drag = [anEvent type] != NSEventTypeMouseMoved;
1590         if ([windowsBeingDragged count])
1591             targetWindow = nil;
1592         else if (mouseCaptureWindow)
1593             targetWindow = mouseCaptureWindow;
1594         else if (drag)
1595             targetWindow = (WineWindow*)[anEvent window];
1596         else
1597         {
1598             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1599                event indicates its window is the main window, even if the cursor is
1600                over a different window.  Find the actual WineWindow that is under the
1601                cursor and post the event as being for that window. */
1602             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1603             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1604             NSInteger windowUnderNumber;
1606             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1607                                   belowWindowWithWindowNumber:0];
1608             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1609             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1610                 targetWindow = nil;
1611         }
1613         if ([targetWindow isKindOfClass:[WineWindow class]])
1614         {
1615             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1616             macdrv_event* event;
1617             BOOL absolute;
1619             // If we recently warped the cursor (other than in our cursor-clipping
1620             // event tap), discard mouse move events until we see an event which is
1621             // later than that time.
1622             if (lastSetCursorPositionTime)
1623             {
1624                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1625                     return;
1627                 lastSetCursorPositionTime = 0;
1628                 forceNextMouseMoveAbsolute = TRUE;
1629             }
1631             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1632             {
1633                 absolute = TRUE;
1634                 forceNextMouseMoveAbsolute = FALSE;
1635             }
1636             else
1637             {
1638                 // Send absolute move events if the cursor is in the interior of
1639                 // its range.  Only send relative moves if the cursor is pinned to
1640                 // the boundaries of where it can go.  We compute the position
1641                 // that's one additional point in the direction of movement.  If
1642                 // that is outside of the clipping rect or desktop region (the
1643                 // union of the screen frames), then we figure the cursor would
1644                 // have moved outside if it could but it was pinned.
1645                 CGPoint computedPoint = point;
1646                 CGFloat deltaX = [anEvent deltaX];
1647                 CGFloat deltaY = [anEvent deltaY];
1649                 if (deltaX > 0.001)
1650                     computedPoint.x++;
1651                 else if (deltaX < -0.001)
1652                     computedPoint.x--;
1654                 if (deltaY > 0.001)
1655                     computedPoint.y++;
1656                 else if (deltaY < -0.001)
1657                     computedPoint.y--;
1659                 // Assume cursor is pinned for now
1660                 absolute = FALSE;
1661                 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1662                 {
1663                     const CGRect* rects;
1664                     NSUInteger count, i;
1666                     // Caches screenFrameCGRects if necessary
1667                     [self primaryScreenHeight];
1669                     rects = [screenFrameCGRects bytes];
1670                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1672                     for (i = 0; i < count; i++)
1673                     {
1674                         if (CGRectContainsPoint(rects[i], computedPoint))
1675                         {
1676                             absolute = TRUE;
1677                             break;
1678                         }
1679                     }
1680                 }
1681             }
1683             if (absolute)
1684             {
1685                 if (clippingCursor)
1686                     [self clipCursorLocation:&point];
1687                 point = cgpoint_win_from_mac(point);
1689                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1690                 event->mouse_moved.x = floor(point.x);
1691                 event->mouse_moved.y = floor(point.y);
1693                 mouseMoveDeltaX = 0;
1694                 mouseMoveDeltaY = 0;
1695             }
1696             else
1697             {
1698                 double scale = retina_on ? 2 : 1;
1700                 /* Add event delta to accumulated delta error */
1701                 /* deltaY is already flipped */
1702                 mouseMoveDeltaX += [anEvent deltaX];
1703                 mouseMoveDeltaY += [anEvent deltaY];
1705                 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1706                 event->mouse_moved.x = mouseMoveDeltaX * scale;
1707                 event->mouse_moved.y = mouseMoveDeltaY * scale;
1709                 /* Keep the remainder after integer truncation. */
1710                 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1711                 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1712             }
1714             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1715             {
1716                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1717                 event->mouse_moved.drag = drag;
1719                 [targetWindow.queue postEvent:event];
1720             }
1722             macdrv_release_event(event);
1724             lastTargetWindow = targetWindow;
1725         }
1726         else
1727             lastTargetWindow = nil;
1729         [self updateCursor:FALSE];
1730     }
1732     - (void) handleMouseButton:(NSEvent*)theEvent
1733     {
1734         WineWindow* window = (WineWindow*)[theEvent window];
1735         NSEventType type = [theEvent type];
1736         WineWindow* windowBroughtForward = nil;
1737         BOOL process = FALSE;
1739         if (type == NSEventTypeLeftMouseUp && [windowsBeingDragged count])
1740             [self handleWindowDrag:theEvent begin:NO];
1742         if ([window isKindOfClass:[WineWindow class]] &&
1743             type == NSEventTypeLeftMouseDown &&
1744             ![theEvent wine_commandKeyDown])
1745         {
1746             NSWindowButton windowButton;
1748             windowBroughtForward = window;
1750             /* Any left-click on our window anyplace other than the close or
1751                minimize buttons will bring it forward. */
1752             for (windowButton = NSWindowCloseButton;
1753                  windowButton <= NSWindowMiniaturizeButton;
1754                  windowButton++)
1755             {
1756                 NSButton* button = [window standardWindowButton:windowButton];
1757                 if (button)
1758                 {
1759                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1760                     if ([button mouse:point inRect:[button bounds]])
1761                     {
1762                         windowBroughtForward = nil;
1763                         break;
1764                     }
1765                 }
1766             }
1767         }
1769         if ([windowsBeingDragged count])
1770             window = nil;
1771         else if (mouseCaptureWindow)
1772             window = mouseCaptureWindow;
1774         if ([window isKindOfClass:[WineWindow class]])
1775         {
1776             BOOL pressed = (type == NSEventTypeLeftMouseDown ||
1777                             type == NSEventTypeRightMouseDown ||
1778                             type == NSEventTypeOtherMouseDown);
1779             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1781             if (clippingCursor)
1782                 [self clipCursorLocation:&pt];
1784             if (pressed)
1785             {
1786                 if (mouseCaptureWindow)
1787                     process = TRUE;
1788                 else
1789                 {
1790                     // Test if the click was in the window's content area.
1791                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1792                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1793                     process = NSMouseInRect(nspoint, contentRect, NO);
1794                     if (process && [window styleMask] & NSWindowStyleMaskResizable)
1795                     {
1796                         // Ignore clicks in the grow box (resize widget).
1797                         HIPoint origin = { 0, 0 };
1798                         HIThemeGrowBoxDrawInfo info = { 0 };
1799                         HIRect bounds;
1800                         OSStatus status;
1802                         info.kind = kHIThemeGrowBoxKindNormal;
1803                         info.direction = kThemeGrowRight | kThemeGrowDown;
1804                         if ([window styleMask] & NSWindowStyleMaskUtilityWindow)
1805                             info.size = kHIThemeGrowBoxSizeSmall;
1806                         else
1807                             info.size = kHIThemeGrowBoxSizeNormal;
1809                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1810                         if (status == noErr)
1811                         {
1812                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1813                                                         NSMinY(contentRect),
1814                                                         bounds.size.width,
1815                                                         bounds.size.height);
1816                             process = !NSMouseInRect(nspoint, growBox, NO);
1817                         }
1818                     }
1819                 }
1820                 if (process)
1821                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1822             }
1823             else
1824             {
1825                 NSEventType downType = type - 1;
1826                 NSUInteger downMask = NSEventMaskFromType(downType);
1827                 process = (unmatchedMouseDowns & downMask) != 0;
1828                 unmatchedMouseDowns &= ~downMask;
1829             }
1831             if (process)
1832             {
1833                 macdrv_event* event;
1835                 pt = cgpoint_win_from_mac(pt);
1837                 event = macdrv_create_event(MOUSE_BUTTON, window);
1838                 event->mouse_button.button = [theEvent buttonNumber];
1839                 event->mouse_button.pressed = pressed;
1840                 event->mouse_button.x = floor(pt.x);
1841                 event->mouse_button.y = floor(pt.y);
1842                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1844                 [window.queue postEvent:event];
1846                 macdrv_release_event(event);
1847             }
1848         }
1850         if (windowBroughtForward)
1851         {
1852             WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1853             NSInteger ancestorNumber = [ancestor windowNumber];
1854             NSInteger ancestorLevel = [ancestor level];
1856             for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1857             {
1858                 NSInteger windowNumber = [windowNumberObject integerValue];
1859                 if (windowNumber == ancestorNumber)
1860                     break;
1861                 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1862                 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1863                     [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1864                 {
1865                     [ancestor postBroughtForwardEvent];
1866                     break;
1867                 }
1868             }
1869             if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate)
1870                 [self windowGotFocus:windowBroughtForward];
1871         }
1873         // Since mouse button events deliver absolute cursor position, the
1874         // accumulating delta from move events is invalidated.  Make sure
1875         // next mouse move event starts over from an absolute baseline.
1876         // Also, it's at least possible that the title bar widgets (e.g. close
1877         // button, etc.) could enter an internal event loop on a mouse down that
1878         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1879         // dragged events and, after that, any notion of the cursor position
1880         // computed from accumulating deltas would be wrong.
1881         forceNextMouseMoveAbsolute = TRUE;
1882     }
1884     - (void) handleScrollWheel:(NSEvent*)theEvent
1885     {
1886         WineWindow* window;
1888         if (mouseCaptureWindow)
1889             window = mouseCaptureWindow;
1890         else
1891             window = (WineWindow*)[theEvent window];
1893         if ([window isKindOfClass:[WineWindow class]])
1894         {
1895             CGEventRef cgevent = [theEvent CGEvent];
1896             CGPoint pt = CGEventGetLocation(cgevent);
1897             BOOL process;
1899             if (clippingCursor)
1900                 [self clipCursorLocation:&pt];
1902             if (mouseCaptureWindow)
1903                 process = TRUE;
1904             else
1905             {
1906                 // Only process the event if it was in the window's content area.
1907                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1908                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1909                 process = NSMouseInRect(nspoint, contentRect, NO);
1910             }
1912             if (process)
1913             {
1914                 macdrv_event* event;
1915                 double x, y;
1916                 BOOL continuous = FALSE;
1918                 pt = cgpoint_win_from_mac(pt);
1920                 event = macdrv_create_event(MOUSE_SCROLL, window);
1921                 event->mouse_scroll.x = floor(pt.x);
1922                 event->mouse_scroll.y = floor(pt.y);
1923                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1925                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1926                 {
1927                     continuous = TRUE;
1929                     /* Continuous scroll wheel events come from high-precision scrolling
1930                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1931                        For these, we can get more precise data from the CGEvent API. */
1932                     /* Axis 1 is vertical, axis 2 is horizontal. */
1933                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1934                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1935                 }
1936                 else
1937                 {
1938                     double pixelsPerLine = 10;
1939                     CGEventSourceRef source;
1941                     /* The non-continuous values are in units of "lines", not pixels. */
1942                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1943                     {
1944                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1945                         CFRelease(source);
1946                     }
1948                     x = pixelsPerLine * [theEvent deltaX];
1949                     y = pixelsPerLine * [theEvent deltaY];
1950                 }
1952                 /* Mac: negative is right or down, positive is left or up.
1953                    Win32: negative is left or down, positive is right or up.
1954                    So, negate the X scroll value to translate. */
1955                 x = -x;
1957                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1958                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1959                    6 times the pixel value. */
1960                 x *= 6;
1961                 y *= 6;
1963                 if (use_precise_scrolling)
1964                 {
1965                     event->mouse_scroll.x_scroll = x;
1966                     event->mouse_scroll.y_scroll = y;
1968                     if (!continuous)
1969                     {
1970                         /* For non-continuous "clicky" wheels, if there was any motion, make
1971                            sure there was at least WHEEL_DELTA motion.  This is so, at slow
1972                            speeds where the system's acceleration curve is actually reducing the
1973                            scroll distance, the user is sure to get some action out of each click.
1974                            For example, this is important for rotating though weapons in a
1975                            first-person shooter. */
1976                         if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1977                             event->mouse_scroll.x_scroll = 120;
1978                         else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1979                             event->mouse_scroll.x_scroll = -120;
1981                         if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1982                             event->mouse_scroll.y_scroll = 120;
1983                         else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1984                             event->mouse_scroll.y_scroll = -120;
1985                     }
1986                 }
1987                 else
1988                 {
1989                     /* If it's been a while since the last scroll event or if the scrolling has
1990                        reversed direction, reset the accumulated scroll value. */
1991                     if ([theEvent timestamp] - lastScrollTime > 1)
1992                         accumScrollX = accumScrollY = 0;
1993                     else
1994                     {
1995                         /* The accumulated scroll value is in the opposite direction/sign of the last
1996                            scroll.  That's because it's the "debt" resulting from over-scrolling in
1997                            that direction.  We accumulate by adding in the scroll amount and then, if
1998                            it has the same sign as the scroll value, we subtract any whole or partial
1999                            WHEEL_DELTAs, leaving it 0 or the opposite sign.  So, the user switched
2000                            scroll direction if the accumulated debt and the new scroll value have the
2001                            same sign. */
2002                         if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
2003                             accumScrollX = 0;
2004                         if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
2005                             accumScrollY = 0;
2006                     }
2007                     lastScrollTime = [theEvent timestamp];
2009                     accumScrollX += x;
2010                     accumScrollY += y;
2012                     if (accumScrollX > 0 && x > 0)
2013                         event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
2014                     if (accumScrollX < 0 && x < 0)
2015                         event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
2016                     if (accumScrollY > 0 && y > 0)
2017                         event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
2018                     if (accumScrollY < 0 && y < 0)
2019                         event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
2021                     accumScrollX -= event->mouse_scroll.x_scroll;
2022                     accumScrollY -= event->mouse_scroll.y_scroll;
2023                 }
2025                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
2026                     [window.queue postEvent:event];
2028                 macdrv_release_event(event);
2030                 // Since scroll wheel events deliver absolute cursor position, the
2031                 // accumulating delta from move events is invalidated.  Make sure next
2032                 // mouse move event starts over from an absolute baseline.
2033                 forceNextMouseMoveAbsolute = TRUE;
2034             }
2035         }
2036     }
2038     // Returns TRUE if the event was handled and caller should do nothing more
2039     // with it.  Returns FALSE if the caller should process it as normal and
2040     // then call -didSendEvent:.
2041     - (BOOL) handleEvent:(NSEvent*)anEvent
2042     {
2043         BOOL ret = FALSE;
2044         NSEventType type = [anEvent type];
2046         if (type == NSEventTypeFlagsChanged)
2047             self.lastFlagsChanged = anEvent;
2048         else if (type == NSEventTypeMouseMoved || type == NSEventTypeLeftMouseDragged ||
2049                  type == NSEventTypeRightMouseDragged || type == NSEventTypeOtherMouseDragged)
2050         {
2051             [self handleMouseMove:anEvent];
2052             ret = mouseCaptureWindow && ![windowsBeingDragged count];
2053         }
2054         else if (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp ||
2055                  type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp ||
2056                  type == NSEventTypeOtherMouseDown || type == NSEventTypeOtherMouseUp)
2057         {
2058             [self handleMouseButton:anEvent];
2059             ret = mouseCaptureWindow && ![windowsBeingDragged count];
2060         }
2061         else if (type == NSEventTypeScrollWheel)
2062         {
2063             [self handleScrollWheel:anEvent];
2064             ret = mouseCaptureWindow != nil;
2065         }
2066         else if (type == NSEventTypeKeyDown)
2067         {
2068             // -[NSApplication sendEvent:] seems to consume presses of the Help
2069             // key (Insert key on PC keyboards), so we have to bypass it and
2070             // send the event directly to the window.
2071             if (anEvent.keyCode == kVK_Help)
2072             {
2073                 [anEvent.window sendEvent:anEvent];
2074                 ret = TRUE;
2075             }
2076         }
2077         else if (type == NSEventTypeKeyUp)
2078         {
2079             uint16_t keyCode = [anEvent keyCode];
2080             if ([self isKeyPressed:keyCode])
2081             {
2082                 WineWindow* window = (WineWindow*)[anEvent window];
2083                 [self noteKey:keyCode pressed:FALSE];
2084                 if ([window isKindOfClass:[WineWindow class]])
2085                     [window postKeyEvent:anEvent];
2086             }
2087         }
2088         else if (type == NSEventTypeAppKitDefined)
2089         {
2090             short subtype = [anEvent subtype];
2092             // These subtypes are not documented but they appear to mean
2093             // "a window is being dragged" and "a window is no longer being
2094             // dragged", respectively.
2095             if (subtype == 20 || subtype == 21)
2096                 [self handleWindowDrag:anEvent begin:(subtype == 20)];
2097         }
2099         return ret;
2100     }
2102     - (void) didSendEvent:(NSEvent*)anEvent
2103     {
2104         NSEventType type = [anEvent type];
2106         if (type == NSEventTypeKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
2107         {
2108             NSUInteger modifiers = [anEvent modifierFlags];
2109             if ((modifiers & NSEventModifierFlagCommand) &&
2110                 !(modifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)))
2111             {
2112                 // Command-Tab and Command-Shift-Tab would normally be intercepted
2113                 // by the system to switch applications.  If we're seeing it, it's
2114                 // presumably because we've captured the displays, preventing
2115                 // normal application switching.  Do it manually.
2116                 [self handleCommandTab];
2117             }
2118         }
2119     }
2121     - (void) setupObservations
2122     {
2123         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2124         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
2125         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
2127         [nc addObserverForName:NSWindowDidBecomeKeyNotification
2128                         object:nil
2129                          queue:nil
2130                     usingBlock:^(NSNotification *note){
2131             NSWindow* window = [note object];
2132             [keyWindows removeObjectIdenticalTo:window];
2133             [keyWindows insertObject:window atIndex:0];
2134         }];
2136         [nc addObserverForName:NSWindowWillCloseNotification
2137                         object:nil
2138                          queue:[NSOperationQueue mainQueue]
2139                     usingBlock:^(NSNotification *note){
2140             NSWindow* window = [note object];
2141             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
2142                 return;
2143             [keyWindows removeObjectIdenticalTo:window];
2144             if (window == lastTargetWindow)
2145                 lastTargetWindow = nil;
2146             if (window == self.mouseCaptureWindow)
2147                 self.mouseCaptureWindow = nil;
2148             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
2149             {
2150                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
2151                     [self updateFullscreenWindows];
2152                 });
2153             }
2154             [windowsBeingDragged removeObject:window];
2155             [self updateCursorClippingState];
2156         }];
2158         [nc addObserver:self
2159                selector:@selector(keyboardSelectionDidChange)
2160                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
2161                  object:nil];
2163         /* The above notification isn't sent unless the NSTextInputContext
2164            class has initialized itself.  Poke it. */
2165         [NSTextInputContext self];
2167         [wsnc addObserver:self
2168                  selector:@selector(activeSpaceDidChange)
2169                      name:NSWorkspaceActiveSpaceDidChangeNotification
2170                    object:nil];
2172         [nc addObserver:self
2173                selector:@selector(releaseMouseCapture)
2174                    name:NSMenuDidBeginTrackingNotification
2175                  object:nil];
2177         [dnc        addObserver:self
2178                        selector:@selector(releaseMouseCapture)
2179                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2180                          object:nil
2181              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2183         [dnc addObserver:self
2184                 selector:@selector(enabledKeyboardInputSourcesChanged)
2185                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2186                   object:nil];
2187     }
2189     - (BOOL) inputSourceIsInputMethod
2190     {
2191         if (!inputSourceIsInputMethodValid)
2192         {
2193             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2194             if (inputSource)
2195             {
2196                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2197                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2198                 CFRelease(inputSource);
2199             }
2200             else
2201                 inputSourceIsInputMethod = FALSE;
2202             inputSourceIsInputMethodValid = TRUE;
2203         }
2205         return inputSourceIsInputMethod;
2206     }
2208     - (void) releaseMouseCapture
2209     {
2210         // This might be invoked on a background thread by the distributed
2211         // notification center.  Shunt it to the main thread.
2212         if (![NSThread isMainThread])
2213         {
2214             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2215             return;
2216         }
2218         if (mouseCaptureWindow)
2219         {
2220             macdrv_event* event;
2222             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2223             [mouseCaptureWindow.queue postEvent:event];
2224             macdrv_release_event(event);
2225         }
2226     }
2228     - (void) unminimizeWindowIfNoneVisible
2229     {
2230         if (![self frontWineWindow])
2231         {
2232             for (WineWindow* window in [NSApp windows])
2233             {
2234                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2235                 {
2236                     [window deminiaturize:self];
2237                     break;
2238                 }
2239             }
2240         }
2241     }
2243     - (void) setRetinaMode:(int)mode
2244     {
2245         retina_on = mode;
2247         if (clippingCursor)
2248         {
2249             double scale = mode ? 0.5 : 2.0;
2250             cursorClipRect.origin.x *= scale;
2251             cursorClipRect.origin.y *= scale;
2252             cursorClipRect.size.width *= scale;
2253             cursorClipRect.size.height *= scale;
2254         }
2256         for (WineWindow* window in [NSApp windows])
2257         {
2258             if ([window isKindOfClass:[WineWindow class]])
2259                 [window setRetinaMode:mode];
2260         }
2261     }
2264     /*
2265      * ---------- NSApplicationDelegate methods ----------
2266      */
2267     - (void)applicationDidBecomeActive:(NSNotification *)notification
2268     {
2269         NSNumber* displayID;
2270         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2272         latentDisplayModes = [[NSMutableDictionary alloc] init];
2273         for (displayID in modesToRealize)
2274         {
2275             CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2276             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2277         }
2279         [self updateCursorClippingState];
2281         [self updateFullscreenWindows];
2282         [self adjustWindowLevels:YES];
2284         if (beenActive)
2285             [self unminimizeWindowIfNoneVisible];
2286         beenActive = TRUE;
2288         // If a Wine process terminates abruptly while it has the display captured
2289         // and switched to a different resolution, Mac OS X will uncapture the
2290         // displays and switch their resolutions back.  However, the other Wine
2291         // processes won't have their notion of the desktop rect changed back.
2292         // This can lead them to refuse to draw or acknowledge clicks in certain
2293         // portions of their windows.
2294         //
2295         // To solve this, we synthesize a displays-changed event whenever we're
2296         // activated.  This will provoke a re-synchronization of Wine's notion of
2297         // the desktop rect with the actual state.
2298         [self sendDisplaysChanged:TRUE];
2300         // The cursor probably moved while we were inactive.  Accumulated mouse
2301         // movement deltas are invalidated.  Make sure the next mouse move event
2302         // starts over from an absolute baseline.
2303         forceNextMouseMoveAbsolute = TRUE;
2304     }
2306     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2307     {
2308         primaryScreenHeightValid = FALSE;
2309         [self sendDisplaysChanged:FALSE];
2310         [self adjustWindowLevels];
2312         // When the display configuration changes, the cursor position may jump.
2313         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2314         // mouse move event starts over from an absolute baseline.
2315         forceNextMouseMoveAbsolute = TRUE;
2316     }
2318     - (void)applicationDidResignActive:(NSNotification *)notification
2319     {
2320         macdrv_event* event;
2321         WineEventQueue* queue;
2323         [self updateCursorClippingState];
2325         [self invalidateGotFocusEvents];
2327         event = macdrv_create_event(APP_DEACTIVATED, nil);
2329         [eventQueuesLock lock];
2330         for (queue in eventQueues)
2331             [queue postEvent:event];
2332         [eventQueuesLock unlock];
2334         macdrv_release_event(event);
2336         [self releaseMouseCapture];
2337     }
2339     - (void) applicationDidUnhide:(NSNotification*)aNotification
2340     {
2341         [self adjustWindowLevels];
2342     }
2344     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2345     {
2346         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2347         // don't count as "visible windows" for this purpose.
2348         [self unminimizeWindowIfNoneVisible];
2349         return YES;
2350     }
2352     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2353     {
2354         NSApplicationTerminateReply ret = NSTerminateNow;
2355         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2356         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2357         macdrv_event* event;
2358         WineEventQueue* queue;
2360         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2361         event->deliver = 1;
2362         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2363         {
2364             case kAELogOut:
2365             case kAEReallyLogOut:
2366                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2367                 break;
2368             case kAEShowRestartDialog:
2369                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2370                 break;
2371             case kAEShowShutdownDialog:
2372                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2373                 break;
2374             default:
2375                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2376                 break;
2377         }
2379         [eventQueuesLock lock];
2381         if ([eventQueues count])
2382         {
2383             for (queue in eventQueues)
2384                 [queue postEvent:event];
2385             ret = NSTerminateLater;
2386         }
2388         [eventQueuesLock unlock];
2390         macdrv_release_event(event);
2392         return ret;
2393     }
2395     - (void)applicationWillBecomeActive:(NSNotification *)notification
2396     {
2397         macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2398         event->deliver = 1;
2400         [eventQueuesLock lock];
2401         for (WineEventQueue* queue in eventQueues)
2402             [queue postEvent:event];
2403         [eventQueuesLock unlock];
2405         macdrv_release_event(event);
2406     }
2408     - (void)applicationWillResignActive:(NSNotification *)notification
2409     {
2410         [self adjustWindowLevels:NO];
2411     }
2413 /***********************************************************************
2414  *              PerformRequest
2416  * Run-loop-source perform callback.  Pull request blocks from the
2417  * array of queued requests and invoke them.
2418  */
2419 static void PerformRequest(void *info)
2421     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2422     WineApplicationController* controller = [WineApplicationController sharedController];
2424     for (;;)
2425     {
2426         __block dispatch_block_t block;
2428         dispatch_sync(controller->requestsManipQueue, ^{
2429             if ([controller->requests count])
2430             {
2431                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2432                 [controller->requests removeObjectAtIndex:0];
2433             }
2434             else
2435                 block = nil;
2436         });
2438         if (!block)
2439             break;
2441         block();
2442         [block release];
2444         [pool release];
2445         pool = [[NSAutoreleasePool alloc] init];
2446     }
2448     [pool release];
2451 /***********************************************************************
2452  *              OnMainThreadAsync
2454  * Run a block on the main thread asynchronously.
2455  */
2456 void OnMainThreadAsync(dispatch_block_t block)
2458     WineApplicationController* controller = [WineApplicationController sharedController];
2460     block = [block copy];
2461     dispatch_sync(controller->requestsManipQueue, ^{
2462         [controller->requests addObject:block];
2463     });
2464     [block release];
2465     CFRunLoopSourceSignal(controller->requestSource);
2466     CFRunLoopWakeUp(CFRunLoopGetMain());
2469 @end
2471 /***********************************************************************
2472  *              LogError
2473  */
2474 void LogError(const char* func, NSString* format, ...)
2476     va_list args;
2477     va_start(args, format);
2478     LogErrorv(func, format, args);
2479     va_end(args);
2482 /***********************************************************************
2483  *              LogErrorv
2484  */
2485 void LogErrorv(const char* func, NSString* format, va_list args)
2487     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2489     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2490     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2491     [message release];
2493     [pool release];
2496 /***********************************************************************
2497  *              macdrv_window_rejected_focus
2499  * Pass focus to the next window that hasn't already rejected this same
2500  * WINDOW_GOT_FOCUS event.
2501  */
2502 void macdrv_window_rejected_focus(const macdrv_event *event)
2504     OnMainThread(^{
2505         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2506     });
2509 /***********************************************************************
2510  *              macdrv_get_input_source_info
2512  * Returns the keyboard layout uchr data, keyboard type and input source.
2513  */
2514 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2516     OnMainThread(^{
2517         TISInputSourceRef inputSourceLayout;
2519         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2520         if (inputSourceLayout)
2521         {
2522             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2523                                 kTISPropertyUnicodeKeyLayoutData);
2524             *uchr = CFDataCreateCopy(NULL, data);
2525             CFRelease(inputSourceLayout);
2527             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2528             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2529             if (input_source)
2530                 *input_source = TISCopyCurrentKeyboardInputSource();
2531         }
2532     });
2535 /***********************************************************************
2536  *              macdrv_beep
2538  * Play the beep sound configured by the user in System Preferences.
2539  */
2540 void macdrv_beep(void)
2542     OnMainThreadAsync(^{
2543         NSBeep();
2544     });
2547 /***********************************************************************
2548  *              macdrv_set_display_mode
2549  */
2550 int macdrv_set_display_mode(const struct macdrv_display* display,
2551                             CGDisplayModeRef display_mode)
2553     __block int ret;
2555     OnMainThread(^{
2556         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2557     });
2559     return ret;
2562 /***********************************************************************
2563  *              macdrv_set_cursor
2565  * Set the cursor.
2567  * If name is non-NULL, it is a selector for a class method on NSCursor
2568  * identifying the cursor to set.  In that case, frames is ignored.  If
2569  * name is NULL, then frames is used.
2571  * frames is an array of dictionaries.  Each dictionary is a frame of
2572  * an animated cursor.  Under the key "image" is a CGImage for the
2573  * frame.  Under the key "duration" is a CFNumber time interval, in
2574  * seconds, for how long that frame is presented before proceeding to
2575  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2576  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2577  * This is the hot spot, measured in pixels down and to the right of the
2578  * top-left corner of the image.
2580  * If the array has exactly 1 element, the cursor is static, not
2581  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2582  */
2583 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2585     SEL sel;
2587     sel = NSSelectorFromString((NSString*)name);
2588     if (sel)
2589     {
2590         OnMainThreadAsync(^{
2591             WineApplicationController* controller = [WineApplicationController sharedController];
2592             [controller setCursorWithFrames:nil];
2593             controller.cursor = [NSCursor performSelector:sel];
2594             [controller unhideCursor];
2595         });
2596     }
2597     else
2598     {
2599         NSArray* nsframes = (NSArray*)frames;
2600         if ([nsframes count])
2601         {
2602             OnMainThreadAsync(^{
2603                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2604             });
2605         }
2606         else
2607         {
2608             OnMainThreadAsync(^{
2609                 WineApplicationController* controller = [WineApplicationController sharedController];
2610                 [controller setCursorWithFrames:nil];
2611                 [controller hideCursor];
2612             });
2613         }
2614     }
2617 /***********************************************************************
2618  *              macdrv_get_cursor_position
2620  * Obtains the current cursor position.  Returns zero on failure,
2621  * non-zero on success.
2622  */
2623 int macdrv_get_cursor_position(CGPoint *pos)
2625     OnMainThread(^{
2626         NSPoint location = [NSEvent mouseLocation];
2627         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2628         *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2629     });
2631     return TRUE;
2634 /***********************************************************************
2635  *              macdrv_set_cursor_position
2637  * Sets the cursor position without generating events.  Returns zero on
2638  * failure, non-zero on success.
2639  */
2640 int macdrv_set_cursor_position(CGPoint pos)
2642     __block int ret;
2644     OnMainThread(^{
2645         ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2646     });
2648     return ret;
2651 /***********************************************************************
2652  *              macdrv_clip_cursor
2654  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2655  * to or larger than the whole desktop region, the cursor is unclipped.
2656  * Returns zero on failure, non-zero on success.
2657  */
2658 int macdrv_clip_cursor(CGRect r)
2660     __block int ret;
2662     OnMainThread(^{
2663         WineApplicationController* controller = [WineApplicationController sharedController];
2664         BOOL clipping = FALSE;
2665         CGRect rect = r;
2667         if (!CGRectIsInfinite(rect))
2668             rect = cgrect_mac_from_win(rect);
2670         if (!CGRectIsInfinite(rect))
2671         {
2672             NSRect nsrect = NSRectFromCGRect(rect);
2673             NSScreen* screen;
2675             /* Convert the rectangle from top-down coords to bottom-up. */
2676             [controller flipRect:&nsrect];
2678             clipping = FALSE;
2679             for (screen in [NSScreen screens])
2680             {
2681                 if (!NSContainsRect(nsrect, [screen frame]))
2682                 {
2683                     clipping = TRUE;
2684                     break;
2685                 }
2686             }
2687         }
2689         if (clipping)
2690             ret = [controller startClippingCursor:rect];
2691         else
2692             ret = [controller stopClippingCursor];
2693     });
2695     return ret;
2698 /***********************************************************************
2699  *              macdrv_set_application_icon
2701  * Set the application icon.  The images array contains CGImages.  If
2702  * there are more than one, then they represent different sizes or
2703  * color depths from the icon resource.  If images is NULL or empty,
2704  * restores the default application image.
2705  */
2706 void macdrv_set_application_icon(CFArrayRef images)
2708     NSArray* imageArray = (NSArray*)images;
2710     OnMainThreadAsync(^{
2711         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2712     });
2715 /***********************************************************************
2716  *              macdrv_quit_reply
2717  */
2718 void macdrv_quit_reply(int reply)
2720     OnMainThread(^{
2721         [NSApp replyToApplicationShouldTerminate:reply];
2722     });
2725 /***********************************************************************
2726  *              macdrv_using_input_method
2727  */
2728 int macdrv_using_input_method(void)
2730     __block BOOL ret;
2732     OnMainThread(^{
2733         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2734     });
2736     return ret;
2739 /***********************************************************************
2740  *              macdrv_set_mouse_capture_window
2741  */
2742 void macdrv_set_mouse_capture_window(macdrv_window window)
2744     WineWindow* w = (WineWindow*)window;
2746     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2748     OnMainThread(^{
2749         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2750     });
2753 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2754 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2755 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2757 /***********************************************************************
2758  *              macdrv_create_input_source_list
2759  */
2760 CFArrayRef macdrv_create_input_source_list(void)
2762     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2764     OnMainThread(^{
2765         CFArrayRef input_list;
2766         CFDictionaryRef filter_dict;
2767         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2768         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2769         int i;
2771         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2772                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2773         input_list = TISCreateInputSourceList(filter_dict, false);
2775         for (i = 0; i < CFArrayGetCount(input_list); i++)
2776         {
2777             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2778             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2779             CFDictionaryRef entry;
2780             const void *input_keys[3] = { macdrv_input_source_input_key,
2781                                           macdrv_input_source_type_key,
2782                                           macdrv_input_source_lang_key };
2783             const void *input_values[3];
2785             input_values[0] = input;
2786             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2787             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2789             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2790                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2792             CFArrayAppendValue(ret, entry);
2793             CFRelease(entry);
2794         }
2795         CFRelease(input_list);
2796         CFRelease(filter_dict);
2797     });
2799     return ret;
2802 int macdrv_select_input_source(TISInputSourceRef input_source)
2804     __block int ret = FALSE;
2806     OnMainThread(^{
2807         ret = (TISSelectInputSource(input_source) == noErr);
2808     });
2810     return ret;
2813 void macdrv_set_cocoa_retina_mode(int new_mode)
2815     OnMainThread(^{
2816         [[WineApplicationController sharedController] setRetinaMode:new_mode];
2817     });