d3dx10: Implement D3DX10GetImageInfoFromFileW().
[wine.git] / dlls / winemac.drv / cocoa_app.m
blobb4dcbfff4732ecc171c77f261ca1623a09b78d0f
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:NSEventModifierFlagCommand | NSEventModifierFlagOption];
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:NSEventModifierFlagCommand | NSEventModifierFlagOption];
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:NSEventModifierFlagCommand |
294                                                    NSEventModifierFlagOption |
295                                                    NSEventModifierFlagControl];
296             }
297             [submenu addItem:[NSMenuItem separatorItem]];
298             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
299                                action:@selector(arrangeInFront:)
300                         keyEquivalent:@""];
301             item = [[[NSMenuItem alloc] init] autorelease];
302             [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
303             [item setSubmenu:submenu];
304             [mainMenu addItem:item];
306             [NSApp setMainMenu:mainMenu];
307             [NSApp setWindowsMenu:submenu];
309             [NSApp setApplicationIconImage:self.applicationIcon];
310         }
311     }
313     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
314     {
315         PerformRequest(NULL);
317         do
318         {
319             if (processEvents)
320             {
321                 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
322                 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
323                                                     untilDate:timeout
324                                                        inMode:NSDefaultRunLoopMode
325                                                       dequeue:YES];
326                 if (event)
327                     [NSApp sendEvent:event];
328                 [pool release];
329             }
330             else
331                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
332         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
334         return *done;
335     }
337     - (BOOL) registerEventQueue:(WineEventQueue*)queue
338     {
339         [eventQueuesLock lock];
340         [eventQueues addObject:queue];
341         [eventQueuesLock unlock];
342         return TRUE;
343     }
345     - (void) unregisterEventQueue:(WineEventQueue*)queue
346     {
347         [eventQueuesLock lock];
348         [eventQueues removeObjectIdenticalTo:queue];
349         [eventQueuesLock unlock];
350     }
352     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
353     {
354         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
355     }
357     - (double) ticksForEventTime:(NSTimeInterval)eventTime
358     {
359         return (eventTime + eventTimeAdjustment) * 1000;
360     }
362     /* Invalidate old focus offers across all queues. */
363     - (void) invalidateGotFocusEvents
364     {
365         WineEventQueue* queue;
367         windowFocusSerial++;
369         [eventQueuesLock lock];
370         for (queue in eventQueues)
371         {
372             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
373                                    forWindow:nil];
374         }
375         [eventQueuesLock unlock];
376     }
378     - (void) windowGotFocus:(WineWindow*)window
379     {
380         macdrv_event* event;
382         [self invalidateGotFocusEvents];
384         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
385         event->window_got_focus.serial = windowFocusSerial;
386         if (triedWindows)
387             event->window_got_focus.tried_windows = [triedWindows retain];
388         else
389             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
390         [window.queue postEvent:event];
391         macdrv_release_event(event);
392     }
394     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
395     {
396         if (event->window_got_focus.serial == windowFocusSerial)
397         {
398             NSMutableArray* windows = [keyWindows mutableCopy];
399             NSNumber* windowNumber;
400             WineWindow* window;
402             for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
403             {
404                 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
405                 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
406                     ![windows containsObject:window])
407                     [windows addObject:window];
408             }
410             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
411             [triedWindows addObject:(WineWindow*)event->window];
412             for (window in windows)
413             {
414                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
415                 {
416                     [window makeKeyWindow];
417                     break;
418                 }
419             }
420             triedWindows = nil;
421             [windows release];
422         }
423     }
425     static BOOL EqualInputSource(TISInputSourceRef source1, TISInputSourceRef source2)
426     {
427         if (!source1 && !source2)
428             return TRUE;
429         if (!source1 || !source2)
430             return FALSE;
431         return CFEqual(source1, source2);
432     }
434     - (void) keyboardSelectionDidChange:(BOOL)force
435     {
436         TISInputSourceRef inputSource, inputSourceLayout;
438         if (!force)
439         {
440             NSTextInputContext* context = [NSTextInputContext currentInputContext];
441             if (!context || ![context client])
442                 return;
443         }
445         inputSource = TISCopyCurrentKeyboardInputSource();
446         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
447         if (!force && EqualInputSource(inputSource, lastKeyboardInputSource) &&
448             EqualInputSource(inputSourceLayout, lastKeyboardLayoutInputSource))
449         {
450             if (inputSource) CFRelease(inputSource);
451             if (inputSourceLayout) CFRelease(inputSourceLayout);
452             return;
453         }
455         if (lastKeyboardInputSource)
456             CFRelease(lastKeyboardInputSource);
457         lastKeyboardInputSource = inputSource;
458         if (lastKeyboardLayoutInputSource)
459             CFRelease(lastKeyboardLayoutInputSource);
460         lastKeyboardLayoutInputSource = inputSourceLayout;
462         inputSourceIsInputMethodValid = FALSE;
464         if (inputSourceLayout)
465         {
466             CFDataRef uchr;
467             uchr = TISGetInputSourceProperty(inputSourceLayout,
468                     kTISPropertyUnicodeKeyLayoutData);
469             if (uchr)
470             {
471                 macdrv_event* event;
472                 WineEventQueue* queue;
474                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
475                 event->keyboard_changed.keyboard_type = self.keyboardType;
476                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
477                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
478                 event->keyboard_changed.input_source = (TISInputSourceRef)CFRetain(inputSource);
480                 if (event->keyboard_changed.uchr)
481                 {
482                     [eventQueuesLock lock];
484                     for (queue in eventQueues)
485                         [queue postEvent:event];
487                     [eventQueuesLock unlock];
488                 }
490                 macdrv_release_event(event);
491             }
492         }
493     }
495     - (void) keyboardSelectionDidChange
496     {
497         [self keyboardSelectionDidChange:NO];
498     }
500     - (void) setKeyboardType:(CGEventSourceKeyboardType)newType
501     {
502         if (newType != keyboardType)
503         {
504             keyboardType = newType;
505             [self keyboardSelectionDidChange:YES];
506         }
507     }
509     - (void) enabledKeyboardInputSourcesChanged
510     {
511         macdrv_layout_list_needs_update = TRUE;
512     }
514     - (CGFloat) primaryScreenHeight
515     {
516         if (!primaryScreenHeightValid)
517         {
518             NSArray* screens = [NSScreen screens];
519             NSUInteger count = [screens count];
520             if (count)
521             {
522                 NSUInteger size;
523                 CGRect* rect;
524                 NSScreen* screen;
526                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
527                 primaryScreenHeightValid = TRUE;
529                 size = count * sizeof(CGRect);
530                 if (!screenFrameCGRects)
531                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
532                 else
533                     [screenFrameCGRects setLength:size];
535                 rect = [screenFrameCGRects mutableBytes];
536                 for (screen in screens)
537                 {
538                     CGRect temp = NSRectToCGRect([screen frame]);
539                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
540                     *rect++ = temp;
541                 }
542             }
543             else
544                 return 1280; /* arbitrary value */
545         }
547         return primaryScreenHeight;
548     }
550     - (NSPoint) flippedMouseLocation:(NSPoint)point
551     {
552         /* This relies on the fact that Cocoa's mouse location points are
553            actually off by one (precisely because they were flipped from
554            Quartz screen coordinates using this same technique). */
555         point.y = [self primaryScreenHeight] - point.y;
556         return point;
557     }
559     - (void) flipRect:(NSRect*)rect
560     {
561         // We don't use -primaryScreenHeight here so there's no chance of having
562         // out-of-date cached info.  This method is called infrequently enough
563         // that getting the screen height each time is not prohibitively expensive.
564         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
565     }
567     - (WineWindow*) frontWineWindow
568     {
569         NSNumber* windowNumber;
570         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
571         {
572             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
573             if ([window isKindOfClass:[WineWindow class]] && [window screen])
574                 return (WineWindow*)window;
575         }
577         return nil;
578     }
580     - (void) adjustWindowLevels:(BOOL)active
581     {
582         NSArray* windowNumbers;
583         NSMutableArray* wineWindows;
584         NSNumber* windowNumber;
585         NSUInteger nextFloatingIndex = 0;
586         __block NSInteger maxLevel = NSIntegerMin;
587         __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
588         __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
589         __block WineWindow* prev = nil;
590         WineWindow* window;
592         if ([NSApp isHidden]) return;
594         windowNumbers = [NSWindow windowNumbersWithOptions:0];
595         wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
597         // For the most part, we rely on the window server's ordering of the windows
598         // to be authoritative.  The one exception is if the "floating" property of
599         // one of the windows has been changed, it may be in the wrong level and thus
600         // in the order.  This method is what's supposed to fix that up.  So build
601         // a list of Wine windows sorted first by floating-ness and then by order
602         // as indicated by the window server.
603         for (windowNumber in windowNumbers)
604         {
605             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
606             if ([window isKindOfClass:[WineWindow class]])
607             {
608                 if (window.floating)
609                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
610                 else
611                     [wineWindows addObject:window];
612             }
613         }
615         NSDisableScreenUpdates();
617         // Go from back to front so that all windows in front of one which is
618         // elevated for full-screen are also elevated.
619         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
620                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
621             WineWindow* window = (WineWindow*)obj;
622             NSInteger origLevel = [window level];
623             NSInteger newLevel = [window minimumLevelForActive:active];
625             if (window.floating)
626             {
627                 if (minFloatingLevel <= maxNonfloatingLevel)
628                     minFloatingLevel = maxNonfloatingLevel + 1;
629                 if (newLevel < minFloatingLevel)
630                     newLevel = minFloatingLevel;
631             }
633             if (newLevel < maxLevel)
634                 newLevel = maxLevel;
635             else
636                 maxLevel = newLevel;
638             if (!window.floating && maxNonfloatingLevel < newLevel)
639                 maxNonfloatingLevel = newLevel;
641             if (newLevel != origLevel)
642             {
643                 [window setLevel:newLevel];
645                 // -setLevel: puts the window at the front of its new level.  If
646                 // we decreased the level, that's good (it was in front of that
647                 // level before, so it should still be now).  But if we increased
648                 // the level, the window should be toward the back (but still
649                 // ahead of the previous windows we did this to).
650                 if (origLevel < newLevel)
651                 {
652                     if (prev)
653                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
654                     else
655                         [window orderBack:nil];
656                 }
657             }
659             prev = window;
660         }];
662         NSEnableScreenUpdates();
664         [wineWindows release];
666         // The above took care of the visible windows on the current space.  That
667         // leaves windows on other spaces, minimized windows, and windows which
668         // are not ordered in.  We want to leave windows on other spaces alone
669         // so the space remains just as they left it (when viewed in Exposé or
670         // Mission Control, for example).  We'll adjust the window levels again
671         // after we switch to another space, anyway.  Windows which aren't
672         // ordered in will be handled when we order them in.  Minimized windows
673         // on the current space should be set to the level they would have gotten
674         // if they were at the front of the windows with the same floating-ness,
675         // because that's where they'll go if/when they are unminimized.  Again,
676         // for good measure we'll adjust window levels again when a window is
677         // unminimized, too.
678         for (window in [NSApp windows])
679         {
680             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
681                 [window isOnActiveSpace])
682             {
683                 NSInteger origLevel = [window level];
684                 NSInteger newLevel = [window minimumLevelForActive:YES];
685                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
687                 if (newLevel < maxLevelForType)
688                     newLevel = maxLevelForType;
690                 if (newLevel != origLevel)
691                     [window setLevel:newLevel];
692             }
693         }
694     }
696     - (void) adjustWindowLevels
697     {
698         [self adjustWindowLevels:[NSApp isActive]];
699     }
701     - (void) updateFullscreenWindows
702     {
703         if (capture_displays_for_fullscreen && [NSApp isActive])
704         {
705             BOOL anyFullscreen = FALSE;
706             NSNumber* windowNumber;
707             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
708             {
709                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
710                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
711                 {
712                     anyFullscreen = TRUE;
713                     break;
714                 }
715             }
717             if (anyFullscreen)
718             {
719                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
720                     displaysCapturedForFullscreen = TRUE;
721             }
722             else if (displaysCapturedForFullscreen)
723             {
724                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
725                     displaysCapturedForFullscreen = FALSE;
726             }
727         }
728     }
730     - (void) activeSpaceDidChange
731     {
732         [self updateFullscreenWindows];
733         [self adjustWindowLevels];
734     }
736     - (void) sendDisplaysChanged:(BOOL)activating
737     {
738         macdrv_event* event;
739         WineEventQueue* queue;
741         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
742         event->displays_changed.activating = activating;
744         [eventQueuesLock lock];
746         // If we're activating, then we just need one of our threads to get the
747         // event, so it can send it directly to the desktop window.  Otherwise,
748         // we need all of the threads to get it because we don't know which owns
749         // the desktop window and only that one will do anything with it.
750         if (activating) event->deliver = 1;
752         for (queue in eventQueues)
753             [queue postEvent:event];
754         [eventQueuesLock unlock];
756         macdrv_release_event(event);
757     }
759     // We can compare two modes directly using CFEqual, but that may require that
760     // they are identical to a level that we don't need.  In particular, when the
761     // OS switches between the integrated and discrete GPUs, the set of display
762     // modes can change in subtle ways.  We're interested in whether two modes
763     // match in their most salient features, even if they aren't identical.
764     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
765     {
766         NSString *encoding1, *encoding2;
767         uint32_t ioflags1, ioflags2, different;
768         double refresh1, refresh2;
770         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
771         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
773 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
774         if (&CGDisplayModeGetPixelWidth != NULL &&
775             CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
776         if (&CGDisplayModeGetPixelHeight != NULL &&
777             CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
778 #endif
780         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
781         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
782         if (![encoding1 isEqualToString:encoding2]) return FALSE;
784         ioflags1 = CGDisplayModeGetIOFlags(mode1);
785         ioflags2 = CGDisplayModeGetIOFlags(mode2);
786         different = ioflags1 ^ ioflags2;
787         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
788                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
789             return FALSE;
791         refresh1 = CGDisplayModeGetRefreshRate(mode1);
792         if (refresh1 == 0) refresh1 = 60;
793         refresh2 = CGDisplayModeGetRefreshRate(mode2);
794         if (refresh2 == 0) refresh2 = 60;
795         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
797         return TRUE;
798     }
800     - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
801     {
802         NSMutableArray* ret = [NSMutableArray array];
803         NSDictionary* options = nil;
805 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
806         options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:TRUE]
807                                               forKey:(NSString*)kCGDisplayShowDuplicateLowResolutionModes];
808 #endif
810         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
811         for (id candidateModeObject in modes)
812         {
813             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
814             if ([self mode:candidateMode matchesMode:mode])
815                 [ret addObject:candidateModeObject];
816         }
817         return ret;
818     }
820     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
821     {
822         BOOL ret = FALSE;
823         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
824         CGDisplayModeRef originalMode;
826         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
828         if (originalMode && [self mode:mode matchesMode:originalMode])
829         {
830             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
831             {
832                 CGRestorePermanentDisplayConfiguration();
833                 if (!displaysCapturedForFullscreen)
834                     CGReleaseAllDisplays();
835                 [originalDisplayModes removeAllObjects];
836                 ret = TRUE;
837             }
838             else // ... otherwise, try to restore just the one display
839             {
840                 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
841                 {
842                     mode = (CGDisplayModeRef)modeObject;
843                     if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
844                     {
845                         [originalDisplayModes removeObjectForKey:displayIDKey];
846                         ret = TRUE;
847                         break;
848                     }
849                 }
850             }
851         }
852         else
853         {
854             CGDisplayModeRef currentMode;
855             NSArray* modes;
857             currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
858             if (!currentMode)
859                 currentMode = CGDisplayCopyDisplayMode(displayID);
860             if (!currentMode) // Invalid display ID
861                 return FALSE;
863             if ([self mode:mode matchesMode:currentMode]) // Already there!
864             {
865                 CGDisplayModeRelease(currentMode);
866                 return TRUE;
867             }
869             CGDisplayModeRelease(currentMode);
870             currentMode = NULL;
872             modes = [self modesMatchingMode:mode forDisplay:displayID];
873             if (!modes.count)
874                 return FALSE;
876             [self transformProcessToForeground];
878             BOOL active = [NSApp isActive];
880             if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
881                 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
882             {
883                 if (active)
884                 {
885                     // If we get here, we have the displays captured.  If we don't
886                     // know the original mode of the display, the current mode must
887                     // be the original.  We should re-query the current mode since
888                     // another process could have changed it between when we last
889                     // checked and when we captured the displays.
890                     if (!originalMode)
891                         originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
893                     if (originalMode)
894                     {
895                         for (id modeObject in modes)
896                         {
897                             mode = (CGDisplayModeRef)modeObject;
898                             if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
899                             {
900                                 ret = TRUE;
901                                 break;
902                             }
903                         }
904                     }
905                     if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
906                         [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
907                     else if (![originalDisplayModes count])
908                     {
909                         CGRestorePermanentDisplayConfiguration();
910                         if (!displaysCapturedForFullscreen)
911                             CGReleaseAllDisplays();
912                     }
914                     if (currentMode)
915                         CGDisplayModeRelease(currentMode);
916                 }
917                 else
918                 {
919                     [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
920                     ret = TRUE;
921                 }
922             }
923         }
925         if (ret)
926             [self adjustWindowLevels];
928         return ret;
929     }
931     - (BOOL) areDisplaysCaptured
932     {
933         return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
934     }
936     - (void) updateCursor:(BOOL)force
937     {
938         if (force || lastTargetWindow)
939         {
940             if (clientWantsCursorHidden && !cursorHidden)
941             {
942                 [NSCursor hide];
943                 cursorHidden = TRUE;
944             }
946             if (!cursorIsCurrent)
947             {
948                 [cursor set];
949                 cursorIsCurrent = TRUE;
950             }
952             if (!clientWantsCursorHidden && cursorHidden)
953             {
954                 [NSCursor unhide];
955                 cursorHidden = FALSE;
956             }
957         }
958         else
959         {
960             if (cursorIsCurrent)
961             {
962                 [[NSCursor arrowCursor] set];
963                 cursorIsCurrent = FALSE;
964             }
965             if (cursorHidden)
966             {
967                 [NSCursor unhide];
968                 cursorHidden = FALSE;
969             }
970         }
971     }
973     - (void) hideCursor
974     {
975         if (!clientWantsCursorHidden)
976         {
977             clientWantsCursorHidden = TRUE;
978             [self updateCursor:TRUE];
979         }
980     }
982     - (void) unhideCursor
983     {
984         if (clientWantsCursorHidden)
985         {
986             clientWantsCursorHidden = FALSE;
987             [self updateCursor:FALSE];
988         }
989     }
991     - (void) setCursor:(NSCursor*)newCursor
992     {
993         if (newCursor != cursor)
994         {
995             [cursor release];
996             cursor = [newCursor retain];
997             cursorIsCurrent = FALSE;
998             [self updateCursor:FALSE];
999         }
1000     }
1002     - (void) setCursor
1003     {
1004         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
1005         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
1006         CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
1007         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
1008         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
1009         CGPoint hotSpot;
1011         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
1012             hotSpot = CGPointZero;
1013         hotSpot = cgpoint_mac_from_win(hotSpot);
1014         self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1015         [image release];
1016         [self unhideCursor];
1017     }
1019     - (void) nextCursorFrame:(NSTimer*)theTimer
1020     {
1021         NSDictionary* frame;
1022         NSTimeInterval duration;
1023         NSDate* date;
1025         cursorFrame++;
1026         if (cursorFrame >= [cursorFrames count])
1027             cursorFrame = 0;
1028         [self setCursor];
1030         frame = [cursorFrames objectAtIndex:cursorFrame];
1031         duration = [[frame objectForKey:@"duration"] doubleValue];
1032         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1033         [cursorTimer setFireDate:date];
1034     }
1036     - (void) setCursorWithFrames:(NSArray*)frames
1037     {
1038         if (self.cursorFrames == frames)
1039             return;
1041         self.cursorFrames = frames;
1042         cursorFrame = 0;
1043         [cursorTimer invalidate];
1044         self.cursorTimer = nil;
1046         if ([frames count])
1047         {
1048             if ([frames count] > 1)
1049             {
1050                 NSDictionary* frame = [frames objectAtIndex:0];
1051                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
1052                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1053                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1054                                                              interval:1000000
1055                                                                target:self
1056                                                              selector:@selector(nextCursorFrame:)
1057                                                              userInfo:nil
1058                                                               repeats:YES] autorelease];
1059                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1060             }
1062             [self setCursor];
1063         }
1064     }
1066     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1067     {
1068         NSImage* nsimage = nil;
1070         if ([images count])
1071         {
1072             NSSize bestSize = NSZeroSize;
1073             id image;
1075             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1077             for (image in images)
1078             {
1079                 CGImageRef cgimage = (CGImageRef)image;
1080                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1081                 if (imageRep)
1082                 {
1083                     NSSize size = [imageRep size];
1085                     [nsimage addRepresentation:imageRep];
1086                     [imageRep release];
1088                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1089                         bestSize = size;
1090                 }
1091             }
1093             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1094                 [nsimage setSize:bestSize];
1095             else
1096                 nsimage = nil;
1097         }
1099         self.applicationIcon = nsimage;
1100     }
1102     - (void) handleCommandTab
1103     {
1104         if ([NSApp isActive])
1105         {
1106             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1107             NSRunningApplication* app;
1108             NSRunningApplication* otherValidApp = nil;
1110             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1111             {
1112                 NSNumber* displayID;
1113                 for (displayID in originalDisplayModes)
1114                 {
1115                     CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1116                     [latentDisplayModes setObject:(id)mode forKey:displayID];
1117                     CGDisplayModeRelease(mode);
1118                 }
1120                 CGRestorePermanentDisplayConfiguration();
1121                 CGReleaseAllDisplays();
1122                 [originalDisplayModes removeAllObjects];
1123                 displaysCapturedForFullscreen = FALSE;
1124             }
1126             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1127             {
1128                 if (![app isEqual:thisApp] && !app.terminated &&
1129                     app.activationPolicy == NSApplicationActivationPolicyRegular)
1130                 {
1131                     if (!app.hidden)
1132                     {
1133                         // There's another visible app.  Just hide ourselves and let
1134                         // the system activate the other app.
1135                         [NSApp hide:self];
1136                         return;
1137                     }
1139                     if (!otherValidApp)
1140                         otherValidApp = app;
1141                 }
1142             }
1144             // Didn't find a visible GUI app.  Try the Finder or, if that's not
1145             // running, the first hidden GUI app.  If even that doesn't work, we
1146             // just fail to switch and remain the active app.
1147             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1148             if (!app) app = otherValidApp;
1149             [app unhide];
1150             [app activateWithOptions:0];
1151         }
1152     }
1154     /*
1155      * ---------- Cursor clipping methods ----------
1156      *
1157      * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1158      * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1159      * equivalent: CGAssociateMouseAndMouseCursorPosition(false).  For the
1160      * general case, we leverage that.  We disassociate mouse movements from
1161      * the cursor position and then move the cursor manually, keeping it within
1162      * the clipping rectangle.
1163      *
1164      * Moving the cursor manually isn't enough.  We need to modify the event
1165      * stream so that the events have the new location, too.  We need to do
1166      * this at a point before the events enter Cocoa, so that Cocoa will assign
1167      * the correct window to the event.  So, we install a Quartz event tap to
1168      * do that.
1169      *
1170      * Also, there's a complication when we move the cursor.  We use
1171      * CGWarpMouseCursorPosition().  That doesn't generate mouse movement
1172      * events, but the change of cursor position is incorporated into the
1173      * deltas of the next mouse move event.  When the mouse is disassociated
1174      * from the cursor position, we need the deltas to only reflect actual
1175      * device movement, not programmatic changes.  So, the event tap cancels
1176      * out the change caused by our calls to CGWarpMouseCursorPosition().
1177      */
1178     - (void) clipCursorLocation:(CGPoint*)location
1179     {
1180         if (location->x < CGRectGetMinX(cursorClipRect))
1181             location->x = CGRectGetMinX(cursorClipRect);
1182         if (location->y < CGRectGetMinY(cursorClipRect))
1183             location->y = CGRectGetMinY(cursorClipRect);
1184         if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1185             location->x = CGRectGetMaxX(cursorClipRect) - 1;
1186         if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1187             location->y = CGRectGetMaxY(cursorClipRect) - 1;
1188     }
1190     - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1191     {
1192         CGPoint oldLocation;
1194         if (currentLocation)
1195             oldLocation = *currentLocation;
1196         else
1197             oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1199         if (!CGPointEqualToPoint(oldLocation, *newLocation))
1200         {
1201             WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1202             CGError err;
1204             warpRecord.from = oldLocation;
1205             warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1207             /* Actually move the cursor. */
1208             err = CGWarpMouseCursorPosition(*newLocation);
1209             if (err != kCGErrorSuccess)
1210                 return FALSE;
1212             warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1213             *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1215             if (!CGPointEqualToPoint(oldLocation, *newLocation))
1216             {
1217                 warpRecord.to = *newLocation;
1218                 [warpRecords addObject:warpRecord];
1219             }
1220         }
1222         return TRUE;
1223     }
1225     - (BOOL) isMouseMoveEventType:(CGEventType)type
1226     {
1227         switch(type)
1228         {
1229         case kCGEventMouseMoved:
1230         case kCGEventLeftMouseDragged:
1231         case kCGEventRightMouseDragged:
1232         case kCGEventOtherMouseDragged:
1233             return TRUE;
1234         default:
1235             return FALSE;
1236         }
1237     }
1239     - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1240     {
1241         int warpsFinished = 0;
1242         for (WarpRecord* warpRecord in warpRecords)
1243         {
1244             if (warpRecord.timeAfter < eventTime ||
1245                 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1246                 warpsFinished++;
1247             else
1248                 break;
1249         }
1251         return warpsFinished;
1252     }
1254     - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1255                                 type:(CGEventType)type
1256                                event:(CGEventRef)event
1257     {
1258         CGEventTimestamp eventTime;
1259         CGPoint eventLocation, cursorLocation;
1261         if (type == kCGEventTapDisabledByUserInput)
1262             return event;
1263         if (type == kCGEventTapDisabledByTimeout)
1264         {
1265             CGEventTapEnable(cursorClippingEventTap, TRUE);
1266             return event;
1267         }
1269         if (!clippingCursor)
1270             return event;
1272         eventTime = CGEventGetTimestamp(event);
1273         lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1275         eventLocation = CGEventGetLocation(event);
1277         cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1279         if ([self isMouseMoveEventType:type])
1280         {
1281             double deltaX, deltaY;
1282             int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1283             int i;
1285             deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1286             deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1288             for (i = 0; i < warpsFinished; i++)
1289             {
1290                 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1291                 deltaX -= warpRecord.to.x - warpRecord.from.x;
1292                 deltaY -= warpRecord.to.y - warpRecord.from.y;
1293                 [warpRecords removeObjectAtIndex:0];
1294             }
1296             if (warpsFinished)
1297             {
1298                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1299                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1300             }
1302             synthesizedLocation.x += deltaX;
1303             synthesizedLocation.y += deltaY;
1304         }
1306         // If the event is destined for another process, don't clip it.  This may
1307         // happen if the user activates Exposé or Mission Control.  In that case,
1308         // our app does not resign active status, so clipping is still in effect,
1309         // but the cursor should not actually be clipped.
1310         //
1311         // In addition, the fact that mouse moves may have been delivered to a
1312         // different process means we have to treat the next one we receive as
1313         // absolute rather than relative.
1314         if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1315             [self clipCursorLocation:&synthesizedLocation];
1316         else
1317             lastSetCursorPositionTime = lastEventTapEventTime;
1319         [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1320         if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1321             CGEventSetLocation(event, synthesizedLocation);
1323         return event;
1324     }
1326     CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1327                                        CGEventRef event, void *refcon)
1328     {
1329         WineApplicationController* controller = refcon;
1330         return [controller eventTapWithProxy:proxy type:type event:event];
1331     }
1333     - (BOOL) installEventTap
1334     {
1335         ProcessSerialNumber psn;
1336         OSErr err;
1337         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
1338                            CGEventMaskBit(kCGEventLeftMouseUp)          |
1339                            CGEventMaskBit(kCGEventRightMouseDown)       |
1340                            CGEventMaskBit(kCGEventRightMouseUp)         |
1341                            CGEventMaskBit(kCGEventMouseMoved)           |
1342                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
1343                            CGEventMaskBit(kCGEventRightMouseDragged)    |
1344                            CGEventMaskBit(kCGEventOtherMouseDown)       |
1345                            CGEventMaskBit(kCGEventOtherMouseUp)         |
1346                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
1347                            CGEventMaskBit(kCGEventScrollWheel);
1348         CFRunLoopSourceRef source;
1349         void* appServices;
1350         OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1352         if (cursorClippingEventTap)
1353             return TRUE;
1355         // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1356         // framework with dlsym() because the Win32 function of the same name
1357         // obscures it.
1358         appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1359         if (!appServices)
1360             return FALSE;
1362         pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1363         if (!pGetCurrentProcess)
1364         {
1365             dlclose(appServices);
1366             return FALSE;
1367         }
1369         err = pGetCurrentProcess(&psn);
1370         dlclose(appServices);
1371         if (err != noErr)
1372             return FALSE;
1374         // We create an annotated session event tap rather than a process-specific
1375         // event tap because we need to programmatically move the cursor even when
1376         // mouse moves are directed to other processes.  We disable our tap when
1377         // other processes are active, but things like Exposé are handled by other
1378         // processes even when we remain active.
1379         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1380             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1381         if (!cursorClippingEventTap)
1382             return FALSE;
1384         CGEventTapEnable(cursorClippingEventTap, FALSE);
1386         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1387         if (!source)
1388         {
1389             CFRelease(cursorClippingEventTap);
1390             cursorClippingEventTap = NULL;
1391             return FALSE;
1392         }
1394         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1395         CFRelease(source);
1396         return TRUE;
1397     }
1399     - (BOOL) setCursorPosition:(CGPoint)pos
1400     {
1401         BOOL ret;
1403         if ([windowsBeingDragged count])
1404             ret = FALSE;
1405         else if (clippingCursor)
1406         {
1407             [self clipCursorLocation:&pos];
1409             ret = [self warpCursorTo:&pos from:NULL];
1410             synthesizedLocation = pos;
1411             if (ret)
1412             {
1413                 // We want to discard mouse-move events that have already been
1414                 // through the event tap, because it's too late to account for
1415                 // the setting of the cursor position with them.  However, the
1416                 // events that may be queued with times after that but before
1417                 // the above warp can still be used.  So, use the last event
1418                 // tap event time so that -sendEvent: doesn't discard them.
1419                 lastSetCursorPositionTime = lastEventTapEventTime;
1420             }
1421         }
1422         else
1423         {
1424             // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1425             // the mouse from the cursor position for 0.25 seconds.  This means
1426             // that mouse movement during that interval doesn't move the cursor
1427             // and events carry a constant location (the warped-to position)
1428             // even though they have delta values.  For apps which warp the
1429             // cursor frequently (like after every mouse move), this makes
1430             // cursor movement horribly laggy and jerky, as only a fraction of
1431             // mouse move events have any effect.
1432             //
1433             // On some versions of OS X, it's sufficient to forcibly reassociate
1434             // the mouse and cursor position.  On others, it's necessary to set
1435             // the local events suppression interval to 0 for the warp.  That's
1436             // deprecated, but I'm not aware of any other way.  For good
1437             // measure, we do both.
1438             CGSetLocalEventsSuppressionInterval(0);
1439             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1440             CGSetLocalEventsSuppressionInterval(0.25);
1441             if (ret)
1442             {
1443                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1445                 CGAssociateMouseAndMouseCursorPosition(true);
1446             }
1447         }
1449         if (ret)
1450         {
1451             WineEventQueue* queue;
1453             // Discard all pending mouse move events.
1454             [eventQueuesLock lock];
1455             for (queue in eventQueues)
1456             {
1457                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1458                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1459                                        forWindow:nil];
1460                 [queue resetMouseEventPositions:pos];
1461             }
1462             [eventQueuesLock unlock];
1463         }
1465         return ret;
1466     }
1468     - (void) activateCursorClipping
1469     {
1470         if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1471         {
1472             CGEventTapEnable(cursorClippingEventTap, TRUE);
1473             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1474         }
1475     }
1477     - (void) deactivateCursorClipping
1478     {
1479         if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1480         {
1481             CGEventTapEnable(cursorClippingEventTap, FALSE);
1482             [warpRecords removeAllObjects];
1483             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1484         }
1485     }
1487     - (void) updateCursorClippingState
1488     {
1489         if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1490             [self activateCursorClipping];
1491         else
1492             [self deactivateCursorClipping];
1493     }
1495     - (void) updateWindowsForCursorClipping
1496     {
1497         WineWindow* window;
1498         for (window in [NSApp windows])
1499         {
1500             if ([window isKindOfClass:[WineWindow class]])
1501                 [window updateForCursorClipping];
1502         }
1503     }
1505     - (BOOL) startClippingCursor:(CGRect)rect
1506     {
1507         CGError err;
1509         if (!cursorClippingEventTap && ![self installEventTap])
1510             return FALSE;
1512         if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1513             CGEventTapIsEnabled(cursorClippingEventTap))
1514             return TRUE;
1516         err = CGAssociateMouseAndMouseCursorPosition(false);
1517         if (err != kCGErrorSuccess)
1518             return FALSE;
1520         clippingCursor = TRUE;
1521         cursorClipRect = rect;
1522         [self updateCursorClippingState];
1523         [self updateWindowsForCursorClipping];
1525         return TRUE;
1526     }
1528     - (BOOL) stopClippingCursor
1529     {
1530         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1531         if (err != kCGErrorSuccess)
1532             return FALSE;
1534         clippingCursor = FALSE;
1535         [self updateCursorClippingState];
1536         [self updateWindowsForCursorClipping];
1538         return TRUE;
1539     }
1541     - (BOOL) isKeyPressed:(uint16_t)keyCode
1542     {
1543         int bits = sizeof(pressedKeyCodes[0]) * 8;
1544         int index = keyCode / bits;
1545         uint32_t mask = 1 << (keyCode % bits);
1546         return (pressedKeyCodes[index] & mask) != 0;
1547     }
1549     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1550     {
1551         int bits = sizeof(pressedKeyCodes[0]) * 8;
1552         int index = keyCode / bits;
1553         uint32_t mask = 1 << (keyCode % bits);
1554         if (pressed)
1555             pressedKeyCodes[index] |= mask;
1556         else
1557             pressedKeyCodes[index] &= ~mask;
1558     }
1560     - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1561     {
1562         if (dragged)
1563             [windowsBeingDragged addObject:window];
1564         else
1565             [windowsBeingDragged removeObject:window];
1566         [self updateCursorClippingState];
1567     }
1569     - (void) windowWillOrderOut:(WineWindow*)window
1570     {
1571         if ([windowsBeingDragged containsObject:window])
1572         {
1573             [self window:window isBeingDragged:NO];
1575             macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1576             [window.queue postEvent:event];
1577             macdrv_release_event(event);
1578         }
1579     }
1581     - (void) handleWindowDrag:(NSEvent*)anEvent begin:(BOOL)begin
1582     {
1583         WineWindow* window = (WineWindow*)[anEvent window];
1584         if ([window isKindOfClass:[WineWindow class]])
1585         {
1586             macdrv_event* event;
1587             int eventType;
1589             if (begin)
1590             {
1591                 [windowsBeingDragged addObject:window];
1592                 eventType = WINDOW_DRAG_BEGIN;
1593             }
1594             else
1595             {
1596                 [windowsBeingDragged removeObject:window];
1597                 eventType = WINDOW_DRAG_END;
1598             }
1599             [self updateCursorClippingState];
1601             event = macdrv_create_event(eventType, window);
1602             if (eventType == WINDOW_DRAG_BEGIN)
1603                 event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
1604             [window.queue postEvent:event];
1605             macdrv_release_event(event);
1606         }
1607     }
1609     - (void) handleMouseMove:(NSEvent*)anEvent
1610     {
1611         WineWindow* targetWindow;
1612         BOOL drag = [anEvent type] != NSEventTypeMouseMoved;
1614         if ([windowsBeingDragged count])
1615             targetWindow = nil;
1616         else if (mouseCaptureWindow)
1617             targetWindow = mouseCaptureWindow;
1618         else if (drag)
1619             targetWindow = (WineWindow*)[anEvent window];
1620         else
1621         {
1622             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1623                event indicates its window is the main window, even if the cursor is
1624                over a different window.  Find the actual WineWindow that is under the
1625                cursor and post the event as being for that window. */
1626             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1627             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1628             NSInteger windowUnderNumber;
1630             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1631                                   belowWindowWithWindowNumber:0];
1632             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1633             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1634                 targetWindow = nil;
1635         }
1637         if ([targetWindow isKindOfClass:[WineWindow class]])
1638         {
1639             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1640             macdrv_event* event;
1641             BOOL absolute;
1643             // If we recently warped the cursor (other than in our cursor-clipping
1644             // event tap), discard mouse move events until we see an event which is
1645             // later than that time.
1646             if (lastSetCursorPositionTime)
1647             {
1648                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1649                     return;
1651                 lastSetCursorPositionTime = 0;
1652                 forceNextMouseMoveAbsolute = TRUE;
1653             }
1655             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1656             {
1657                 absolute = TRUE;
1658                 forceNextMouseMoveAbsolute = FALSE;
1659             }
1660             else
1661             {
1662                 // Send absolute move events if the cursor is in the interior of
1663                 // its range.  Only send relative moves if the cursor is pinned to
1664                 // the boundaries of where it can go.  We compute the position
1665                 // that's one additional point in the direction of movement.  If
1666                 // that is outside of the clipping rect or desktop region (the
1667                 // union of the screen frames), then we figure the cursor would
1668                 // have moved outside if it could but it was pinned.
1669                 CGPoint computedPoint = point;
1670                 CGFloat deltaX = [anEvent deltaX];
1671                 CGFloat deltaY = [anEvent deltaY];
1673                 if (deltaX > 0.001)
1674                     computedPoint.x++;
1675                 else if (deltaX < -0.001)
1676                     computedPoint.x--;
1678                 if (deltaY > 0.001)
1679                     computedPoint.y++;
1680                 else if (deltaY < -0.001)
1681                     computedPoint.y--;
1683                 // Assume cursor is pinned for now
1684                 absolute = FALSE;
1685                 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1686                 {
1687                     const CGRect* rects;
1688                     NSUInteger count, i;
1690                     // Caches screenFrameCGRects if necessary
1691                     [self primaryScreenHeight];
1693                     rects = [screenFrameCGRects bytes];
1694                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1696                     for (i = 0; i < count; i++)
1697                     {
1698                         if (CGRectContainsPoint(rects[i], computedPoint))
1699                         {
1700                             absolute = TRUE;
1701                             break;
1702                         }
1703                     }
1704                 }
1705             }
1707             if (absolute)
1708             {
1709                 if (clippingCursor)
1710                     [self clipCursorLocation:&point];
1711                 point = cgpoint_win_from_mac(point);
1713                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1714                 event->mouse_moved.x = floor(point.x);
1715                 event->mouse_moved.y = floor(point.y);
1717                 mouseMoveDeltaX = 0;
1718                 mouseMoveDeltaY = 0;
1719             }
1720             else
1721             {
1722                 double scale = retina_on ? 2 : 1;
1724                 /* Add event delta to accumulated delta error */
1725                 /* deltaY is already flipped */
1726                 mouseMoveDeltaX += [anEvent deltaX];
1727                 mouseMoveDeltaY += [anEvent deltaY];
1729                 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1730                 event->mouse_moved.x = mouseMoveDeltaX * scale;
1731                 event->mouse_moved.y = mouseMoveDeltaY * scale;
1733                 /* Keep the remainder after integer truncation. */
1734                 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1735                 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1736             }
1738             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1739             {
1740                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1741                 event->mouse_moved.drag = drag;
1743                 [targetWindow.queue postEvent:event];
1744             }
1746             macdrv_release_event(event);
1748             lastTargetWindow = targetWindow;
1749         }
1750         else
1751             lastTargetWindow = nil;
1753         [self updateCursor:FALSE];
1754     }
1756     - (void) handleMouseButton:(NSEvent*)theEvent
1757     {
1758         WineWindow* window = (WineWindow*)[theEvent window];
1759         NSEventType type = [theEvent type];
1760         WineWindow* windowBroughtForward = nil;
1761         BOOL process = FALSE;
1763         if (type == NSEventTypeLeftMouseUp && [windowsBeingDragged count])
1764             [self handleWindowDrag:theEvent begin:NO];
1766         if ([window isKindOfClass:[WineWindow class]] &&
1767             type == NSEventTypeLeftMouseDown &&
1768             ![theEvent wine_commandKeyDown])
1769         {
1770             NSWindowButton windowButton;
1772             windowBroughtForward = window;
1774             /* Any left-click on our window anyplace other than the close or
1775                minimize buttons will bring it forward. */
1776             for (windowButton = NSWindowCloseButton;
1777                  windowButton <= NSWindowMiniaturizeButton;
1778                  windowButton++)
1779             {
1780                 NSButton* button = [window standardWindowButton:windowButton];
1781                 if (button)
1782                 {
1783                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1784                     if ([button mouse:point inRect:[button bounds]])
1785                     {
1786                         windowBroughtForward = nil;
1787                         break;
1788                     }
1789                 }
1790             }
1791         }
1793         if ([windowsBeingDragged count])
1794             window = nil;
1795         else if (mouseCaptureWindow)
1796             window = mouseCaptureWindow;
1798         if ([window isKindOfClass:[WineWindow class]])
1799         {
1800             BOOL pressed = (type == NSEventTypeLeftMouseDown ||
1801                             type == NSEventTypeRightMouseDown ||
1802                             type == NSEventTypeOtherMouseDown);
1803             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1805             if (clippingCursor)
1806                 [self clipCursorLocation:&pt];
1808             if (pressed)
1809             {
1810                 if (mouseCaptureWindow)
1811                     process = TRUE;
1812                 else
1813                 {
1814                     // Test if the click was in the window's content area.
1815                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1816                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1817                     process = NSMouseInRect(nspoint, contentRect, NO);
1818                     if (process && [window styleMask] & NSWindowStyleMaskResizable)
1819                     {
1820                         // Ignore clicks in the grow box (resize widget).
1821                         HIPoint origin = { 0, 0 };
1822                         HIThemeGrowBoxDrawInfo info = { 0 };
1823                         HIRect bounds;
1824                         OSStatus status;
1826                         info.kind = kHIThemeGrowBoxKindNormal;
1827                         info.direction = kThemeGrowRight | kThemeGrowDown;
1828                         if ([window styleMask] & NSWindowStyleMaskUtilityWindow)
1829                             info.size = kHIThemeGrowBoxSizeSmall;
1830                         else
1831                             info.size = kHIThemeGrowBoxSizeNormal;
1833                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1834                         if (status == noErr)
1835                         {
1836                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1837                                                         NSMinY(contentRect),
1838                                                         bounds.size.width,
1839                                                         bounds.size.height);
1840                             process = !NSMouseInRect(nspoint, growBox, NO);
1841                         }
1842                     }
1843                 }
1844                 if (process)
1845                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1846             }
1847             else
1848             {
1849                 NSEventType downType = type - 1;
1850                 NSUInteger downMask = NSEventMaskFromType(downType);
1851                 process = (unmatchedMouseDowns & downMask) != 0;
1852                 unmatchedMouseDowns &= ~downMask;
1853             }
1855             if (process)
1856             {
1857                 macdrv_event* event;
1859                 pt = cgpoint_win_from_mac(pt);
1861                 event = macdrv_create_event(MOUSE_BUTTON, window);
1862                 event->mouse_button.button = [theEvent buttonNumber];
1863                 event->mouse_button.pressed = pressed;
1864                 event->mouse_button.x = floor(pt.x);
1865                 event->mouse_button.y = floor(pt.y);
1866                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1868                 [window.queue postEvent:event];
1870                 macdrv_release_event(event);
1871             }
1872         }
1874         if (windowBroughtForward)
1875         {
1876             WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1877             NSInteger ancestorNumber = [ancestor windowNumber];
1878             NSInteger ancestorLevel = [ancestor level];
1880             for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1881             {
1882                 NSInteger windowNumber = [windowNumberObject integerValue];
1883                 if (windowNumber == ancestorNumber)
1884                     break;
1885                 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1886                 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1887                     [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1888                 {
1889                     [ancestor postBroughtForwardEvent];
1890                     break;
1891                 }
1892             }
1893             if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate)
1894                 [self windowGotFocus:windowBroughtForward];
1895         }
1897         // Since mouse button events deliver absolute cursor position, the
1898         // accumulating delta from move events is invalidated.  Make sure
1899         // next mouse move event starts over from an absolute baseline.
1900         // Also, it's at least possible that the title bar widgets (e.g. close
1901         // button, etc.) could enter an internal event loop on a mouse down that
1902         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1903         // dragged events and, after that, any notion of the cursor position
1904         // computed from accumulating deltas would be wrong.
1905         forceNextMouseMoveAbsolute = TRUE;
1906     }
1908     - (void) handleScrollWheel:(NSEvent*)theEvent
1909     {
1910         WineWindow* window;
1912         if (mouseCaptureWindow)
1913             window = mouseCaptureWindow;
1914         else
1915             window = (WineWindow*)[theEvent window];
1917         if ([window isKindOfClass:[WineWindow class]])
1918         {
1919             CGEventRef cgevent = [theEvent CGEvent];
1920             CGPoint pt = CGEventGetLocation(cgevent);
1921             BOOL process;
1923             if (clippingCursor)
1924                 [self clipCursorLocation:&pt];
1926             if (mouseCaptureWindow)
1927                 process = TRUE;
1928             else
1929             {
1930                 // Only process the event if it was in the window's content area.
1931                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1932                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1933                 process = NSMouseInRect(nspoint, contentRect, NO);
1934             }
1936             if (process)
1937             {
1938                 macdrv_event* event;
1939                 double x, y;
1940                 BOOL continuous = FALSE;
1942                 pt = cgpoint_win_from_mac(pt);
1944                 event = macdrv_create_event(MOUSE_SCROLL, window);
1945                 event->mouse_scroll.x = floor(pt.x);
1946                 event->mouse_scroll.y = floor(pt.y);
1947                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1949                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1950                 {
1951                     continuous = TRUE;
1953                     /* Continuous scroll wheel events come from high-precision scrolling
1954                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1955                        For these, we can get more precise data from the CGEvent API. */
1956                     /* Axis 1 is vertical, axis 2 is horizontal. */
1957                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1958                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1959                 }
1960                 else
1961                 {
1962                     double pixelsPerLine = 10;
1963                     CGEventSourceRef source;
1965                     /* The non-continuous values are in units of "lines", not pixels. */
1966                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1967                     {
1968                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1969                         CFRelease(source);
1970                     }
1972                     x = pixelsPerLine * [theEvent deltaX];
1973                     y = pixelsPerLine * [theEvent deltaY];
1974                 }
1976                 /* Mac: negative is right or down, positive is left or up.
1977                    Win32: negative is left or down, positive is right or up.
1978                    So, negate the X scroll value to translate. */
1979                 x = -x;
1981                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1982                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1983                    6 times the pixel value. */
1984                 x *= 6;
1985                 y *= 6;
1987                 if (use_precise_scrolling)
1988                 {
1989                     event->mouse_scroll.x_scroll = x;
1990                     event->mouse_scroll.y_scroll = y;
1992                     if (!continuous)
1993                     {
1994                         /* For non-continuous "clicky" wheels, if there was any motion, make
1995                            sure there was at least WHEEL_DELTA motion.  This is so, at slow
1996                            speeds where the system's acceleration curve is actually reducing the
1997                            scroll distance, the user is sure to get some action out of each click.
1998                            For example, this is important for rotating though weapons in a
1999                            first-person shooter. */
2000                         if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
2001                             event->mouse_scroll.x_scroll = 120;
2002                         else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
2003                             event->mouse_scroll.x_scroll = -120;
2005                         if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
2006                             event->mouse_scroll.y_scroll = 120;
2007                         else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
2008                             event->mouse_scroll.y_scroll = -120;
2009                     }
2010                 }
2011                 else
2012                 {
2013                     /* If it's been a while since the last scroll event or if the scrolling has
2014                        reversed direction, reset the accumulated scroll value. */
2015                     if ([theEvent timestamp] - lastScrollTime > 1)
2016                         accumScrollX = accumScrollY = 0;
2017                     else
2018                     {
2019                         /* The accumulated scroll value is in the opposite direction/sign of the last
2020                            scroll.  That's because it's the "debt" resulting from over-scrolling in
2021                            that direction.  We accumulate by adding in the scroll amount and then, if
2022                            it has the same sign as the scroll value, we subtract any whole or partial
2023                            WHEEL_DELTAs, leaving it 0 or the opposite sign.  So, the user switched
2024                            scroll direction if the accumulated debt and the new scroll value have the
2025                            same sign. */
2026                         if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
2027                             accumScrollX = 0;
2028                         if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
2029                             accumScrollY = 0;
2030                     }
2031                     lastScrollTime = [theEvent timestamp];
2033                     accumScrollX += x;
2034                     accumScrollY += y;
2036                     if (accumScrollX > 0 && x > 0)
2037                         event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
2038                     if (accumScrollX < 0 && x < 0)
2039                         event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
2040                     if (accumScrollY > 0 && y > 0)
2041                         event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
2042                     if (accumScrollY < 0 && y < 0)
2043                         event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
2045                     accumScrollX -= event->mouse_scroll.x_scroll;
2046                     accumScrollY -= event->mouse_scroll.y_scroll;
2047                 }
2049                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
2050                     [window.queue postEvent:event];
2052                 macdrv_release_event(event);
2054                 // Since scroll wheel events deliver absolute cursor position, the
2055                 // accumulating delta from move events is invalidated.  Make sure next
2056                 // mouse move event starts over from an absolute baseline.
2057                 forceNextMouseMoveAbsolute = TRUE;
2058             }
2059         }
2060     }
2062     // Returns TRUE if the event was handled and caller should do nothing more
2063     // with it.  Returns FALSE if the caller should process it as normal and
2064     // then call -didSendEvent:.
2065     - (BOOL) handleEvent:(NSEvent*)anEvent
2066     {
2067         BOOL ret = FALSE;
2068         NSEventType type = [anEvent type];
2070         if (type == NSEventTypeFlagsChanged)
2071             self.lastFlagsChanged = anEvent;
2072         else if (type == NSEventTypeMouseMoved || type == NSEventTypeLeftMouseDragged ||
2073                  type == NSEventTypeRightMouseDragged || type == NSEventTypeOtherMouseDragged)
2074         {
2075             [self handleMouseMove:anEvent];
2076             ret = mouseCaptureWindow && ![windowsBeingDragged count];
2077         }
2078         else if (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp ||
2079                  type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp ||
2080                  type == NSEventTypeOtherMouseDown || type == NSEventTypeOtherMouseUp)
2081         {
2082             [self handleMouseButton:anEvent];
2083             ret = mouseCaptureWindow && ![windowsBeingDragged count];
2084         }
2085         else if (type == NSEventTypeScrollWheel)
2086         {
2087             [self handleScrollWheel:anEvent];
2088             ret = mouseCaptureWindow != nil;
2089         }
2090         else if (type == NSEventTypeKeyDown)
2091         {
2092             // -[NSApplication sendEvent:] seems to consume presses of the Help
2093             // key (Insert key on PC keyboards), so we have to bypass it and
2094             // send the event directly to the window.
2095             if (anEvent.keyCode == kVK_Help)
2096             {
2097                 [anEvent.window sendEvent:anEvent];
2098                 ret = TRUE;
2099             }
2100         }
2101         else if (type == NSEventTypeKeyUp)
2102         {
2103             uint16_t keyCode = [anEvent keyCode];
2104             if ([self isKeyPressed:keyCode])
2105             {
2106                 WineWindow* window = (WineWindow*)[anEvent window];
2107                 [self noteKey:keyCode pressed:FALSE];
2108                 if ([window isKindOfClass:[WineWindow class]])
2109                     [window postKeyEvent:anEvent];
2110             }
2111         }
2112         else if (type == NSEventTypeAppKitDefined)
2113         {
2114             short subtype = [anEvent subtype];
2116             // These subtypes are not documented but they appear to mean
2117             // "a window is being dragged" and "a window is no longer being
2118             // dragged", respectively.
2119             if (subtype == 20 || subtype == 21)
2120                 [self handleWindowDrag:anEvent begin:(subtype == 20)];
2121         }
2123         return ret;
2124     }
2126     - (void) didSendEvent:(NSEvent*)anEvent
2127     {
2128         NSEventType type = [anEvent type];
2130         if (type == NSEventTypeKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
2131         {
2132             NSUInteger modifiers = [anEvent modifierFlags];
2133             if ((modifiers & NSEventModifierFlagCommand) &&
2134                 !(modifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)))
2135             {
2136                 // Command-Tab and Command-Shift-Tab would normally be intercepted
2137                 // by the system to switch applications.  If we're seeing it, it's
2138                 // presumably because we've captured the displays, preventing
2139                 // normal application switching.  Do it manually.
2140                 [self handleCommandTab];
2141             }
2142         }
2143     }
2145     - (void) setupObservations
2146     {
2147         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2148         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
2149         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
2151         [nc addObserverForName:NSWindowDidBecomeKeyNotification
2152                         object:nil
2153                          queue:nil
2154                     usingBlock:^(NSNotification *note){
2155             NSWindow* window = [note object];
2156             [keyWindows removeObjectIdenticalTo:window];
2157             [keyWindows insertObject:window atIndex:0];
2158         }];
2160         [nc addObserverForName:NSWindowWillCloseNotification
2161                         object:nil
2162                          queue:[NSOperationQueue mainQueue]
2163                     usingBlock:^(NSNotification *note){
2164             NSWindow* window = [note object];
2165             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
2166                 return;
2167             [keyWindows removeObjectIdenticalTo:window];
2168             if (window == lastTargetWindow)
2169                 lastTargetWindow = nil;
2170             if (window == self.mouseCaptureWindow)
2171                 self.mouseCaptureWindow = nil;
2172             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
2173             {
2174                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
2175                     [self updateFullscreenWindows];
2176                 });
2177             }
2178             [windowsBeingDragged removeObject:window];
2179             [self updateCursorClippingState];
2180         }];
2182         [nc addObserver:self
2183                selector:@selector(keyboardSelectionDidChange)
2184                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
2185                  object:nil];
2187         /* The above notification isn't sent unless the NSTextInputContext
2188            class has initialized itself.  Poke it. */
2189         [NSTextInputContext self];
2191         [wsnc addObserver:self
2192                  selector:@selector(activeSpaceDidChange)
2193                      name:NSWorkspaceActiveSpaceDidChangeNotification
2194                    object:nil];
2196         [nc addObserver:self
2197                selector:@selector(releaseMouseCapture)
2198                    name:NSMenuDidBeginTrackingNotification
2199                  object:nil];
2201         [dnc        addObserver:self
2202                        selector:@selector(releaseMouseCapture)
2203                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2204                          object:nil
2205              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2207         [dnc addObserver:self
2208                 selector:@selector(enabledKeyboardInputSourcesChanged)
2209                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2210                   object:nil];
2211     }
2213     - (BOOL) inputSourceIsInputMethod
2214     {
2215         if (!inputSourceIsInputMethodValid)
2216         {
2217             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2218             if (inputSource)
2219             {
2220                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2221                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2222                 CFRelease(inputSource);
2223             }
2224             else
2225                 inputSourceIsInputMethod = FALSE;
2226             inputSourceIsInputMethodValid = TRUE;
2227         }
2229         return inputSourceIsInputMethod;
2230     }
2232     - (void) releaseMouseCapture
2233     {
2234         // This might be invoked on a background thread by the distributed
2235         // notification center.  Shunt it to the main thread.
2236         if (![NSThread isMainThread])
2237         {
2238             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2239             return;
2240         }
2242         if (mouseCaptureWindow)
2243         {
2244             macdrv_event* event;
2246             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2247             [mouseCaptureWindow.queue postEvent:event];
2248             macdrv_release_event(event);
2249         }
2250     }
2252     - (void) unminimizeWindowIfNoneVisible
2253     {
2254         if (![self frontWineWindow])
2255         {
2256             for (WineWindow* window in [NSApp windows])
2257             {
2258                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2259                 {
2260                     [window deminiaturize:self];
2261                     break;
2262                 }
2263             }
2264         }
2265     }
2267     - (void) setRetinaMode:(int)mode
2268     {
2269         retina_on = mode;
2271         if (clippingCursor)
2272         {
2273             double scale = mode ? 0.5 : 2.0;
2274             cursorClipRect.origin.x *= scale;
2275             cursorClipRect.origin.y *= scale;
2276             cursorClipRect.size.width *= scale;
2277             cursorClipRect.size.height *= scale;
2278         }
2280         for (WineWindow* window in [NSApp windows])
2281         {
2282             if ([window isKindOfClass:[WineWindow class]])
2283                 [window setRetinaMode:mode];
2284         }
2285     }
2288     /*
2289      * ---------- NSApplicationDelegate methods ----------
2290      */
2291     - (void)applicationDidBecomeActive:(NSNotification *)notification
2292     {
2293         NSNumber* displayID;
2294         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2296         latentDisplayModes = [[NSMutableDictionary alloc] init];
2297         for (displayID in modesToRealize)
2298         {
2299             CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2300             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2301         }
2303         [self updateCursorClippingState];
2305         [self updateFullscreenWindows];
2306         [self adjustWindowLevels:YES];
2308         if (beenActive)
2309             [self unminimizeWindowIfNoneVisible];
2310         beenActive = TRUE;
2312         // If a Wine process terminates abruptly while it has the display captured
2313         // and switched to a different resolution, Mac OS X will uncapture the
2314         // displays and switch their resolutions back.  However, the other Wine
2315         // processes won't have their notion of the desktop rect changed back.
2316         // This can lead them to refuse to draw or acknowledge clicks in certain
2317         // portions of their windows.
2318         //
2319         // To solve this, we synthesize a displays-changed event whenever we're
2320         // activated.  This will provoke a re-synchronization of Wine's notion of
2321         // the desktop rect with the actual state.
2322         [self sendDisplaysChanged:TRUE];
2324         // The cursor probably moved while we were inactive.  Accumulated mouse
2325         // movement deltas are invalidated.  Make sure the next mouse move event
2326         // starts over from an absolute baseline.
2327         forceNextMouseMoveAbsolute = TRUE;
2328     }
2330     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2331     {
2332         primaryScreenHeightValid = FALSE;
2333         [self sendDisplaysChanged:FALSE];
2334         [self adjustWindowLevels];
2336         // When the display configuration changes, the cursor position may jump.
2337         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2338         // mouse move event starts over from an absolute baseline.
2339         forceNextMouseMoveAbsolute = TRUE;
2340     }
2342     - (void)applicationDidResignActive:(NSNotification *)notification
2343     {
2344         macdrv_event* event;
2345         WineEventQueue* queue;
2347         [self updateCursorClippingState];
2349         [self invalidateGotFocusEvents];
2351         event = macdrv_create_event(APP_DEACTIVATED, nil);
2353         [eventQueuesLock lock];
2354         for (queue in eventQueues)
2355             [queue postEvent:event];
2356         [eventQueuesLock unlock];
2358         macdrv_release_event(event);
2360         [self releaseMouseCapture];
2361     }
2363     - (void) applicationDidUnhide:(NSNotification*)aNotification
2364     {
2365         [self adjustWindowLevels];
2366     }
2368     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2369     {
2370         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2371         // don't count as "visible windows" for this purpose.
2372         [self unminimizeWindowIfNoneVisible];
2373         return YES;
2374     }
2376     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2377     {
2378         NSApplicationTerminateReply ret = NSTerminateNow;
2379         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2380         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2381         macdrv_event* event;
2382         WineEventQueue* queue;
2384         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2385         event->deliver = 1;
2386         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2387         {
2388             case kAELogOut:
2389             case kAEReallyLogOut:
2390                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2391                 break;
2392             case kAEShowRestartDialog:
2393                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2394                 break;
2395             case kAEShowShutdownDialog:
2396                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2397                 break;
2398             default:
2399                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2400                 break;
2401         }
2403         [eventQueuesLock lock];
2405         if ([eventQueues count])
2406         {
2407             for (queue in eventQueues)
2408                 [queue postEvent:event];
2409             ret = NSTerminateLater;
2410         }
2412         [eventQueuesLock unlock];
2414         macdrv_release_event(event);
2416         return ret;
2417     }
2419     - (void)applicationWillBecomeActive:(NSNotification *)notification
2420     {
2421         macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2422         event->deliver = 1;
2424         [eventQueuesLock lock];
2425         for (WineEventQueue* queue in eventQueues)
2426             [queue postEvent:event];
2427         [eventQueuesLock unlock];
2429         macdrv_release_event(event);
2430     }
2432     - (void)applicationWillResignActive:(NSNotification *)notification
2433     {
2434         [self adjustWindowLevels:NO];
2435     }
2437 /***********************************************************************
2438  *              PerformRequest
2440  * Run-loop-source perform callback.  Pull request blocks from the
2441  * array of queued requests and invoke them.
2442  */
2443 static void PerformRequest(void *info)
2445     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2446     WineApplicationController* controller = [WineApplicationController sharedController];
2448     for (;;)
2449     {
2450         __block dispatch_block_t block;
2452         dispatch_sync(controller->requestsManipQueue, ^{
2453             if ([controller->requests count])
2454             {
2455                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2456                 [controller->requests removeObjectAtIndex:0];
2457             }
2458             else
2459                 block = nil;
2460         });
2462         if (!block)
2463             break;
2465         block();
2466         [block release];
2468         [pool release];
2469         pool = [[NSAutoreleasePool alloc] init];
2470     }
2472     [pool release];
2475 /***********************************************************************
2476  *              OnMainThreadAsync
2478  * Run a block on the main thread asynchronously.
2479  */
2480 void OnMainThreadAsync(dispatch_block_t block)
2482     WineApplicationController* controller = [WineApplicationController sharedController];
2484     block = [block copy];
2485     dispatch_sync(controller->requestsManipQueue, ^{
2486         [controller->requests addObject:block];
2487     });
2488     [block release];
2489     CFRunLoopSourceSignal(controller->requestSource);
2490     CFRunLoopWakeUp(CFRunLoopGetMain());
2493 @end
2495 /***********************************************************************
2496  *              LogError
2497  */
2498 void LogError(const char* func, NSString* format, ...)
2500     va_list args;
2501     va_start(args, format);
2502     LogErrorv(func, format, args);
2503     va_end(args);
2506 /***********************************************************************
2507  *              LogErrorv
2508  */
2509 void LogErrorv(const char* func, NSString* format, va_list args)
2511     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2513     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2514     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2515     [message release];
2517     [pool release];
2520 /***********************************************************************
2521  *              macdrv_window_rejected_focus
2523  * Pass focus to the next window that hasn't already rejected this same
2524  * WINDOW_GOT_FOCUS event.
2525  */
2526 void macdrv_window_rejected_focus(const macdrv_event *event)
2528     OnMainThread(^{
2529         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2530     });
2533 /***********************************************************************
2534  *              macdrv_get_input_source_info
2536  * Returns the keyboard layout uchr data, keyboard type and input source.
2537  */
2538 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2540     OnMainThread(^{
2541         TISInputSourceRef inputSourceLayout;
2543         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2544         if (inputSourceLayout)
2545         {
2546             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2547                                 kTISPropertyUnicodeKeyLayoutData);
2548             *uchr = CFDataCreateCopy(NULL, data);
2549             CFRelease(inputSourceLayout);
2551             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2552             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2553             if (input_source)
2554                 *input_source = TISCopyCurrentKeyboardInputSource();
2555         }
2556     });
2559 /***********************************************************************
2560  *              macdrv_beep
2562  * Play the beep sound configured by the user in System Preferences.
2563  */
2564 void macdrv_beep(void)
2566     OnMainThreadAsync(^{
2567         NSBeep();
2568     });
2571 /***********************************************************************
2572  *              macdrv_set_display_mode
2573  */
2574 int macdrv_set_display_mode(const struct macdrv_display* display,
2575                             CGDisplayModeRef display_mode)
2577     __block int ret;
2579     OnMainThread(^{
2580         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2581     });
2583     return ret;
2586 /***********************************************************************
2587  *              macdrv_set_cursor
2589  * Set the cursor.
2591  * If name is non-NULL, it is a selector for a class method on NSCursor
2592  * identifying the cursor to set.  In that case, frames is ignored.  If
2593  * name is NULL, then frames is used.
2595  * frames is an array of dictionaries.  Each dictionary is a frame of
2596  * an animated cursor.  Under the key "image" is a CGImage for the
2597  * frame.  Under the key "duration" is a CFNumber time interval, in
2598  * seconds, for how long that frame is presented before proceeding to
2599  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2600  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2601  * This is the hot spot, measured in pixels down and to the right of the
2602  * top-left corner of the image.
2604  * If the array has exactly 1 element, the cursor is static, not
2605  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2606  */
2607 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2609     SEL sel;
2611     sel = NSSelectorFromString((NSString*)name);
2612     if (sel)
2613     {
2614         OnMainThreadAsync(^{
2615             WineApplicationController* controller = [WineApplicationController sharedController];
2616             [controller setCursorWithFrames:nil];
2617             controller.cursor = [NSCursor performSelector:sel];
2618             [controller unhideCursor];
2619         });
2620     }
2621     else
2622     {
2623         NSArray* nsframes = (NSArray*)frames;
2624         if ([nsframes count])
2625         {
2626             OnMainThreadAsync(^{
2627                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2628             });
2629         }
2630         else
2631         {
2632             OnMainThreadAsync(^{
2633                 WineApplicationController* controller = [WineApplicationController sharedController];
2634                 [controller setCursorWithFrames:nil];
2635                 [controller hideCursor];
2636             });
2637         }
2638     }
2641 /***********************************************************************
2642  *              macdrv_get_cursor_position
2644  * Obtains the current cursor position.  Returns zero on failure,
2645  * non-zero on success.
2646  */
2647 int macdrv_get_cursor_position(CGPoint *pos)
2649     OnMainThread(^{
2650         NSPoint location = [NSEvent mouseLocation];
2651         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2652         *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2653     });
2655     return TRUE;
2658 /***********************************************************************
2659  *              macdrv_set_cursor_position
2661  * Sets the cursor position without generating events.  Returns zero on
2662  * failure, non-zero on success.
2663  */
2664 int macdrv_set_cursor_position(CGPoint pos)
2666     __block int ret;
2668     OnMainThread(^{
2669         ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2670     });
2672     return ret;
2675 /***********************************************************************
2676  *              macdrv_clip_cursor
2678  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2679  * to or larger than the whole desktop region, the cursor is unclipped.
2680  * Returns zero on failure, non-zero on success.
2681  */
2682 int macdrv_clip_cursor(CGRect r)
2684     __block int ret;
2686     OnMainThread(^{
2687         WineApplicationController* controller = [WineApplicationController sharedController];
2688         BOOL clipping = FALSE;
2689         CGRect rect = r;
2691         if (!CGRectIsInfinite(rect))
2692             rect = cgrect_mac_from_win(rect);
2694         if (!CGRectIsInfinite(rect))
2695         {
2696             NSRect nsrect = NSRectFromCGRect(rect);
2697             NSScreen* screen;
2699             /* Convert the rectangle from top-down coords to bottom-up. */
2700             [controller flipRect:&nsrect];
2702             clipping = FALSE;
2703             for (screen in [NSScreen screens])
2704             {
2705                 if (!NSContainsRect(nsrect, [screen frame]))
2706                 {
2707                     clipping = TRUE;
2708                     break;
2709                 }
2710             }
2711         }
2713         if (clipping)
2714             ret = [controller startClippingCursor:rect];
2715         else
2716             ret = [controller stopClippingCursor];
2717     });
2719     return ret;
2722 /***********************************************************************
2723  *              macdrv_set_application_icon
2725  * Set the application icon.  The images array contains CGImages.  If
2726  * there are more than one, then they represent different sizes or
2727  * color depths from the icon resource.  If images is NULL or empty,
2728  * restores the default application image.
2729  */
2730 void macdrv_set_application_icon(CFArrayRef images)
2732     NSArray* imageArray = (NSArray*)images;
2734     OnMainThreadAsync(^{
2735         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2736     });
2739 /***********************************************************************
2740  *              macdrv_quit_reply
2741  */
2742 void macdrv_quit_reply(int reply)
2744     OnMainThread(^{
2745         [NSApp replyToApplicationShouldTerminate:reply];
2746     });
2749 /***********************************************************************
2750  *              macdrv_using_input_method
2751  */
2752 int macdrv_using_input_method(void)
2754     __block BOOL ret;
2756     OnMainThread(^{
2757         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2758     });
2760     return ret;
2763 /***********************************************************************
2764  *              macdrv_set_mouse_capture_window
2765  */
2766 void macdrv_set_mouse_capture_window(macdrv_window window)
2768     WineWindow* w = (WineWindow*)window;
2770     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2772     OnMainThread(^{
2773         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2774     });
2777 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2778 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2779 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2781 /***********************************************************************
2782  *              macdrv_create_input_source_list
2783  */
2784 CFArrayRef macdrv_create_input_source_list(void)
2786     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2788     OnMainThread(^{
2789         CFArrayRef input_list;
2790         CFDictionaryRef filter_dict;
2791         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2792         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2793         int i;
2795         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2796                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2797         input_list = TISCreateInputSourceList(filter_dict, false);
2799         for (i = 0; i < CFArrayGetCount(input_list); i++)
2800         {
2801             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2802             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2803             CFDictionaryRef entry;
2804             const void *input_keys[3] = { macdrv_input_source_input_key,
2805                                           macdrv_input_source_type_key,
2806                                           macdrv_input_source_lang_key };
2807             const void *input_values[3];
2809             input_values[0] = input;
2810             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2811             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2813             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2814                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2816             CFArrayAppendValue(ret, entry);
2817             CFRelease(entry);
2818         }
2819         CFRelease(input_list);
2820         CFRelease(filter_dict);
2821     });
2823     return ret;
2826 int macdrv_select_input_source(TISInputSourceRef input_source)
2828     __block int ret = FALSE;
2830     OnMainThread(^{
2831         ret = (TISSelectInputSource(input_source) == noErr);
2832     });
2834     return ret;
2837 void macdrv_set_cocoa_retina_mode(int new_mode)
2839     OnMainThread(^{
2840         [[WineApplicationController sharedController] setRetinaMode:new_mode];
2841     });