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