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