msvcp140: Implement _Stat and _Lstat.
[wine.git] / dlls / winemac.drv / cocoa_app.m
blob2cf67f17ffc01292f8c4ee5d58916df14d4e5fb9
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         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
996         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
997         CGPoint hotSpot;
999         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
1000             hotSpot = CGPointZero;
1001         self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1002         [image release];
1003         [self unhideCursor];
1004     }
1006     - (void) nextCursorFrame:(NSTimer*)theTimer
1007     {
1008         NSDictionary* frame;
1009         NSTimeInterval duration;
1010         NSDate* date;
1012         cursorFrame++;
1013         if (cursorFrame >= [cursorFrames count])
1014             cursorFrame = 0;
1015         [self setCursor];
1017         frame = [cursorFrames objectAtIndex:cursorFrame];
1018         duration = [[frame objectForKey:@"duration"] doubleValue];
1019         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1020         [cursorTimer setFireDate:date];
1021     }
1023     - (void) setCursorWithFrames:(NSArray*)frames
1024     {
1025         if (self.cursorFrames == frames)
1026             return;
1028         self.cursorFrames = frames;
1029         cursorFrame = 0;
1030         [cursorTimer invalidate];
1031         self.cursorTimer = nil;
1033         if ([frames count])
1034         {
1035             if ([frames count] > 1)
1036             {
1037                 NSDictionary* frame = [frames objectAtIndex:0];
1038                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
1039                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1040                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1041                                                              interval:1000000
1042                                                                target:self
1043                                                              selector:@selector(nextCursorFrame:)
1044                                                              userInfo:nil
1045                                                               repeats:YES] autorelease];
1046                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1047             }
1049             [self setCursor];
1050         }
1051     }
1053     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1054     {
1055         NSImage* nsimage = nil;
1057         if ([images count])
1058         {
1059             NSSize bestSize = NSZeroSize;
1060             id image;
1062             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1064             for (image in images)
1065             {
1066                 CGImageRef cgimage = (CGImageRef)image;
1067                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1068                 if (imageRep)
1069                 {
1070                     NSSize size = [imageRep size];
1072                     [nsimage addRepresentation:imageRep];
1073                     [imageRep release];
1075                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1076                         bestSize = size;
1077                 }
1078             }
1080             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1081                 [nsimage setSize:bestSize];
1082             else
1083                 nsimage = nil;
1084         }
1086         self.applicationIcon = nsimage;
1087     }
1089     - (void) handleCommandTab
1090     {
1091         if ([NSApp isActive])
1092         {
1093             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1094             NSRunningApplication* app;
1095             NSRunningApplication* otherValidApp = nil;
1097             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1098             {
1099                 NSNumber* displayID;
1100                 for (displayID in originalDisplayModes)
1101                 {
1102                     CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1103                     [latentDisplayModes setObject:(id)mode forKey:displayID];
1104                     CGDisplayModeRelease(mode);
1105                 }
1107                 CGRestorePermanentDisplayConfiguration();
1108                 CGReleaseAllDisplays();
1109                 [originalDisplayModes removeAllObjects];
1110                 displaysCapturedForFullscreen = FALSE;
1111             }
1113             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1114             {
1115                 if (![app isEqual:thisApp] && !app.terminated &&
1116                     app.activationPolicy == NSApplicationActivationPolicyRegular)
1117                 {
1118                     if (!app.hidden)
1119                     {
1120                         // There's another visible app.  Just hide ourselves and let
1121                         // the system activate the other app.
1122                         [NSApp hide:self];
1123                         return;
1124                     }
1126                     if (!otherValidApp)
1127                         otherValidApp = app;
1128                 }
1129             }
1131             // Didn't find a visible GUI app.  Try the Finder or, if that's not
1132             // running, the first hidden GUI app.  If even that doesn't work, we
1133             // just fail to switch and remain the active app.
1134             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1135             if (!app) app = otherValidApp;
1136             [app unhide];
1137             [app activateWithOptions:0];
1138         }
1139     }
1141     /*
1142      * ---------- Cursor clipping methods ----------
1143      *
1144      * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1145      * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1146      * equivalent: CGAssociateMouseAndMouseCursorPosition(false).  For the
1147      * general case, we leverage that.  We disassociate mouse movements from
1148      * the cursor position and then move the cursor manually, keeping it within
1149      * the clipping rectangle.
1150      *
1151      * Moving the cursor manually isn't enough.  We need to modify the event
1152      * stream so that the events have the new location, too.  We need to do
1153      * this at a point before the events enter Cocoa, so that Cocoa will assign
1154      * the correct window to the event.  So, we install a Quartz event tap to
1155      * do that.
1156      *
1157      * Also, there's a complication when we move the cursor.  We use
1158      * CGWarpMouseCursorPosition().  That doesn't generate mouse movement
1159      * events, but the change of cursor position is incorporated into the
1160      * deltas of the next mouse move event.  When the mouse is disassociated
1161      * from the cursor position, we need the deltas to only reflect actual
1162      * device movement, not programmatic changes.  So, the event tap cancels
1163      * out the change caused by our calls to CGWarpMouseCursorPosition().
1164      */
1165     - (void) clipCursorLocation:(CGPoint*)location
1166     {
1167         if (location->x < CGRectGetMinX(cursorClipRect))
1168             location->x = CGRectGetMinX(cursorClipRect);
1169         if (location->y < CGRectGetMinY(cursorClipRect))
1170             location->y = CGRectGetMinY(cursorClipRect);
1171         if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1172             location->x = CGRectGetMaxX(cursorClipRect) - 1;
1173         if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1174             location->y = CGRectGetMaxY(cursorClipRect) - 1;
1175     }
1177     - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1178     {
1179         CGPoint oldLocation;
1181         if (currentLocation)
1182             oldLocation = *currentLocation;
1183         else
1184             oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1186         if (!CGPointEqualToPoint(oldLocation, *newLocation))
1187         {
1188             WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1189             CGError err;
1191             warpRecord.from = oldLocation;
1192             warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1194             /* Actually move the cursor. */
1195             err = CGWarpMouseCursorPosition(*newLocation);
1196             if (err != kCGErrorSuccess)
1197                 return FALSE;
1199             warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1200             *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1202             if (!CGPointEqualToPoint(oldLocation, *newLocation))
1203             {
1204                 warpRecord.to = *newLocation;
1205                 [warpRecords addObject:warpRecord];
1206             }
1207         }
1209         return TRUE;
1210     }
1212     - (BOOL) isMouseMoveEventType:(CGEventType)type
1213     {
1214         switch(type)
1215         {
1216         case kCGEventMouseMoved:
1217         case kCGEventLeftMouseDragged:
1218         case kCGEventRightMouseDragged:
1219         case kCGEventOtherMouseDragged:
1220             return TRUE;
1221         }
1223         return FALSE;
1224     }
1226     - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1227     {
1228         int warpsFinished = 0;
1229         for (WarpRecord* warpRecord in warpRecords)
1230         {
1231             if (warpRecord.timeAfter < eventTime ||
1232                 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1233                 warpsFinished++;
1234             else
1235                 break;
1236         }
1238         return warpsFinished;
1239     }
1241     - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1242                                 type:(CGEventType)type
1243                                event:(CGEventRef)event
1244     {
1245         CGEventTimestamp eventTime;
1246         CGPoint eventLocation, cursorLocation;
1248         if (type == kCGEventTapDisabledByUserInput)
1249             return event;
1250         if (type == kCGEventTapDisabledByTimeout)
1251         {
1252             CGEventTapEnable(cursorClippingEventTap, TRUE);
1253             return event;
1254         }
1256         if (!clippingCursor)
1257             return event;
1259         eventTime = CGEventGetTimestamp(event);
1260         lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1262         eventLocation = CGEventGetLocation(event);
1264         cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1266         if ([self isMouseMoveEventType:type])
1267         {
1268             double deltaX, deltaY;
1269             int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1270             int i;
1272             deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1273             deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1275             for (i = 0; i < warpsFinished; i++)
1276             {
1277                 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1278                 deltaX -= warpRecord.to.x - warpRecord.from.x;
1279                 deltaY -= warpRecord.to.y - warpRecord.from.y;
1280                 [warpRecords removeObjectAtIndex:0];
1281             }
1283             if (warpsFinished)
1284             {
1285                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1286                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1287             }
1289             synthesizedLocation.x += deltaX;
1290             synthesizedLocation.y += deltaY;
1291         }
1293         // If the event is destined for another process, don't clip it.  This may
1294         // happen if the user activates Exposé or Mission Control.  In that case,
1295         // our app does not resign active status, so clipping is still in effect,
1296         // but the cursor should not actually be clipped.
1297         //
1298         // In addition, the fact that mouse moves may have been delivered to a
1299         // different process means we have to treat the next one we receive as
1300         // absolute rather than relative.
1301         if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1302             [self clipCursorLocation:&synthesizedLocation];
1303         else
1304             lastSetCursorPositionTime = lastEventTapEventTime;
1306         [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1307         if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1308             CGEventSetLocation(event, synthesizedLocation);
1310         return event;
1311     }
1313     CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1314                                        CGEventRef event, void *refcon)
1315     {
1316         WineApplicationController* controller = refcon;
1317         return [controller eventTapWithProxy:proxy type:type event:event];
1318     }
1320     - (BOOL) installEventTap
1321     {
1322         ProcessSerialNumber psn;
1323         OSErr err;
1324         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
1325                            CGEventMaskBit(kCGEventLeftMouseUp)          |
1326                            CGEventMaskBit(kCGEventRightMouseDown)       |
1327                            CGEventMaskBit(kCGEventRightMouseUp)         |
1328                            CGEventMaskBit(kCGEventMouseMoved)           |
1329                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
1330                            CGEventMaskBit(kCGEventRightMouseDragged)    |
1331                            CGEventMaskBit(kCGEventOtherMouseDown)       |
1332                            CGEventMaskBit(kCGEventOtherMouseUp)         |
1333                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
1334                            CGEventMaskBit(kCGEventScrollWheel);
1335         CFRunLoopSourceRef source;
1336         void* appServices;
1337         OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1339         if (cursorClippingEventTap)
1340             return TRUE;
1342         // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1343         // framework with dlsym() because the Win32 function of the same name
1344         // obscures it.
1345         appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1346         if (!appServices)
1347             return FALSE;
1349         pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1350         if (!pGetCurrentProcess)
1351         {
1352             dlclose(appServices);
1353             return FALSE;
1354         }
1356         err = pGetCurrentProcess(&psn);
1357         dlclose(appServices);
1358         if (err != noErr)
1359             return FALSE;
1361         // We create an annotated session event tap rather than a process-specific
1362         // event tap because we need to programmatically move the cursor even when
1363         // mouse moves are directed to other processes.  We disable our tap when
1364         // other processes are active, but things like Exposé are handled by other
1365         // processes even when we remain active.
1366         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1367             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1368         if (!cursorClippingEventTap)
1369             return FALSE;
1371         CGEventTapEnable(cursorClippingEventTap, FALSE);
1373         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1374         if (!source)
1375         {
1376             CFRelease(cursorClippingEventTap);
1377             cursorClippingEventTap = NULL;
1378             return FALSE;
1379         }
1381         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1382         CFRelease(source);
1383         return TRUE;
1384     }
1386     - (BOOL) setCursorPosition:(CGPoint)pos
1387     {
1388         BOOL ret;
1390         if ([windowsBeingDragged count])
1391             ret = FALSE;
1392         else if (clippingCursor)
1393         {
1394             [self clipCursorLocation:&pos];
1396             ret = [self warpCursorTo:&pos from:NULL];
1397             synthesizedLocation = pos;
1398             if (ret)
1399             {
1400                 // We want to discard mouse-move events that have already been
1401                 // through the event tap, because it's too late to account for
1402                 // the setting of the cursor position with them.  However, the
1403                 // events that may be queued with times after that but before
1404                 // the above warp can still be used.  So, use the last event
1405                 // tap event time so that -sendEvent: doesn't discard them.
1406                 lastSetCursorPositionTime = lastEventTapEventTime;
1407             }
1408         }
1409         else
1410         {
1411             // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1412             // the mouse from the cursor position for 0.25 seconds.  This means
1413             // that mouse movement during that interval doesn't move the cursor
1414             // and events carry a constant location (the warped-to position)
1415             // even though they have delta values.  For apps which warp the
1416             // cursor frequently (like after every mouse move), this makes
1417             // cursor movement horribly laggy and jerky, as only a fraction of
1418             // mouse move events have any effect.
1419             //
1420             // On some versions of OS X, it's sufficient to forcibly reassociate
1421             // the mouse and cursor position.  On others, it's necessary to set
1422             // the local events suppression interval to 0 for the warp.  That's
1423             // deprecated, but I'm not aware of any other way.  For good
1424             // measure, we do both.
1425             CGSetLocalEventsSuppressionInterval(0);
1426             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1427             CGSetLocalEventsSuppressionInterval(0.25);
1428             if (ret)
1429             {
1430                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1432                 CGAssociateMouseAndMouseCursorPosition(true);
1433             }
1434         }
1436         if (ret)
1437         {
1438             WineEventQueue* queue;
1440             // Discard all pending mouse move events.
1441             [eventQueuesLock lock];
1442             for (queue in eventQueues)
1443             {
1444                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1445                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1446                                        forWindow:nil];
1447                 [queue resetMouseEventPositions:pos];
1448             }
1449             [eventQueuesLock unlock];
1450         }
1452         return ret;
1453     }
1455     - (void) activateCursorClipping
1456     {
1457         if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1458         {
1459             CGEventTapEnable(cursorClippingEventTap, TRUE);
1460             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1461         }
1462     }
1464     - (void) deactivateCursorClipping
1465     {
1466         if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1467         {
1468             CGEventTapEnable(cursorClippingEventTap, FALSE);
1469             [warpRecords removeAllObjects];
1470             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1471         }
1472     }
1474     - (void) updateCursorClippingState
1475     {
1476         if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1477             [self activateCursorClipping];
1478         else
1479             [self deactivateCursorClipping];
1480     }
1482     - (void) updateWindowsForCursorClipping
1483     {
1484         WineWindow* window;
1485         for (window in [NSApp windows])
1486         {
1487             if ([window isKindOfClass:[WineWindow class]])
1488                 [window updateForCursorClipping];
1489         }
1490     }
1492     - (BOOL) startClippingCursor:(CGRect)rect
1493     {
1494         CGError err;
1496         if (!cursorClippingEventTap && ![self installEventTap])
1497             return FALSE;
1499         if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1500             CGEventTapIsEnabled(cursorClippingEventTap))
1501             return TRUE;
1503         err = CGAssociateMouseAndMouseCursorPosition(false);
1504         if (err != kCGErrorSuccess)
1505             return FALSE;
1507         clippingCursor = TRUE;
1508         cursorClipRect = rect;
1509         [self updateCursorClippingState];
1510         [self updateWindowsForCursorClipping];
1512         return TRUE;
1513     }
1515     - (BOOL) stopClippingCursor
1516     {
1517         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1518         if (err != kCGErrorSuccess)
1519             return FALSE;
1521         clippingCursor = FALSE;
1522         [self updateCursorClippingState];
1523         [self updateWindowsForCursorClipping];
1525         return TRUE;
1526     }
1528     - (BOOL) isKeyPressed:(uint16_t)keyCode
1529     {
1530         int bits = sizeof(pressedKeyCodes[0]) * 8;
1531         int index = keyCode / bits;
1532         uint32_t mask = 1 << (keyCode % bits);
1533         return (pressedKeyCodes[index] & mask) != 0;
1534     }
1536     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1537     {
1538         int bits = sizeof(pressedKeyCodes[0]) * 8;
1539         int index = keyCode / bits;
1540         uint32_t mask = 1 << (keyCode % bits);
1541         if (pressed)
1542             pressedKeyCodes[index] |= mask;
1543         else
1544             pressedKeyCodes[index] &= ~mask;
1545     }
1547     - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1548     {
1549         if (dragged)
1550             [windowsBeingDragged addObject:window];
1551         else
1552             [windowsBeingDragged removeObject:window];
1553         [self updateCursorClippingState];
1554     }
1556     - (void) windowWillOrderOut:(WineWindow*)window
1557     {
1558         if ([windowsBeingDragged containsObject:window])
1559         {
1560             [self window:window isBeingDragged:NO];
1562             macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1563             [window.queue postEvent:event];
1564             macdrv_release_event(event);
1565         }
1566     }
1568     - (void) handleMouseMove:(NSEvent*)anEvent
1569     {
1570         WineWindow* targetWindow;
1571         BOOL drag = [anEvent type] != NSMouseMoved;
1573         if ([windowsBeingDragged count])
1574             targetWindow = nil;
1575         else if (mouseCaptureWindow)
1576             targetWindow = mouseCaptureWindow;
1577         else if (drag)
1578             targetWindow = (WineWindow*)[anEvent window];
1579         else
1580         {
1581             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1582                event indicates its window is the main window, even if the cursor is
1583                over a different window.  Find the actual WineWindow that is under the
1584                cursor and post the event as being for that window. */
1585             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1586             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1587             NSInteger windowUnderNumber;
1589             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1590                                   belowWindowWithWindowNumber:0];
1591             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1592             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1593                 targetWindow = nil;
1594         }
1596         if ([targetWindow isKindOfClass:[WineWindow class]])
1597         {
1598             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1599             macdrv_event* event;
1600             BOOL absolute;
1602             // If we recently warped the cursor (other than in our cursor-clipping
1603             // event tap), discard mouse move events until we see an event which is
1604             // later than that time.
1605             if (lastSetCursorPositionTime)
1606             {
1607                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1608                     return;
1610                 lastSetCursorPositionTime = 0;
1611                 forceNextMouseMoveAbsolute = TRUE;
1612             }
1614             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1615             {
1616                 absolute = TRUE;
1617                 forceNextMouseMoveAbsolute = FALSE;
1618             }
1619             else
1620             {
1621                 // Send absolute move events if the cursor is in the interior of
1622                 // its range.  Only send relative moves if the cursor is pinned to
1623                 // the boundaries of where it can go.  We compute the position
1624                 // that's one additional point in the direction of movement.  If
1625                 // that is outside of the clipping rect or desktop region (the
1626                 // union of the screen frames), then we figure the cursor would
1627                 // have moved outside if it could but it was pinned.
1628                 CGPoint computedPoint = point;
1629                 CGFloat deltaX = [anEvent deltaX];
1630                 CGFloat deltaY = [anEvent deltaY];
1632                 if (deltaX > 0.001)
1633                     computedPoint.x++;
1634                 else if (deltaX < -0.001)
1635                     computedPoint.x--;
1637                 if (deltaY > 0.001)
1638                     computedPoint.y++;
1639                 else if (deltaY < -0.001)
1640                     computedPoint.y--;
1642                 // Assume cursor is pinned for now
1643                 absolute = FALSE;
1644                 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1645                 {
1646                     const CGRect* rects;
1647                     NSUInteger count, i;
1649                     // Caches screenFrameCGRects if necessary
1650                     [self primaryScreenHeight];
1652                     rects = [screenFrameCGRects bytes];
1653                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1655                     for (i = 0; i < count; i++)
1656                     {
1657                         if (CGRectContainsPoint(rects[i], computedPoint))
1658                         {
1659                             absolute = TRUE;
1660                             break;
1661                         }
1662                     }
1663                 }
1664             }
1666             if (absolute)
1667             {
1668                 if (clippingCursor)
1669                     [self clipCursorLocation:&point];
1670                 point = cgpoint_win_from_mac(point);
1672                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1673                 event->mouse_moved.x = floor(point.x);
1674                 event->mouse_moved.y = floor(point.y);
1676                 mouseMoveDeltaX = 0;
1677                 mouseMoveDeltaY = 0;
1678             }
1679             else
1680             {
1681                 double scale = retina_on ? 2 : 1;
1683                 /* Add event delta to accumulated delta error */
1684                 /* deltaY is already flipped */
1685                 mouseMoveDeltaX += [anEvent deltaX];
1686                 mouseMoveDeltaY += [anEvent deltaY];
1688                 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1689                 event->mouse_moved.x = mouseMoveDeltaX * scale;
1690                 event->mouse_moved.y = mouseMoveDeltaY * scale;
1692                 /* Keep the remainder after integer truncation. */
1693                 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1694                 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1695             }
1697             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1698             {
1699                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1700                 event->mouse_moved.drag = drag;
1702                 [targetWindow.queue postEvent:event];
1703             }
1705             macdrv_release_event(event);
1707             lastTargetWindow = targetWindow;
1708         }
1709         else
1710             lastTargetWindow = nil;
1712         [self updateCursor:FALSE];
1713     }
1715     - (void) handleMouseButton:(NSEvent*)theEvent
1716     {
1717         WineWindow* window = (WineWindow*)[theEvent window];
1718         NSEventType type = [theEvent type];
1719         WineWindow* windowBroughtForward = nil;
1720         BOOL process = FALSE;
1722         if ([window isKindOfClass:[WineWindow class]] &&
1723             type == NSLeftMouseDown &&
1724             ![theEvent wine_commandKeyDown])
1725         {
1726             NSWindowButton windowButton;
1728             windowBroughtForward = window;
1730             /* Any left-click on our window anyplace other than the close or
1731                minimize buttons will bring it forward. */
1732             for (windowButton = NSWindowCloseButton;
1733                  windowButton <= NSWindowMiniaturizeButton;
1734                  windowButton++)
1735             {
1736                 NSButton* button = [window standardWindowButton:windowButton];
1737                 if (button)
1738                 {
1739                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1740                     if ([button mouse:point inRect:[button bounds]])
1741                     {
1742                         windowBroughtForward = nil;
1743                         break;
1744                     }
1745                 }
1746             }
1747         }
1749         if ([windowsBeingDragged count])
1750             window = nil;
1751         else if (mouseCaptureWindow)
1752             window = mouseCaptureWindow;
1754         if ([window isKindOfClass:[WineWindow class]])
1755         {
1756             BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1757             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1759             if (clippingCursor)
1760                 [self clipCursorLocation:&pt];
1762             if (pressed)
1763             {
1764                 if (mouseCaptureWindow)
1765                     process = TRUE;
1766                 else
1767                 {
1768                     // Test if the click was in the window's content area.
1769                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1770                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1771                     process = NSMouseInRect(nspoint, contentRect, NO);
1772                     if (process && [window styleMask] & NSResizableWindowMask)
1773                     {
1774                         // Ignore clicks in the grow box (resize widget).
1775                         HIPoint origin = { 0, 0 };
1776                         HIThemeGrowBoxDrawInfo info = { 0 };
1777                         HIRect bounds;
1778                         OSStatus status;
1780                         info.kind = kHIThemeGrowBoxKindNormal;
1781                         info.direction = kThemeGrowRight | kThemeGrowDown;
1782                         if ([window styleMask] & NSUtilityWindowMask)
1783                             info.size = kHIThemeGrowBoxSizeSmall;
1784                         else
1785                             info.size = kHIThemeGrowBoxSizeNormal;
1787                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1788                         if (status == noErr)
1789                         {
1790                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1791                                                         NSMinY(contentRect),
1792                                                         bounds.size.width,
1793                                                         bounds.size.height);
1794                             process = !NSMouseInRect(nspoint, growBox, NO);
1795                         }
1796                     }
1797                 }
1798                 if (process)
1799                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1800             }
1801             else
1802             {
1803                 NSEventType downType = type - 1;
1804                 NSUInteger downMask = NSEventMaskFromType(downType);
1805                 process = (unmatchedMouseDowns & downMask) != 0;
1806                 unmatchedMouseDowns &= ~downMask;
1807             }
1809             if (process)
1810             {
1811                 macdrv_event* event;
1813                 pt = cgpoint_win_from_mac(pt);
1815                 event = macdrv_create_event(MOUSE_BUTTON, window);
1816                 event->mouse_button.button = [theEvent buttonNumber];
1817                 event->mouse_button.pressed = pressed;
1818                 event->mouse_button.x = floor(pt.x);
1819                 event->mouse_button.y = floor(pt.y);
1820                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1822                 [window.queue postEvent:event];
1824                 macdrv_release_event(event);
1825             }
1826         }
1828         if (windowBroughtForward)
1829         {
1830             WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1831             NSInteger ancestorNumber = [ancestor windowNumber];
1832             NSInteger ancestorLevel = [ancestor level];
1834             for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1835             {
1836                 NSInteger windowNumber = [windowNumberObject integerValue];
1837                 if (windowNumber == ancestorNumber)
1838                     break;
1839                 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1840                 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1841                     [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1842                 {
1843                     [ancestor postBroughtForwardEvent];
1844                     break;
1845                 }
1846             }
1847             if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate)
1848                 [self windowGotFocus:windowBroughtForward];
1849         }
1851         // Since mouse button events deliver absolute cursor position, the
1852         // accumulating delta from move events is invalidated.  Make sure
1853         // next mouse move event starts over from an absolute baseline.
1854         // Also, it's at least possible that the title bar widgets (e.g. close
1855         // button, etc.) could enter an internal event loop on a mouse down that
1856         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1857         // dragged events and, after that, any notion of the cursor position
1858         // computed from accumulating deltas would be wrong.
1859         forceNextMouseMoveAbsolute = TRUE;
1860     }
1862     - (void) handleScrollWheel:(NSEvent*)theEvent
1863     {
1864         WineWindow* window;
1866         if (mouseCaptureWindow)
1867             window = mouseCaptureWindow;
1868         else
1869             window = (WineWindow*)[theEvent window];
1871         if ([window isKindOfClass:[WineWindow class]])
1872         {
1873             CGEventRef cgevent = [theEvent CGEvent];
1874             CGPoint pt = CGEventGetLocation(cgevent);
1875             BOOL process;
1877             if (clippingCursor)
1878                 [self clipCursorLocation:&pt];
1880             if (mouseCaptureWindow)
1881                 process = TRUE;
1882             else
1883             {
1884                 // Only process the event if it was in the window's content area.
1885                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1886                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1887                 process = NSMouseInRect(nspoint, contentRect, NO);
1888             }
1890             if (process)
1891             {
1892                 macdrv_event* event;
1893                 double x, y;
1894                 BOOL continuous = FALSE;
1896                 pt = cgpoint_win_from_mac(pt);
1898                 event = macdrv_create_event(MOUSE_SCROLL, window);
1899                 event->mouse_scroll.x = floor(pt.x);
1900                 event->mouse_scroll.y = floor(pt.y);
1901                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1903                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1904                 {
1905                     continuous = TRUE;
1907                     /* Continuous scroll wheel events come from high-precision scrolling
1908                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1909                        For these, we can get more precise data from the CGEvent API. */
1910                     /* Axis 1 is vertical, axis 2 is horizontal. */
1911                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1912                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1913                 }
1914                 else
1915                 {
1916                     double pixelsPerLine = 10;
1917                     CGEventSourceRef source;
1919                     /* The non-continuous values are in units of "lines", not pixels. */
1920                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1921                     {
1922                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1923                         CFRelease(source);
1924                     }
1926                     x = pixelsPerLine * [theEvent deltaX];
1927                     y = pixelsPerLine * [theEvent deltaY];
1928                 }
1930                 /* Mac: negative is right or down, positive is left or up.
1931                    Win32: negative is left or down, positive is right or up.
1932                    So, negate the X scroll value to translate. */
1933                 x = -x;
1935                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1936                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1937                    6 times the pixel value. */
1938                 x *= 6;
1939                 y *= 6;
1941                 if (use_precise_scrolling)
1942                 {
1943                     event->mouse_scroll.x_scroll = x;
1944                     event->mouse_scroll.y_scroll = y;
1946                     if (!continuous)
1947                     {
1948                         /* For non-continuous "clicky" wheels, if there was any motion, make
1949                            sure there was at least WHEEL_DELTA motion.  This is so, at slow
1950                            speeds where the system's acceleration curve is actually reducing the
1951                            scroll distance, the user is sure to get some action out of each click.
1952                            For example, this is important for rotating though weapons in a
1953                            first-person shooter. */
1954                         if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1955                             event->mouse_scroll.x_scroll = 120;
1956                         else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1957                             event->mouse_scroll.x_scroll = -120;
1959                         if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1960                             event->mouse_scroll.y_scroll = 120;
1961                         else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1962                             event->mouse_scroll.y_scroll = -120;
1963                     }
1964                 }
1965                 else
1966                 {
1967                     /* If it's been a while since the last scroll event or if the scrolling has
1968                        reversed direction, reset the accumulated scroll value. */
1969                     if ([theEvent timestamp] - lastScrollTime > 1)
1970                         accumScrollX = accumScrollY = 0;
1971                     else
1972                     {
1973                         /* The accumulated scroll value is in the opposite direction/sign of the last
1974                            scroll.  That's because it's the "debt" resulting from over-scrolling in
1975                            that direction.  We accumulate by adding in the scroll amount and then, if
1976                            it has the same sign as the scroll value, we subtract any whole or partial
1977                            WHEEL_DELTAs, leaving it 0 or the opposite sign.  So, the user switched
1978                            scroll direction if the accumulated debt and the new scroll value have the
1979                            same sign. */
1980                         if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1981                             accumScrollX = 0;
1982                         if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1983                             accumScrollY = 0;
1984                     }
1985                     lastScrollTime = [theEvent timestamp];
1987                     accumScrollX += x;
1988                     accumScrollY += y;
1990                     if (accumScrollX > 0 && x > 0)
1991                         event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1992                     if (accumScrollX < 0 && x < 0)
1993                         event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1994                     if (accumScrollY > 0 && y > 0)
1995                         event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1996                     if (accumScrollY < 0 && y < 0)
1997                         event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1999                     accumScrollX -= event->mouse_scroll.x_scroll;
2000                     accumScrollY -= event->mouse_scroll.y_scroll;
2001                 }
2003                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
2004                     [window.queue postEvent:event];
2006                 macdrv_release_event(event);
2008                 // Since scroll wheel events deliver absolute cursor position, the
2009                 // accumulating delta from move events is invalidated.  Make sure next
2010                 // mouse move event starts over from an absolute baseline.
2011                 forceNextMouseMoveAbsolute = TRUE;
2012             }
2013         }
2014     }
2016     // Returns TRUE if the event was handled and caller should do nothing more
2017     // with it.  Returns FALSE if the caller should process it as normal and
2018     // then call -didSendEvent:.
2019     - (BOOL) handleEvent:(NSEvent*)anEvent
2020     {
2021         BOOL ret = FALSE;
2022         NSEventType type = [anEvent type];
2024         if (type == NSFlagsChanged)
2025             self.lastFlagsChanged = anEvent;
2026         else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
2027                  type == NSRightMouseDragged || type == NSOtherMouseDragged)
2028         {
2029             [self handleMouseMove:anEvent];
2030             ret = mouseCaptureWindow && ![windowsBeingDragged count];
2031         }
2032         else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
2033                  type == NSRightMouseDown || type == NSRightMouseUp ||
2034                  type == NSOtherMouseDown || type == NSOtherMouseUp)
2035         {
2036             [self handleMouseButton:anEvent];
2037             ret = mouseCaptureWindow && ![windowsBeingDragged count];
2038         }
2039         else if (type == NSScrollWheel)
2040         {
2041             [self handleScrollWheel:anEvent];
2042             ret = mouseCaptureWindow != nil;
2043         }
2044         else if (type == NSKeyDown)
2045         {
2046             // -[NSApplication sendEvent:] seems to consume presses of the Help
2047             // key (Insert key on PC keyboards), so we have to bypass it and
2048             // send the event directly to the window.
2049             if (anEvent.keyCode == kVK_Help)
2050             {
2051                 [anEvent.window sendEvent:anEvent];
2052                 ret = TRUE;
2053             }
2054         }
2055         else if (type == NSKeyUp)
2056         {
2057             uint16_t keyCode = [anEvent keyCode];
2058             if ([self isKeyPressed:keyCode])
2059             {
2060                 WineWindow* window = (WineWindow*)[anEvent window];
2061                 [self noteKey:keyCode pressed:FALSE];
2062                 if ([window isKindOfClass:[WineWindow class]])
2063                     [window postKeyEvent:anEvent];
2064             }
2065         }
2066         else if (type == NSAppKitDefined)
2067         {
2068             short subtype = [anEvent subtype];
2070             // These subtypes are not documented but they appear to mean
2071             // "a window is being dragged" and "a window is no longer being
2072             // dragged", respectively.
2073             if (subtype == 20 || subtype == 21)
2074             {
2075                 WineWindow* window = (WineWindow*)[anEvent window];
2076                 if ([window isKindOfClass:[WineWindow class]])
2077                 {
2078                     macdrv_event* event;
2079                     int eventType;
2081                     if (subtype == 20)
2082                     {
2083                         [windowsBeingDragged addObject:window];
2084                         eventType = WINDOW_DRAG_BEGIN;
2085                     }
2086                     else
2087                     {
2088                         [windowsBeingDragged removeObject:window];
2089                         eventType = WINDOW_DRAG_END;
2090                     }
2091                     [self updateCursorClippingState];
2093                     event = macdrv_create_event(eventType, window);
2094                     if (eventType == WINDOW_DRAG_BEGIN)
2095                         event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
2096                     [window.queue postEvent:event];
2097                     macdrv_release_event(event);
2098                 }
2099             }
2100         }
2102         return ret;
2103     }
2105     - (void) didSendEvent:(NSEvent*)anEvent
2106     {
2107         NSEventType type = [anEvent type];
2109         if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
2110         {
2111             NSUInteger modifiers = [anEvent modifierFlags];
2112             if ((modifiers & NSCommandKeyMask) &&
2113                 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
2114             {
2115                 // Command-Tab and Command-Shift-Tab would normally be intercepted
2116                 // by the system to switch applications.  If we're seeing it, it's
2117                 // presumably because we've captured the displays, preventing
2118                 // normal application switching.  Do it manually.
2119                 [self handleCommandTab];
2120             }
2121         }
2122     }
2124     - (void) setupObservations
2125     {
2126         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2127         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
2128         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
2130         [nc addObserverForName:NSWindowDidBecomeKeyNotification
2131                         object:nil
2132                          queue:nil
2133                     usingBlock:^(NSNotification *note){
2134             NSWindow* window = [note object];
2135             [keyWindows removeObjectIdenticalTo:window];
2136             [keyWindows insertObject:window atIndex:0];
2137         }];
2139         [nc addObserverForName:NSWindowWillCloseNotification
2140                         object:nil
2141                          queue:[NSOperationQueue mainQueue]
2142                     usingBlock:^(NSNotification *note){
2143             NSWindow* window = [note object];
2144             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
2145                 return;
2146             [keyWindows removeObjectIdenticalTo:window];
2147             if (window == lastTargetWindow)
2148                 lastTargetWindow = nil;
2149             if (window == self.mouseCaptureWindow)
2150                 self.mouseCaptureWindow = nil;
2151             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
2152             {
2153                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
2154                     [self updateFullscreenWindows];
2155                 });
2156             }
2157             [windowsBeingDragged removeObject:window];
2158             [self updateCursorClippingState];
2159         }];
2161         [nc addObserver:self
2162                selector:@selector(keyboardSelectionDidChange)
2163                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
2164                  object:nil];
2166         /* The above notification isn't sent unless the NSTextInputContext
2167            class has initialized itself.  Poke it. */
2168         [NSTextInputContext self];
2170         [wsnc addObserver:self
2171                  selector:@selector(activeSpaceDidChange)
2172                      name:NSWorkspaceActiveSpaceDidChangeNotification
2173                    object:nil];
2175         [nc addObserver:self
2176                selector:@selector(releaseMouseCapture)
2177                    name:NSMenuDidBeginTrackingNotification
2178                  object:nil];
2180         [dnc        addObserver:self
2181                        selector:@selector(releaseMouseCapture)
2182                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2183                          object:nil
2184              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2186         [dnc addObserver:self
2187                 selector:@selector(enabledKeyboardInputSourcesChanged)
2188                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2189                   object:nil];
2190     }
2192     - (BOOL) inputSourceIsInputMethod
2193     {
2194         if (!inputSourceIsInputMethodValid)
2195         {
2196             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2197             if (inputSource)
2198             {
2199                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2200                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2201                 CFRelease(inputSource);
2202             }
2203             else
2204                 inputSourceIsInputMethod = FALSE;
2205             inputSourceIsInputMethodValid = TRUE;
2206         }
2208         return inputSourceIsInputMethod;
2209     }
2211     - (void) releaseMouseCapture
2212     {
2213         // This might be invoked on a background thread by the distributed
2214         // notification center.  Shunt it to the main thread.
2215         if (![NSThread isMainThread])
2216         {
2217             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2218             return;
2219         }
2221         if (mouseCaptureWindow)
2222         {
2223             macdrv_event* event;
2225             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2226             [mouseCaptureWindow.queue postEvent:event];
2227             macdrv_release_event(event);
2228         }
2229     }
2231     - (void) unminimizeWindowIfNoneVisible
2232     {
2233         if (![self frontWineWindow])
2234         {
2235             for (WineWindow* window in [NSApp windows])
2236             {
2237                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2238                 {
2239                     [window deminiaturize:self];
2240                     break;
2241                 }
2242             }
2243         }
2244     }
2246     - (void) setRetinaMode:(int)mode
2247     {
2248         retina_on = mode;
2250         if (clippingCursor)
2251         {
2252             double scale = mode ? 0.5 : 2.0;
2253             cursorClipRect.origin.x *= scale;
2254             cursorClipRect.origin.y *= scale;
2255             cursorClipRect.size.width *= scale;
2256             cursorClipRect.size.height *= scale;
2257         }
2259         for (WineWindow* window in [NSApp windows])
2260         {
2261             if ([window isKindOfClass:[WineWindow class]])
2262                 [window setRetinaMode:mode];
2263         }
2264     }
2267     /*
2268      * ---------- NSApplicationDelegate methods ----------
2269      */
2270     - (void)applicationDidBecomeActive:(NSNotification *)notification
2271     {
2272         NSNumber* displayID;
2273         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2275         latentDisplayModes = [[NSMutableDictionary alloc] init];
2276         for (displayID in modesToRealize)
2277         {
2278             CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2279             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2280         }
2282         [self updateCursorClippingState];
2284         [self updateFullscreenWindows];
2285         [self adjustWindowLevels:YES];
2287         if (beenActive)
2288             [self unminimizeWindowIfNoneVisible];
2289         beenActive = TRUE;
2291         // If a Wine process terminates abruptly while it has the display captured
2292         // and switched to a different resolution, Mac OS X will uncapture the
2293         // displays and switch their resolutions back.  However, the other Wine
2294         // processes won't have their notion of the desktop rect changed back.
2295         // This can lead them to refuse to draw or acknowledge clicks in certain
2296         // portions of their windows.
2297         //
2298         // To solve this, we synthesize a displays-changed event whenever we're
2299         // activated.  This will provoke a re-synchronization of Wine's notion of
2300         // the desktop rect with the actual state.
2301         [self sendDisplaysChanged:TRUE];
2303         // The cursor probably moved while we were inactive.  Accumulated mouse
2304         // movement deltas are invalidated.  Make sure the next mouse move event
2305         // starts over from an absolute baseline.
2306         forceNextMouseMoveAbsolute = TRUE;
2307     }
2309     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2310     {
2311         primaryScreenHeightValid = FALSE;
2312         [self sendDisplaysChanged:FALSE];
2313         [self adjustWindowLevels];
2315         // When the display configuration changes, the cursor position may jump.
2316         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2317         // mouse move event starts over from an absolute baseline.
2318         forceNextMouseMoveAbsolute = TRUE;
2319     }
2321     - (void)applicationDidResignActive:(NSNotification *)notification
2322     {
2323         macdrv_event* event;
2324         WineEventQueue* queue;
2326         [self updateCursorClippingState];
2328         [self invalidateGotFocusEvents];
2330         event = macdrv_create_event(APP_DEACTIVATED, nil);
2332         [eventQueuesLock lock];
2333         for (queue in eventQueues)
2334             [queue postEvent:event];
2335         [eventQueuesLock unlock];
2337         macdrv_release_event(event);
2339         [self releaseMouseCapture];
2340     }
2342     - (void) applicationDidUnhide:(NSNotification*)aNotification
2343     {
2344         [self adjustWindowLevels];
2345     }
2347     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2348     {
2349         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2350         // don't count as "visible windows" for this purpose.
2351         [self unminimizeWindowIfNoneVisible];
2352         return YES;
2353     }
2355     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2356     {
2357         NSApplicationTerminateReply ret = NSTerminateNow;
2358         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2359         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2360         macdrv_event* event;
2361         WineEventQueue* queue;
2363         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2364         event->deliver = 1;
2365         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2366         {
2367             case kAELogOut:
2368             case kAEReallyLogOut:
2369                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2370                 break;
2371             case kAEShowRestartDialog:
2372                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2373                 break;
2374             case kAEShowShutdownDialog:
2375                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2376                 break;
2377             default:
2378                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2379                 break;
2380         }
2382         [eventQueuesLock lock];
2384         if ([eventQueues count])
2385         {
2386             for (queue in eventQueues)
2387                 [queue postEvent:event];
2388             ret = NSTerminateLater;
2389         }
2391         [eventQueuesLock unlock];
2393         macdrv_release_event(event);
2395         return ret;
2396     }
2398     - (void)applicationWillBecomeActive:(NSNotification *)notification
2399     {
2400         macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2401         event->deliver = 1;
2403         [eventQueuesLock lock];
2404         for (WineEventQueue* queue in eventQueues)
2405             [queue postEvent:event];
2406         [eventQueuesLock unlock];
2408         macdrv_release_event(event);
2409     }
2411     - (void)applicationWillResignActive:(NSNotification *)notification
2412     {
2413         [self adjustWindowLevels:NO];
2414     }
2416 /***********************************************************************
2417  *              PerformRequest
2419  * Run-loop-source perform callback.  Pull request blocks from the
2420  * array of queued requests and invoke them.
2421  */
2422 static void PerformRequest(void *info)
2424     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2425     WineApplicationController* controller = [WineApplicationController sharedController];
2427     for (;;)
2428     {
2429         __block dispatch_block_t block;
2431         dispatch_sync(controller->requestsManipQueue, ^{
2432             if ([controller->requests count])
2433             {
2434                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2435                 [controller->requests removeObjectAtIndex:0];
2436             }
2437             else
2438                 block = nil;
2439         });
2441         if (!block)
2442             break;
2444         block();
2445         [block release];
2447         [pool release];
2448         pool = [[NSAutoreleasePool alloc] init];
2449     }
2451     [pool release];
2454 /***********************************************************************
2455  *              OnMainThreadAsync
2457  * Run a block on the main thread asynchronously.
2458  */
2459 void OnMainThreadAsync(dispatch_block_t block)
2461     WineApplicationController* controller = [WineApplicationController sharedController];
2463     block = [block copy];
2464     dispatch_sync(controller->requestsManipQueue, ^{
2465         [controller->requests addObject:block];
2466     });
2467     [block release];
2468     CFRunLoopSourceSignal(controller->requestSource);
2469     CFRunLoopWakeUp(CFRunLoopGetMain());
2472 @end
2474 /***********************************************************************
2475  *              LogError
2476  */
2477 void LogError(const char* func, NSString* format, ...)
2479     va_list args;
2480     va_start(args, format);
2481     LogErrorv(func, format, args);
2482     va_end(args);
2485 /***********************************************************************
2486  *              LogErrorv
2487  */
2488 void LogErrorv(const char* func, NSString* format, va_list args)
2490     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2492     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2493     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2494     [message release];
2496     [pool release];
2499 /***********************************************************************
2500  *              macdrv_window_rejected_focus
2502  * Pass focus to the next window that hasn't already rejected this same
2503  * WINDOW_GOT_FOCUS event.
2504  */
2505 void macdrv_window_rejected_focus(const macdrv_event *event)
2507     OnMainThread(^{
2508         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2509     });
2512 /***********************************************************************
2513  *              macdrv_get_input_source_info
2515  * Returns the keyboard layout uchr data, keyboard type and input source.
2516  */
2517 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2519     OnMainThread(^{
2520         TISInputSourceRef inputSourceLayout;
2522         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2523         if (inputSourceLayout)
2524         {
2525             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2526                                 kTISPropertyUnicodeKeyLayoutData);
2527             *uchr = CFDataCreateCopy(NULL, data);
2528             CFRelease(inputSourceLayout);
2530             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2531             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2532             if (input_source)
2533                 *input_source = TISCopyCurrentKeyboardInputSource();
2534         }
2535     });
2538 /***********************************************************************
2539  *              macdrv_beep
2541  * Play the beep sound configured by the user in System Preferences.
2542  */
2543 void macdrv_beep(void)
2545     OnMainThreadAsync(^{
2546         NSBeep();
2547     });
2550 /***********************************************************************
2551  *              macdrv_set_display_mode
2552  */
2553 int macdrv_set_display_mode(const struct macdrv_display* display,
2554                             CGDisplayModeRef display_mode)
2556     __block int ret;
2558     OnMainThread(^{
2559         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2560     });
2562     return ret;
2565 /***********************************************************************
2566  *              macdrv_set_cursor
2568  * Set the cursor.
2570  * If name is non-NULL, it is a selector for a class method on NSCursor
2571  * identifying the cursor to set.  In that case, frames is ignored.  If
2572  * name is NULL, then frames is used.
2574  * frames is an array of dictionaries.  Each dictionary is a frame of
2575  * an animated cursor.  Under the key "image" is a CGImage for the
2576  * frame.  Under the key "duration" is a CFNumber time interval, in
2577  * seconds, for how long that frame is presented before proceeding to
2578  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2579  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2580  * This is the hot spot, measured in pixels down and to the right of the
2581  * top-left corner of the image.
2583  * If the array has exactly 1 element, the cursor is static, not
2584  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2585  */
2586 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2588     SEL sel;
2590     sel = NSSelectorFromString((NSString*)name);
2591     if (sel)
2592     {
2593         OnMainThreadAsync(^{
2594             WineApplicationController* controller = [WineApplicationController sharedController];
2595             [controller setCursorWithFrames:nil];
2596             controller.cursor = [NSCursor performSelector:sel];
2597             [controller unhideCursor];
2598         });
2599     }
2600     else
2601     {
2602         NSArray* nsframes = (NSArray*)frames;
2603         if ([nsframes count])
2604         {
2605             OnMainThreadAsync(^{
2606                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2607             });
2608         }
2609         else
2610         {
2611             OnMainThreadAsync(^{
2612                 WineApplicationController* controller = [WineApplicationController sharedController];
2613                 [controller setCursorWithFrames:nil];
2614                 [controller hideCursor];
2615             });
2616         }
2617     }
2620 /***********************************************************************
2621  *              macdrv_get_cursor_position
2623  * Obtains the current cursor position.  Returns zero on failure,
2624  * non-zero on success.
2625  */
2626 int macdrv_get_cursor_position(CGPoint *pos)
2628     OnMainThread(^{
2629         NSPoint location = [NSEvent mouseLocation];
2630         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2631         *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2632     });
2634     return TRUE;
2637 /***********************************************************************
2638  *              macdrv_set_cursor_position
2640  * Sets the cursor position without generating events.  Returns zero on
2641  * failure, non-zero on success.
2642  */
2643 int macdrv_set_cursor_position(CGPoint pos)
2645     __block int ret;
2647     OnMainThread(^{
2648         ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2649     });
2651     return ret;
2654 /***********************************************************************
2655  *              macdrv_clip_cursor
2657  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2658  * to or larger than the whole desktop region, the cursor is unclipped.
2659  * Returns zero on failure, non-zero on success.
2660  */
2661 int macdrv_clip_cursor(CGRect r)
2663     __block int ret;
2665     OnMainThread(^{
2666         WineApplicationController* controller = [WineApplicationController sharedController];
2667         BOOL clipping = FALSE;
2668         CGRect rect = r;
2670         if (!CGRectIsInfinite(rect))
2671             rect = cgrect_mac_from_win(rect);
2673         if (!CGRectIsInfinite(rect))
2674         {
2675             NSRect nsrect = NSRectFromCGRect(rect);
2676             NSScreen* screen;
2678             /* Convert the rectangle from top-down coords to bottom-up. */
2679             [controller flipRect:&nsrect];
2681             clipping = FALSE;
2682             for (screen in [NSScreen screens])
2683             {
2684                 if (!NSContainsRect(nsrect, [screen frame]))
2685                 {
2686                     clipping = TRUE;
2687                     break;
2688                 }
2689             }
2690         }
2692         if (clipping)
2693             ret = [controller startClippingCursor:rect];
2694         else
2695             ret = [controller stopClippingCursor];
2696     });
2698     return ret;
2701 /***********************************************************************
2702  *              macdrv_set_application_icon
2704  * Set the application icon.  The images array contains CGImages.  If
2705  * there are more than one, then they represent different sizes or
2706  * color depths from the icon resource.  If images is NULL or empty,
2707  * restores the default application image.
2708  */
2709 void macdrv_set_application_icon(CFArrayRef images)
2711     NSArray* imageArray = (NSArray*)images;
2713     OnMainThreadAsync(^{
2714         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2715     });
2718 /***********************************************************************
2719  *              macdrv_quit_reply
2720  */
2721 void macdrv_quit_reply(int reply)
2723     OnMainThread(^{
2724         [NSApp replyToApplicationShouldTerminate:reply];
2725     });
2728 /***********************************************************************
2729  *              macdrv_using_input_method
2730  */
2731 int macdrv_using_input_method(void)
2733     __block BOOL ret;
2735     OnMainThread(^{
2736         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2737     });
2739     return ret;
2742 /***********************************************************************
2743  *              macdrv_set_mouse_capture_window
2744  */
2745 void macdrv_set_mouse_capture_window(macdrv_window window)
2747     WineWindow* w = (WineWindow*)window;
2749     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2751     OnMainThread(^{
2752         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2753     });
2756 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2757 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2758 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2760 /***********************************************************************
2761  *              macdrv_create_input_source_list
2762  */
2763 CFArrayRef macdrv_create_input_source_list(void)
2765     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2767     OnMainThread(^{
2768         CFArrayRef input_list;
2769         CFDictionaryRef filter_dict;
2770         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2771         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2772         int i;
2774         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2775                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2776         input_list = TISCreateInputSourceList(filter_dict, false);
2778         for (i = 0; i < CFArrayGetCount(input_list); i++)
2779         {
2780             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2781             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2782             CFDictionaryRef entry;
2783             const void *input_keys[3] = { macdrv_input_source_input_key,
2784                                           macdrv_input_source_type_key,
2785                                           macdrv_input_source_lang_key };
2786             const void *input_values[3];
2788             input_values[0] = input;
2789             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2790             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2792             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2793                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2795             CFArrayAppendValue(ret, entry);
2796             CFRelease(entry);
2797         }
2798         CFRelease(input_list);
2799         CFRelease(filter_dict);
2800     });
2802     return ret;
2805 int macdrv_select_input_source(TISInputSourceRef input_source)
2807     __block int ret = FALSE;
2809     OnMainThread(^{
2810         ret = (TISSelectInputSource(input_source) == noErr);
2811     });
2813     return ret;
2816 void macdrv_set_cocoa_retina_mode(int new_mode)
2818     OnMainThread(^{
2819         [[WineApplicationController sharedController] setRetinaMode:new_mode];
2820     });