winemac: Centralize adjusting of window levels using the window server's z-order.
[wine/multimedia.git] / dlls / winemac.drv / cocoa_app.m
blobbdf4d92590defd48e33b547ff6725c08566001f4
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 @implementation WineApplication
37 @synthesize wineController;
39     - (void) sendEvent:(NSEvent*)anEvent
40     {
41         if (![wineController handleEvent:anEvent])
42         {
43             [super sendEvent:anEvent];
44             [wineController didSendEvent:anEvent];
45         }
46     }
48     - (void) setWineController:(WineApplicationController*)newController
49     {
50         wineController = newController;
51         [self setDelegate:wineController];
52     }
54 @end
57 @interface WarpRecord : NSObject
59     CGEventTimestamp timeBefore, timeAfter;
60     CGPoint from, to;
63 @property (nonatomic) CGEventTimestamp timeBefore;
64 @property (nonatomic) CGEventTimestamp timeAfter;
65 @property (nonatomic) CGPoint from;
66 @property (nonatomic) CGPoint to;
68 @end
71 @implementation WarpRecord
73 @synthesize timeBefore, timeAfter, from, to;
75 @end;
78 @interface WineApplicationController ()
80 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
81 @property (copy, nonatomic) NSArray* cursorFrames;
82 @property (retain, nonatomic) NSTimer* cursorTimer;
83 @property (retain, nonatomic) NSImage* applicationIcon;
84 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
85 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
87     - (void) setupObservations;
88     - (void) applicationDidBecomeActive:(NSNotification *)notification;
90     static void PerformRequest(void *info);
92 @end
95 @implementation WineApplicationController
97     @synthesize keyboardType, lastFlagsChanged;
98     @synthesize applicationIcon;
99     @synthesize cursorFrames, cursorTimer;
100     @synthesize mouseCaptureWindow;
102     + (void) initialize
103     {
104         if (self == [WineApplicationController class])
105         {
106             NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
107                                       @"", @"NSQuotedKeystrokeBinding",
108                                       @"", @"NSRepeatCountBinding",
109                                       [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
110                                       nil];
111             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
112         }
113     }
115     + (WineApplicationController*) sharedController
116     {
117         static WineApplicationController* sharedController;
118         static dispatch_once_t once;
120         dispatch_once(&once, ^{
121             sharedController = [[self alloc] init];
122         });
124         return sharedController;
125     }
127     - (id) init
128     {
129         self = [super init];
130         if (self != nil)
131         {
132             CFRunLoopSourceContext context = { 0 };
133             context.perform = PerformRequest;
134             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
135             if (!requestSource)
136             {
137                 [self release];
138                 return nil;
139             }
140             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
141             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
143             requests =  [[NSMutableArray alloc] init];
144             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
146             eventQueues = [[NSMutableArray alloc] init];
147             eventQueuesLock = [[NSLock alloc] init];
149             keyWindows = [[NSMutableArray alloc] init];
151             originalDisplayModes = [[NSMutableDictionary alloc] init];
153             warpRecords = [[NSMutableArray alloc] init];
155             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
156                 !keyWindows || !originalDisplayModes || !warpRecords)
157             {
158                 [self release];
159                 return nil;
160             }
162             [self setupObservations];
164             keyboardType = LMGetKbdType();
166             if ([NSApp isActive])
167                 [self applicationDidBecomeActive:nil];
168         }
169         return self;
170     }
172     - (void) dealloc
173     {
174         [screenFrameCGRects release];
175         [applicationIcon release];
176         [warpRecords release];
177         [cursorTimer release];
178         [cursorFrames release];
179         [originalDisplayModes release];
180         [keyWindows release];
181         [eventQueues release];
182         [eventQueuesLock release];
183         if (requestsManipQueue) dispatch_release(requestsManipQueue);
184         [requests release];
185         if (requestSource)
186         {
187             CFRunLoopSourceInvalidate(requestSource);
188             CFRelease(requestSource);
189         }
190         [super dealloc];
191     }
193     - (void) transformProcessToForeground
194     {
195         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
196         {
197             NSMenu* mainMenu;
198             NSMenu* submenu;
199             NSString* bundleName;
200             NSString* title;
201             NSMenuItem* item;
203             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
204             [NSApp activateIgnoringOtherApps:YES];
206             mainMenu = [[[NSMenu alloc] init] autorelease];
208             submenu = [[[NSMenu alloc] initWithTitle:@"Wine"] autorelease];
209             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
210             if ([bundleName length])
211                 title = [NSString stringWithFormat:@"Quit %@", bundleName];
212             else
213                 title = @"Quit";
214             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
215             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
216             item = [[[NSMenuItem alloc] init] autorelease];
217             [item setTitle:@"Wine"];
218             [item setSubmenu:submenu];
219             [mainMenu addItem:item];
221             submenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
222             [submenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
223             [submenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
224             [submenu addItem:[NSMenuItem separatorItem]];
225             [submenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
226             item = [[[NSMenuItem alloc] init] autorelease];
227             [item setTitle:@"Window"];
228             [item setSubmenu:submenu];
229             [mainMenu addItem:item];
231             [NSApp setMainMenu:mainMenu];
232             [NSApp setWindowsMenu:submenu];
234             [NSApp setApplicationIconImage:self.applicationIcon];
235         }
236     }
238     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
239     {
240         PerformRequest(NULL);
242         do
243         {
244             if (processEvents)
245             {
246                 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
247                 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
248                                                     untilDate:timeout
249                                                        inMode:NSDefaultRunLoopMode
250                                                       dequeue:YES];
251                 if (event)
252                     [NSApp sendEvent:event];
253                 [pool release];
254             }
255             else
256                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
257         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
259         return *done;
260     }
262     - (BOOL) registerEventQueue:(WineEventQueue*)queue
263     {
264         [eventQueuesLock lock];
265         [eventQueues addObject:queue];
266         [eventQueuesLock unlock];
267         return TRUE;
268     }
270     - (void) unregisterEventQueue:(WineEventQueue*)queue
271     {
272         [eventQueuesLock lock];
273         [eventQueues removeObjectIdenticalTo:queue];
274         [eventQueuesLock unlock];
275     }
277     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
278     {
279         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
280     }
282     - (double) ticksForEventTime:(NSTimeInterval)eventTime
283     {
284         return (eventTime + eventTimeAdjustment) * 1000;
285     }
287     /* Invalidate old focus offers across all queues. */
288     - (void) invalidateGotFocusEvents
289     {
290         WineEventQueue* queue;
292         windowFocusSerial++;
294         [eventQueuesLock lock];
295         for (queue in eventQueues)
296         {
297             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
298                                    forWindow:nil];
299         }
300         [eventQueuesLock unlock];
301     }
303     - (void) windowGotFocus:(WineWindow*)window
304     {
305         macdrv_event* event;
307         [self invalidateGotFocusEvents];
309         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
310         event->window_got_focus.serial = windowFocusSerial;
311         if (triedWindows)
312             event->window_got_focus.tried_windows = [triedWindows retain];
313         else
314             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
315         [window.queue postEvent:event];
316         macdrv_release_event(event);
317     }
319     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
320     {
321         if (event->window_got_focus.serial == windowFocusSerial)
322         {
323             NSMutableArray* windows = [keyWindows mutableCopy];
324             NSNumber* windowNumber;
325             WineWindow* window;
327             for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
328             {
329                 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
330                 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
331                     ![windows containsObject:window])
332                     [windows addObject:window];
333             }
335             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
336             [triedWindows addObject:(WineWindow*)event->window];
337             for (window in windows)
338             {
339                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
340                 {
341                     [window makeKeyWindow];
342                     break;
343                 }
344             }
345             triedWindows = nil;
346             [windows release];
347         }
348     }
350     - (void) keyboardSelectionDidChange
351     {
352         TISInputSourceRef inputSource;
354         inputSourceIsInputMethodValid = FALSE;
356         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
357         if (inputSource)
358         {
359             CFDataRef uchr;
360             uchr = TISGetInputSourceProperty(inputSource,
361                     kTISPropertyUnicodeKeyLayoutData);
362             if (uchr)
363             {
364                 macdrv_event* event;
365                 WineEventQueue* queue;
367                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
368                 event->keyboard_changed.keyboard_type = self.keyboardType;
369                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
370                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
372                 if (event->keyboard_changed.uchr)
373                 {
374                     [eventQueuesLock lock];
376                     for (queue in eventQueues)
377                         [queue postEvent:event];
379                     [eventQueuesLock unlock];
380                 }
382                 macdrv_release_event(event);
383             }
385             CFRelease(inputSource);
386         }
387     }
389     - (CGFloat) primaryScreenHeight
390     {
391         if (!primaryScreenHeightValid)
392         {
393             NSArray* screens = [NSScreen screens];
394             NSUInteger count = [screens count];
395             if (count)
396             {
397                 NSUInteger size;
398                 CGRect* rect;
399                 NSScreen* screen;
401                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
402                 primaryScreenHeightValid = TRUE;
404                 size = count * sizeof(CGRect);
405                 if (!screenFrameCGRects)
406                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
407                 else
408                     [screenFrameCGRects setLength:size];
410                 rect = [screenFrameCGRects mutableBytes];
411                 for (screen in screens)
412                 {
413                     CGRect temp = NSRectToCGRect([screen frame]);
414                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
415                     *rect++ = temp;
416                 }
417             }
418             else
419                 return 1280; /* arbitrary value */
420         }
422         return primaryScreenHeight;
423     }
425     - (NSPoint) flippedMouseLocation:(NSPoint)point
426     {
427         /* This relies on the fact that Cocoa's mouse location points are
428            actually off by one (precisely because they were flipped from
429            Quartz screen coordinates using this same technique). */
430         point.y = [self primaryScreenHeight] - point.y;
431         return point;
432     }
434     - (void) flipRect:(NSRect*)rect
435     {
436         // We don't use -primaryScreenHeight here so there's no chance of having
437         // out-of-date cached info.  This method is called infrequently enough
438         // that getting the screen height each time is not prohibitively expensive.
439         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
440     }
442     - (WineWindow*) frontWineWindow
443     {
444         NSNumber* windowNumber;
445         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
446         {
447             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
448             if ([window isKindOfClass:[WineWindow class]] && [window screen])
449                 return (WineWindow*)window;
450         }
452         return nil;
453     }
455     - (void) adjustWindowLevels:(BOOL)active
456     {
457         NSArray* windowNumbers = [NSWindow windowNumbersWithOptions:0];
458         NSMutableArray* wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
459         NSNumber* windowNumber;
460         NSUInteger nextFloatingIndex = 0;
461         __block NSInteger maxLevel = NSIntegerMin;
462         __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
463         __block WineWindow* prev = nil;
464         WineWindow* window;
466         // For the most part, we rely on the window server's ordering of the windows
467         // to be authoritative.  The one exception is if the "floating" property of
468         // one of the windows has been changed, it may be in the wrong level and thus
469         // in the order.  This method is what's supposed to fix that up.  So build
470         // a list of Wine windows sorted first by floating-ness and then by order
471         // as indicated by the window server.
472         for (windowNumber in windowNumbers)
473         {
474             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
475             if ([window isKindOfClass:[WineWindow class]])
476             {
477                 if (window.floating)
478                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
479                 else
480                     [wineWindows addObject:window];
481             }
482         }
484         NSDisableScreenUpdates();
486         // Go from back to front so that all windows in front of one which is
487         // elevated for full-screen are also elevated.
488         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
489                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
490             WineWindow* window = (WineWindow*)obj;
491             NSInteger origLevel = [window level];
492             NSInteger newLevel = [window minimumLevelForActive:active];
494             if (newLevel < maxLevel)
495                 newLevel = maxLevel;
496             else
497                 maxLevel = newLevel;
499             if (!window.floating && maxNonfloatingLevel < newLevel)
500                 maxNonfloatingLevel = newLevel;
502             if (newLevel != origLevel)
503             {
504                 [window setLevel:newLevel];
506                 // -setLevel: puts the window at the front of its new level.  If
507                 // we decreased the level, that's good (it was in front of that
508                 // level before, so it should still be now).  But if we increased
509                 // the level, the window should be toward the back (but still
510                 // ahead of the previous windows we did this to).
511                 if (origLevel < newLevel)
512                 {
513                     if (prev)
514                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
515                     else
516                         [window orderBack:nil];
517                 }
518             }
520             prev = window;
521         }];
523         NSEnableScreenUpdates();
525         [wineWindows release];
527         // The above took care of the visible windows on the current space.  That
528         // leaves windows on other spaces, minimized windows, and windows which
529         // are not ordered in.  We want to leave windows on other spaces alone
530         // so the space remains just as they left it (when viewed in Exposé or
531         // Mission Control, for example).  We'll adjust the window levels again
532         // after we switch to another space, anyway.  Windows which aren't
533         // ordered in will be handled when we order them in.  Minimized windows
534         // on the current space should be set to the level they would have gotten
535         // if they were at the front of the windows with the same floating-ness,
536         // because that's where they'll go if/when they are unminimized.  Again,
537         // for good measure we'll adjust window levels again when a window is
538         // unminimized, too.
539         for (window in [NSApp windows])
540         {
541             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
542                 [window isOnActiveSpace])
543             {
544                 NSInteger origLevel = [window level];
545                 NSInteger newLevel = [window minimumLevelForActive:YES];
546                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
548                 if (newLevel < maxLevelForType)
549                     newLevel = maxLevelForType;
551                 if (newLevel != origLevel)
552                     [window setLevel:newLevel];
553             }
554         }
555     }
557     - (void) adjustWindowLevels
558     {
559         [self adjustWindowLevels:[NSApp isActive]];
560     }
562     - (void) sendDisplaysChanged:(BOOL)activating
563     {
564         macdrv_event* event;
565         WineEventQueue* queue;
567         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
568         event->displays_changed.activating = activating;
570         [eventQueuesLock lock];
572         // If we're activating, then we just need one of our threads to get the
573         // event, so it can send it directly to the desktop window.  Otherwise,
574         // we need all of the threads to get it because we don't know which owns
575         // the desktop window and only that one will do anything with it.
576         if (activating) event->deliver = 1;
578         for (queue in eventQueues)
579             [queue postEvent:event];
580         [eventQueuesLock unlock];
582         macdrv_release_event(event);
583     }
585     // We can compare two modes directly using CFEqual, but that may require that
586     // they are identical to a level that we don't need.  In particular, when the
587     // OS switches between the integrated and discrete GPUs, the set of display
588     // modes can change in subtle ways.  We're interested in whether two modes
589     // match in their most salient features, even if they aren't identical.
590     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
591     {
592         NSString *encoding1, *encoding2;
593         uint32_t ioflags1, ioflags2, different;
594         double refresh1, refresh2;
596         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
597         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
599         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
600         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
601         if (![encoding1 isEqualToString:encoding2]) return FALSE;
603         ioflags1 = CGDisplayModeGetIOFlags(mode1);
604         ioflags2 = CGDisplayModeGetIOFlags(mode2);
605         different = ioflags1 ^ ioflags2;
606         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
607                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
608             return FALSE;
610         refresh1 = CGDisplayModeGetRefreshRate(mode1);
611         if (refresh1 == 0) refresh1 = 60;
612         refresh2 = CGDisplayModeGetRefreshRate(mode2);
613         if (refresh2 == 0) refresh2 = 60;
614         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
616         return TRUE;
617     }
619     - (CGDisplayModeRef)modeMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
620     {
621         CGDisplayModeRef ret = NULL;
622         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, NULL) autorelease];
623         for (id candidateModeObject in modes)
624         {
625             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
626             if ([self mode:candidateMode matchesMode:mode])
627             {
628                 ret = candidateMode;
629                 break;
630             }
631         }
632         return ret;
633     }
635     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
636     {
637         BOOL ret = FALSE;
638         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
639         CGDisplayModeRef currentMode, originalMode;
641         currentMode = CGDisplayCopyDisplayMode(displayID);
642         if (!currentMode) // Invalid display ID
643             return FALSE;
645         if ([self mode:mode matchesMode:currentMode]) // Already there!
646         {
647             CGDisplayModeRelease(currentMode);
648             return TRUE;
649         }
651         mode = [self modeMatchingMode:mode forDisplay:displayID];
652         if (!mode)
653         {
654             CGDisplayModeRelease(currentMode);
655             return FALSE;
656         }
658         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
659         if (!originalMode)
660             originalMode = currentMode;
662         if ([self mode:mode matchesMode:originalMode])
663         {
664             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
665             {
666                 CGRestorePermanentDisplayConfiguration();
667                 CGReleaseAllDisplays();
668                 [originalDisplayModes removeAllObjects];
669                 ret = TRUE;
670             }
671             else // ... otherwise, try to restore just the one display
672             {
673                 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
674                 {
675                     [originalDisplayModes removeObjectForKey:displayIDKey];
676                     ret = TRUE;
677                 }
678             }
679         }
680         else
681         {
682             if ([originalDisplayModes count] || CGCaptureAllDisplays() == CGDisplayNoErr)
683             {
684                 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
685                 {
686                     [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
687                     ret = TRUE;
688                 }
689                 else if (![originalDisplayModes count])
690                 {
691                     CGRestorePermanentDisplayConfiguration();
692                     CGReleaseAllDisplays();
693                 }
694             }
695         }
697         CGDisplayModeRelease(currentMode);
699         if (ret)
700             [self adjustWindowLevels];
702         return ret;
703     }
705     - (BOOL) areDisplaysCaptured
706     {
707         return ([originalDisplayModes count] > 0);
708     }
710     - (void) hideCursor
711     {
712         if (!cursorHidden)
713         {
714             [NSCursor hide];
715             cursorHidden = TRUE;
716         }
717     }
719     - (void) unhideCursor
720     {
721         if (cursorHidden)
722         {
723             [NSCursor unhide];
724             cursorHidden = FALSE;
725         }
726     }
728     - (void) setCursor
729     {
730         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
731         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
732         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
733         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
734         CGPoint hotSpot;
735         NSCursor* cursor;
737         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
738             hotSpot = CGPointZero;
739         cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)];
740         [image release];
741         [cursor set];
742         [self unhideCursor];
743         [cursor release];
744     }
746     - (void) nextCursorFrame:(NSTimer*)theTimer
747     {
748         NSDictionary* frame;
749         NSTimeInterval duration;
750         NSDate* date;
752         cursorFrame++;
753         if (cursorFrame >= [cursorFrames count])
754             cursorFrame = 0;
755         [self setCursor];
757         frame = [cursorFrames objectAtIndex:cursorFrame];
758         duration = [[frame objectForKey:@"duration"] doubleValue];
759         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
760         [cursorTimer setFireDate:date];
761     }
763     - (void) setCursorWithFrames:(NSArray*)frames
764     {
765         if (self.cursorFrames == frames)
766             return;
768         self.cursorFrames = frames;
769         cursorFrame = 0;
770         [cursorTimer invalidate];
771         self.cursorTimer = nil;
773         if ([frames count])
774         {
775             if ([frames count] > 1)
776             {
777                 NSDictionary* frame = [frames objectAtIndex:0];
778                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
779                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
780                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
781                                                              interval:1000000
782                                                                target:self
783                                                              selector:@selector(nextCursorFrame:)
784                                                              userInfo:nil
785                                                               repeats:YES] autorelease];
786                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
787             }
789             [self setCursor];
790         }
791     }
793     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
794     {
795         NSImage* nsimage = nil;
797         if ([images count])
798         {
799             NSSize bestSize = NSZeroSize;
800             id image;
802             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
804             for (image in images)
805             {
806                 CGImageRef cgimage = (CGImageRef)image;
807                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
808                 if (imageRep)
809                 {
810                     NSSize size = [imageRep size];
812                     [nsimage addRepresentation:imageRep];
813                     [imageRep release];
815                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
816                         bestSize = size;
817                 }
818             }
820             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
821                 [nsimage setSize:bestSize];
822             else
823                 nsimage = nil;
824         }
826         self.applicationIcon = nsimage;
827         [NSApp setApplicationIconImage:nsimage];
828     }
830     - (void) handleCommandTab
831     {
832         if ([NSApp isActive])
833         {
834             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
835             NSRunningApplication* app;
836             NSRunningApplication* otherValidApp = nil;
838             if ([originalDisplayModes count])
839             {
840                 CGRestorePermanentDisplayConfiguration();
841                 CGReleaseAllDisplays();
842                 [originalDisplayModes removeAllObjects];
843             }
845             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
846             {
847                 if (![app isEqual:thisApp] && !app.terminated &&
848                     app.activationPolicy == NSApplicationActivationPolicyRegular)
849                 {
850                     if (!app.hidden)
851                     {
852                         // There's another visible app.  Just hide ourselves and let
853                         // the system activate the other app.
854                         [NSApp hide:self];
855                         return;
856                     }
858                     if (!otherValidApp)
859                         otherValidApp = app;
860                 }
861             }
863             // Didn't find a visible GUI app.  Try the Finder or, if that's not
864             // running, the first hidden GUI app.  If even that doesn't work, we
865             // just fail to switch and remain the active app.
866             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
867             if (!app) app = otherValidApp;
868             [app unhide];
869             [app activateWithOptions:0];
870         }
871     }
873     /*
874      * ---------- Cursor clipping methods ----------
875      *
876      * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
877      * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
878      * equivalent: CGAssociateMouseAndMouseCursorPosition(false).  For the
879      * general case, we leverage that.  We disassociate mouse movements from
880      * the cursor position and then move the cursor manually, keeping it within
881      * the clipping rectangle.
882      *
883      * Moving the cursor manually isn't enough.  We need to modify the event
884      * stream so that the events have the new location, too.  We need to do
885      * this at a point before the events enter Cocoa, so that Cocoa will assign
886      * the correct window to the event.  So, we install a Quartz event tap to
887      * do that.
888      *
889      * Also, there's a complication when we move the cursor.  We use
890      * CGWarpMouseCursorPosition().  That doesn't generate mouse movement
891      * events, but the change of cursor position is incorporated into the
892      * deltas of the next mouse move event.  When the mouse is disassociated
893      * from the cursor position, we need the deltas to only reflect actual
894      * device movement, not programmatic changes.  So, the event tap cancels
895      * out the change caused by our calls to CGWarpMouseCursorPosition().
896      */
897     - (void) clipCursorLocation:(CGPoint*)location
898     {
899         if (location->x < CGRectGetMinX(cursorClipRect))
900             location->x = CGRectGetMinX(cursorClipRect);
901         if (location->y < CGRectGetMinY(cursorClipRect))
902             location->y = CGRectGetMinY(cursorClipRect);
903         if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
904             location->x = CGRectGetMaxX(cursorClipRect) - 1;
905         if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
906             location->y = CGRectGetMaxY(cursorClipRect) - 1;
907     }
909     - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
910     {
911         CGPoint oldLocation;
913         if (currentLocation)
914             oldLocation = *currentLocation;
915         else
916             oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
918         if (!CGPointEqualToPoint(oldLocation, *newLocation))
919         {
920             WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
921             CGError err;
923             warpRecord.from = oldLocation;
924             warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
926             /* Actually move the cursor. */
927             err = CGWarpMouseCursorPosition(*newLocation);
928             if (err != kCGErrorSuccess)
929                 return FALSE;
931             warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
932             *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
934             if (!CGPointEqualToPoint(oldLocation, *newLocation))
935             {
936                 warpRecord.to = *newLocation;
937                 [warpRecords addObject:warpRecord];
938             }
939         }
941         return TRUE;
942     }
944     - (BOOL) isMouseMoveEventType:(CGEventType)type
945     {
946         switch(type)
947         {
948         case kCGEventMouseMoved:
949         case kCGEventLeftMouseDragged:
950         case kCGEventRightMouseDragged:
951         case kCGEventOtherMouseDragged:
952             return TRUE;
953         }
955         return FALSE;
956     }
958     - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
959     {
960         int warpsFinished = 0;
961         for (WarpRecord* warpRecord in warpRecords)
962         {
963             if (warpRecord.timeAfter < eventTime ||
964                 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
965                 warpsFinished++;
966             else
967                 break;
968         }
970         return warpsFinished;
971     }
973     - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
974                                 type:(CGEventType)type
975                                event:(CGEventRef)event
976     {
977         CGEventTimestamp eventTime;
978         CGPoint eventLocation, cursorLocation;
980         if (type == kCGEventTapDisabledByUserInput)
981             return event;
982         if (type == kCGEventTapDisabledByTimeout)
983         {
984             CGEventTapEnable(cursorClippingEventTap, TRUE);
985             return event;
986         }
988         if (!clippingCursor)
989             return event;
991         eventTime = CGEventGetTimestamp(event);
992         lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
994         eventLocation = CGEventGetLocation(event);
996         cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
998         if ([self isMouseMoveEventType:type])
999         {
1000             double deltaX, deltaY;
1001             int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1002             int i;
1004             deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1005             deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1007             for (i = 0; i < warpsFinished; i++)
1008             {
1009                 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1010                 deltaX -= warpRecord.to.x - warpRecord.from.x;
1011                 deltaY -= warpRecord.to.y - warpRecord.from.y;
1012                 [warpRecords removeObjectAtIndex:0];
1013             }
1015             if (warpsFinished)
1016             {
1017                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1018                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1019             }
1021             synthesizedLocation.x += deltaX;
1022             synthesizedLocation.y += deltaY;
1023         }
1025         // If the event is destined for another process, don't clip it.  This may
1026         // happen if the user activates Exposé or Mission Control.  In that case,
1027         // our app does not resign active status, so clipping is still in effect,
1028         // but the cursor should not actually be clipped.
1029         //
1030         // In addition, the fact that mouse moves may have been delivered to a
1031         // different process means we have to treat the next one we receive as
1032         // absolute rather than relative.
1033         if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1034             [self clipCursorLocation:&synthesizedLocation];
1035         else
1036             lastSetCursorPositionTime = lastEventTapEventTime;
1038         [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1039         if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1040             CGEventSetLocation(event, synthesizedLocation);
1042         return event;
1043     }
1045     CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1046                                        CGEventRef event, void *refcon)
1047     {
1048         WineApplicationController* controller = refcon;
1049         return [controller eventTapWithProxy:proxy type:type event:event];
1050     }
1052     - (BOOL) installEventTap
1053     {
1054         ProcessSerialNumber psn;
1055         OSErr err;
1056         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
1057                            CGEventMaskBit(kCGEventLeftMouseUp)          |
1058                            CGEventMaskBit(kCGEventRightMouseDown)       |
1059                            CGEventMaskBit(kCGEventRightMouseUp)         |
1060                            CGEventMaskBit(kCGEventMouseMoved)           |
1061                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
1062                            CGEventMaskBit(kCGEventRightMouseDragged)    |
1063                            CGEventMaskBit(kCGEventOtherMouseDown)       |
1064                            CGEventMaskBit(kCGEventOtherMouseUp)         |
1065                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
1066                            CGEventMaskBit(kCGEventScrollWheel);
1067         CFRunLoopSourceRef source;
1068         void* appServices;
1069         OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1071         if (cursorClippingEventTap)
1072             return TRUE;
1074         // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1075         // framework with dlsym() because the Win32 function of the same name
1076         // obscures it.
1077         appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1078         if (!appServices)
1079             return FALSE;
1081         pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1082         if (!pGetCurrentProcess)
1083         {
1084             dlclose(appServices);
1085             return FALSE;
1086         }
1088         err = pGetCurrentProcess(&psn);
1089         dlclose(appServices);
1090         if (err != noErr)
1091             return FALSE;
1093         // We create an annotated session event tap rather than a process-specific
1094         // event tap because we need to programmatically move the cursor even when
1095         // mouse moves are directed to other processes.  We disable our tap when
1096         // other processes are active, but things like Exposé are handled by other
1097         // processes even when we remain active.
1098         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1099             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1100         if (!cursorClippingEventTap)
1101             return FALSE;
1103         CGEventTapEnable(cursorClippingEventTap, FALSE);
1105         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1106         if (!source)
1107         {
1108             CFRelease(cursorClippingEventTap);
1109             cursorClippingEventTap = NULL;
1110             return FALSE;
1111         }
1113         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1114         CFRelease(source);
1115         return TRUE;
1116     }
1118     - (BOOL) setCursorPosition:(CGPoint)pos
1119     {
1120         BOOL ret;
1122         if (clippingCursor)
1123         {
1124             [self clipCursorLocation:&pos];
1126             ret = [self warpCursorTo:&pos from:NULL];
1127             synthesizedLocation = pos;
1128             if (ret)
1129             {
1130                 // We want to discard mouse-move events that have already been
1131                 // through the event tap, because it's too late to account for
1132                 // the setting of the cursor position with them.  However, the
1133                 // events that may be queued with times after that but before
1134                 // the above warp can still be used.  So, use the last event
1135                 // tap event time so that -sendEvent: doesn't discard them.
1136                 lastSetCursorPositionTime = lastEventTapEventTime;
1137             }
1138         }
1139         else
1140         {
1141             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1142             if (ret)
1143             {
1144                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1146                 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1147                 // the mouse from the cursor position for 0.25 seconds.  This means
1148                 // that mouse movement during that interval doesn't move the cursor
1149                 // and events carry a constant location (the warped-to position)
1150                 // even though they have delta values.  This screws us up because
1151                 // the accumulated deltas we send to Wine don't match any eventual
1152                 // absolute position we send (like with a button press).  We can
1153                 // work around this by simply forcibly reassociating the mouse and
1154                 // cursor position.
1155                 CGAssociateMouseAndMouseCursorPosition(true);
1156             }
1157         }
1159         if (ret)
1160         {
1161             WineEventQueue* queue;
1163             // Discard all pending mouse move events.
1164             [eventQueuesLock lock];
1165             for (queue in eventQueues)
1166             {
1167                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1168                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1169                                        forWindow:nil];
1170                 [queue resetMouseEventPositions:pos];
1171             }
1172             [eventQueuesLock unlock];
1173         }
1175         return ret;
1176     }
1178     - (void) activateCursorClipping
1179     {
1180         if (clippingCursor)
1181         {
1182             CGEventTapEnable(cursorClippingEventTap, TRUE);
1183             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1184         }
1185     }
1187     - (void) deactivateCursorClipping
1188     {
1189         if (clippingCursor)
1190         {
1191             CGEventTapEnable(cursorClippingEventTap, FALSE);
1192             [warpRecords removeAllObjects];
1193             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1194         }
1195     }
1197     - (BOOL) startClippingCursor:(CGRect)rect
1198     {
1199         CGError err;
1201         if (!cursorClippingEventTap && ![self installEventTap])
1202             return FALSE;
1204         err = CGAssociateMouseAndMouseCursorPosition(false);
1205         if (err != kCGErrorSuccess)
1206             return FALSE;
1208         clippingCursor = TRUE;
1209         cursorClipRect = rect;
1210         if ([NSApp isActive])
1211             [self activateCursorClipping];
1213         return TRUE;
1214     }
1216     - (BOOL) stopClippingCursor
1217     {
1218         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1219         if (err != kCGErrorSuccess)
1220             return FALSE;
1222         [self deactivateCursorClipping];
1223         clippingCursor = FALSE;
1225         return TRUE;
1226     }
1228     - (void) handleMouseMove:(NSEvent*)anEvent
1229     {
1230         WineWindow* targetWindow;
1231         BOOL drag = [anEvent type] != NSMouseMoved;
1233         if (mouseCaptureWindow)
1234             targetWindow = mouseCaptureWindow;
1235         else if (drag)
1236             targetWindow = (WineWindow*)[anEvent window];
1237         else
1238         {
1239             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1240                event indicates its window is the main window, even if the cursor is
1241                over a different window.  Find the actual WineWindow that is under the
1242                cursor and post the event as being for that window. */
1243             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1244             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1245             NSInteger windowUnderNumber;
1247             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1248                                   belowWindowWithWindowNumber:0];
1249             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1250         }
1252         if ([targetWindow isKindOfClass:[WineWindow class]])
1253         {
1254             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1255             macdrv_event* event;
1256             BOOL absolute;
1258             // If we recently warped the cursor (other than in our cursor-clipping
1259             // event tap), discard mouse move events until we see an event which is
1260             // later than that time.
1261             if (lastSetCursorPositionTime)
1262             {
1263                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1264                     return;
1266                 lastSetCursorPositionTime = 0;
1267                 forceNextMouseMoveAbsolute = TRUE;
1268             }
1270             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1271             {
1272                 absolute = TRUE;
1273                 forceNextMouseMoveAbsolute = FALSE;
1274             }
1275             else
1276             {
1277                 // Send absolute move events if the cursor is in the interior of
1278                 // its range.  Only send relative moves if the cursor is pinned to
1279                 // the boundaries of where it can go.  We compute the position
1280                 // that's one additional point in the direction of movement.  If
1281                 // that is outside of the clipping rect or desktop region (the
1282                 // union of the screen frames), then we figure the cursor would
1283                 // have moved outside if it could but it was pinned.
1284                 CGPoint computedPoint = point;
1285                 CGFloat deltaX = [anEvent deltaX];
1286                 CGFloat deltaY = [anEvent deltaY];
1288                 if (deltaX > 0.001)
1289                     computedPoint.x++;
1290                 else if (deltaX < -0.001)
1291                     computedPoint.x--;
1293                 if (deltaY > 0.001)
1294                     computedPoint.y++;
1295                 else if (deltaY < -0.001)
1296                     computedPoint.y--;
1298                 // Assume cursor is pinned for now
1299                 absolute = FALSE;
1300                 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1301                 {
1302                     const CGRect* rects;
1303                     NSUInteger count, i;
1305                     // Caches screenFrameCGRects if necessary
1306                     [self primaryScreenHeight];
1308                     rects = [screenFrameCGRects bytes];
1309                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1311                     for (i = 0; i < count; i++)
1312                     {
1313                         if (CGRectContainsPoint(rects[i], computedPoint))
1314                         {
1315                             absolute = TRUE;
1316                             break;
1317                         }
1318                     }
1319                 }
1320             }
1322             if (absolute)
1323             {
1324                 if (clippingCursor)
1325                     [self clipCursorLocation:&point];
1327                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1328                 event->mouse_moved.x = point.x;
1329                 event->mouse_moved.y = point.y;
1331                 mouseMoveDeltaX = 0;
1332                 mouseMoveDeltaY = 0;
1333             }
1334             else
1335             {
1336                 /* Add event delta to accumulated delta error */
1337                 /* deltaY is already flipped */
1338                 mouseMoveDeltaX += [anEvent deltaX];
1339                 mouseMoveDeltaY += [anEvent deltaY];
1341                 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1342                 event->mouse_moved.x = mouseMoveDeltaX;
1343                 event->mouse_moved.y = mouseMoveDeltaY;
1345                 /* Keep the remainder after integer truncation. */
1346                 mouseMoveDeltaX -= event->mouse_moved.x;
1347                 mouseMoveDeltaY -= event->mouse_moved.y;
1348             }
1350             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1351             {
1352                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1353                 event->mouse_moved.drag = drag;
1355                 [targetWindow.queue postEvent:event];
1356             }
1358             macdrv_release_event(event);
1360             lastTargetWindow = targetWindow;
1361         }
1362         else if (lastTargetWindow)
1363         {
1364             [[NSCursor arrowCursor] set];
1365             [self unhideCursor];
1366             lastTargetWindow = nil;
1367         }
1368     }
1370     - (void) handleMouseButton:(NSEvent*)theEvent
1371     {
1372         WineWindow* window;
1374         if (mouseCaptureWindow)
1375             window = mouseCaptureWindow;
1376         else
1377             window = (WineWindow*)[theEvent window];
1379         if ([window isKindOfClass:[WineWindow class]])
1380         {
1381             NSEventType type = [theEvent type];
1382             BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1383             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1384             BOOL process;
1386             if (clippingCursor)
1387                 [self clipCursorLocation:&pt];
1389             if (pressed)
1390             {
1391                 if (mouseCaptureWindow)
1392                     process = TRUE;
1393                 else
1394                 {
1395                     // Test if the click was in the window's content area.
1396                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1397                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1398                     process = NSPointInRect(nspoint, contentRect);
1399                     if (process && [window styleMask] & NSResizableWindowMask)
1400                     {
1401                         // Ignore clicks in the grow box (resize widget).
1402                         HIPoint origin = { 0, 0 };
1403                         HIThemeGrowBoxDrawInfo info = { 0 };
1404                         HIRect bounds;
1405                         OSStatus status;
1407                         info.kind = kHIThemeGrowBoxKindNormal;
1408                         info.direction = kThemeGrowRight | kThemeGrowDown;
1409                         if ([window styleMask] & NSUtilityWindowMask)
1410                             info.size = kHIThemeGrowBoxSizeSmall;
1411                         else
1412                             info.size = kHIThemeGrowBoxSizeNormal;
1414                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1415                         if (status == noErr)
1416                         {
1417                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1418                                                         NSMinY(contentRect),
1419                                                         bounds.size.width,
1420                                                         bounds.size.height);
1421                             process = !NSPointInRect(nspoint, growBox);
1422                         }
1423                     }
1424                 }
1425                 if (process)
1426                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1427             }
1428             else
1429             {
1430                 NSEventType downType = type - 1;
1431                 NSUInteger downMask = NSEventMaskFromType(downType);
1432                 process = (unmatchedMouseDowns & downMask) != 0;
1433                 unmatchedMouseDowns &= ~downMask;
1434             }
1436             if (process)
1437             {
1438                 macdrv_event* event;
1440                 event = macdrv_create_event(MOUSE_BUTTON, window);
1441                 event->mouse_button.button = [theEvent buttonNumber];
1442                 event->mouse_button.pressed = pressed;
1443                 event->mouse_button.x = pt.x;
1444                 event->mouse_button.y = pt.y;
1445                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1447                 [window.queue postEvent:event];
1449                 macdrv_release_event(event);
1450             }
1451         }
1453         // Since mouse button events deliver absolute cursor position, the
1454         // accumulating delta from move events is invalidated.  Make sure
1455         // next mouse move event starts over from an absolute baseline.
1456         // Also, it's at least possible that the title bar widgets (e.g. close
1457         // button, etc.) could enter an internal event loop on a mouse down that
1458         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1459         // dragged events and, after that, any notion of the cursor position
1460         // computed from accumulating deltas would be wrong.
1461         forceNextMouseMoveAbsolute = TRUE;
1462     }
1464     - (void) handleScrollWheel:(NSEvent*)theEvent
1465     {
1466         WineWindow* window;
1468         if (mouseCaptureWindow)
1469             window = mouseCaptureWindow;
1470         else
1471             window = (WineWindow*)[theEvent window];
1473         if ([window isKindOfClass:[WineWindow class]])
1474         {
1475             CGEventRef cgevent = [theEvent CGEvent];
1476             CGPoint pt = CGEventGetLocation(cgevent);
1477             BOOL process;
1479             if (clippingCursor)
1480                 [self clipCursorLocation:&pt];
1482             if (mouseCaptureWindow)
1483                 process = TRUE;
1484             else
1485             {
1486                 // Only process the event if it was in the window's content area.
1487                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1488                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1489                 process = NSPointInRect(nspoint, contentRect);
1490             }
1492             if (process)
1493             {
1494                 macdrv_event* event;
1495                 CGFloat x, y;
1496                 BOOL continuous = FALSE;
1498                 event = macdrv_create_event(MOUSE_SCROLL, window);
1499                 event->mouse_scroll.x = pt.x;
1500                 event->mouse_scroll.y = pt.y;
1501                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1503                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1504                 {
1505                     continuous = TRUE;
1507                     /* Continuous scroll wheel events come from high-precision scrolling
1508                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1509                        For these, we can get more precise data from the CGEvent API. */
1510                     /* Axis 1 is vertical, axis 2 is horizontal. */
1511                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1512                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1513                 }
1514                 else
1515                 {
1516                     double pixelsPerLine = 10;
1517                     CGEventSourceRef source;
1519                     /* The non-continuous values are in units of "lines", not pixels. */
1520                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1521                     {
1522                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1523                         CFRelease(source);
1524                     }
1526                     x = pixelsPerLine * [theEvent deltaX];
1527                     y = pixelsPerLine * [theEvent deltaY];
1528                 }
1530                 /* Mac: negative is right or down, positive is left or up.
1531                    Win32: negative is left or down, positive is right or up.
1532                    So, negate the X scroll value to translate. */
1533                 x = -x;
1535                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1536                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1537                    6 times the pixel value. */
1538                 event->mouse_scroll.x_scroll = 6 * x;
1539                 event->mouse_scroll.y_scroll = 6 * y;
1541                 if (!continuous)
1542                 {
1543                     /* For non-continuous "clicky" wheels, if there was any motion, make
1544                        sure there was at least WHEEL_DELTA motion.  This is so, at slow
1545                        speeds where the system's acceleration curve is actually reducing the
1546                        scroll distance, the user is sure to get some action out of each click.
1547                        For example, this is important for rotating though weapons in a
1548                        first-person shooter. */
1549                     if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1550                         event->mouse_scroll.x_scroll = 120;
1551                     else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1552                         event->mouse_scroll.x_scroll = -120;
1554                     if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1555                         event->mouse_scroll.y_scroll = 120;
1556                     else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1557                         event->mouse_scroll.y_scroll = -120;
1558                 }
1560                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1561                     [window.queue postEvent:event];
1563                 macdrv_release_event(event);
1565                 // Since scroll wheel events deliver absolute cursor position, the
1566                 // accumulating delta from move events is invalidated.  Make sure next
1567                 // mouse move event starts over from an absolute baseline.
1568                 forceNextMouseMoveAbsolute = TRUE;
1569             }
1570         }
1571     }
1573     // Returns TRUE if the event was handled and caller should do nothing more
1574     // with it.  Returns FALSE if the caller should process it as normal and
1575     // then call -didSendEvent:.
1576     - (BOOL) handleEvent:(NSEvent*)anEvent
1577     {
1578         BOOL ret = FALSE;
1579         NSEventType type = [anEvent type];
1581         if (type == NSFlagsChanged)
1582             self.lastFlagsChanged = anEvent;
1583         else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1584                  type == NSRightMouseDragged || type == NSOtherMouseDragged)
1585         {
1586             [self handleMouseMove:anEvent];
1587             ret = mouseCaptureWindow != nil;
1588         }
1589         else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1590                  type == NSRightMouseDown || type == NSRightMouseUp ||
1591                  type == NSOtherMouseDown || type == NSOtherMouseUp)
1592         {
1593             [self handleMouseButton:anEvent];
1594             ret = mouseCaptureWindow != nil;
1595         }
1596         else if (type == NSScrollWheel)
1597         {
1598             [self handleScrollWheel:anEvent];
1599             ret = mouseCaptureWindow != nil;
1600         }
1602         return ret;
1603     }
1605     - (void) didSendEvent:(NSEvent*)anEvent
1606     {
1607         NSEventType type = [anEvent type];
1609         if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1610         {
1611             NSUInteger modifiers = [anEvent modifierFlags];
1612             if ((modifiers & NSCommandKeyMask) &&
1613                 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
1614             {
1615                 // Command-Tab and Command-Shift-Tab would normally be intercepted
1616                 // by the system to switch applications.  If we're seeing it, it's
1617                 // presumably because we've captured the displays, preventing
1618                 // normal application switching.  Do it manually.
1619                 [self handleCommandTab];
1620             }
1621         }
1622     }
1624     - (void) setupObservations
1625     {
1626         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1627         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1629         [nc addObserverForName:NSWindowDidBecomeKeyNotification
1630                         object:nil
1631                          queue:nil
1632                     usingBlock:^(NSNotification *note){
1633             NSWindow* window = [note object];
1634             [keyWindows removeObjectIdenticalTo:window];
1635             [keyWindows insertObject:window atIndex:0];
1636         }];
1638         [nc addObserverForName:NSWindowWillCloseNotification
1639                         object:nil
1640                          queue:[NSOperationQueue mainQueue]
1641                     usingBlock:^(NSNotification *note){
1642             NSWindow* window = [note object];
1643             [keyWindows removeObjectIdenticalTo:window];
1644             if (window == lastTargetWindow)
1645                 lastTargetWindow = nil;
1646             if (window == self.mouseCaptureWindow)
1647                 self.mouseCaptureWindow = nil;
1648         }];
1650         [nc addObserver:self
1651                selector:@selector(keyboardSelectionDidChange)
1652                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
1653                  object:nil];
1655         /* The above notification isn't sent unless the NSTextInputContext
1656            class has initialized itself.  Poke it. */
1657         [NSTextInputContext self];
1659         [wsnc addObserver:self
1660                  selector:@selector(adjustWindowLevels)
1661                      name:NSWorkspaceActiveSpaceDidChangeNotification
1662                    object:nil];
1663     }
1665     - (BOOL) inputSourceIsInputMethod
1666     {
1667         if (!inputSourceIsInputMethodValid)
1668         {
1669             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
1670             if (inputSource)
1671             {
1672                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
1673                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
1674                 CFRelease(inputSource);
1675             }
1676             else
1677                 inputSourceIsInputMethod = FALSE;
1678             inputSourceIsInputMethodValid = TRUE;
1679         }
1681         return inputSourceIsInputMethod;
1682     }
1685     /*
1686      * ---------- NSApplicationDelegate methods ----------
1687      */
1688     - (void)applicationDidBecomeActive:(NSNotification *)notification
1689     {
1690         [self activateCursorClipping];
1692         [self adjustWindowLevels:YES];
1694         if (![self frontWineWindow])
1695         {
1696             for (WineWindow* window in [NSApp windows])
1697             {
1698                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
1699                 {
1700                     [window deminiaturize:self];
1701                     break;
1702                 }
1703             }
1704         }
1706         // If a Wine process terminates abruptly while it has the display captured
1707         // and switched to a different resolution, Mac OS X will uncapture the
1708         // displays and switch their resolutions back.  However, the other Wine
1709         // processes won't have their notion of the desktop rect changed back.
1710         // This can lead them to refuse to draw or acknowledge clicks in certain
1711         // portions of their windows.
1712         //
1713         // To solve this, we synthesize a displays-changed event whenever we're
1714         // activated.  This will provoke a re-synchronization of Wine's notion of
1715         // the desktop rect with the actual state.
1716         [self sendDisplaysChanged:TRUE];
1718         // The cursor probably moved while we were inactive.  Accumulated mouse
1719         // movement deltas are invalidated.  Make sure the next mouse move event
1720         // starts over from an absolute baseline.
1721         forceNextMouseMoveAbsolute = TRUE;
1722     }
1724     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
1725     {
1726         primaryScreenHeightValid = FALSE;
1727         [self sendDisplaysChanged:FALSE];
1728         [self adjustWindowLevels];
1730         // When the display configuration changes, the cursor position may jump.
1731         // Accumulated mouse movement deltas are invalidated.  Make sure the next
1732         // mouse move event starts over from an absolute baseline.
1733         forceNextMouseMoveAbsolute = TRUE;
1734     }
1736     - (void)applicationDidResignActive:(NSNotification *)notification
1737     {
1738         macdrv_event* event;
1739         WineEventQueue* queue;
1741         [self invalidateGotFocusEvents];
1743         event = macdrv_create_event(APP_DEACTIVATED, nil);
1745         [eventQueuesLock lock];
1746         for (queue in eventQueues)
1747             [queue postEvent:event];
1748         [eventQueuesLock unlock];
1750         macdrv_release_event(event);
1751     }
1753     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
1754     {
1755         NSApplicationTerminateReply ret = NSTerminateNow;
1756         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
1757         NSAppleEventDescriptor* desc = [m currentAppleEvent];
1758         macdrv_event* event;
1759         WineEventQueue* queue;
1761         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
1762         event->deliver = 1;
1763         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
1764         {
1765             case kAELogOut:
1766             case kAEReallyLogOut:
1767                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
1768                 break;
1769             case kAEShowRestartDialog:
1770                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
1771                 break;
1772             case kAEShowShutdownDialog:
1773                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
1774                 break;
1775             default:
1776                 event->app_quit_requested.reason = QUIT_REASON_NONE;
1777                 break;
1778         }
1780         [eventQueuesLock lock];
1782         if ([eventQueues count])
1783         {
1784             for (queue in eventQueues)
1785                 [queue postEvent:event];
1786             ret = NSTerminateLater;
1787         }
1789         [eventQueuesLock unlock];
1791         macdrv_release_event(event);
1793         return ret;
1794     }
1796     - (void)applicationWillResignActive:(NSNotification *)notification
1797     {
1798         [self deactivateCursorClipping];
1800         [self adjustWindowLevels:NO];
1801     }
1803 /***********************************************************************
1804  *              PerformRequest
1806  * Run-loop-source perform callback.  Pull request blocks from the
1807  * array of queued requests and invoke them.
1808  */
1809 static void PerformRequest(void *info)
1811     WineApplicationController* controller = [WineApplicationController sharedController];
1813     for (;;)
1814     {
1815         __block dispatch_block_t block;
1817         dispatch_sync(controller->requestsManipQueue, ^{
1818             if ([controller->requests count])
1819             {
1820                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
1821                 [controller->requests removeObjectAtIndex:0];
1822             }
1823             else
1824                 block = nil;
1825         });
1827         if (!block)
1828             break;
1830         block();
1831         [block release];
1832     }
1835 /***********************************************************************
1836  *              OnMainThreadAsync
1838  * Run a block on the main thread asynchronously.
1839  */
1840 void OnMainThreadAsync(dispatch_block_t block)
1842     WineApplicationController* controller = [WineApplicationController sharedController];
1844     block = [block copy];
1845     dispatch_sync(controller->requestsManipQueue, ^{
1846         [controller->requests addObject:block];
1847     });
1848     [block release];
1849     CFRunLoopSourceSignal(controller->requestSource);
1850     CFRunLoopWakeUp(CFRunLoopGetMain());
1853 @end
1855 /***********************************************************************
1856  *              LogError
1857  */
1858 void LogError(const char* func, NSString* format, ...)
1860     va_list args;
1861     va_start(args, format);
1862     LogErrorv(func, format, args);
1863     va_end(args);
1866 /***********************************************************************
1867  *              LogErrorv
1868  */
1869 void LogErrorv(const char* func, NSString* format, va_list args)
1871     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1873     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
1874     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
1875     [message release];
1877     [pool release];
1880 /***********************************************************************
1881  *              macdrv_window_rejected_focus
1883  * Pass focus to the next window that hasn't already rejected this same
1884  * WINDOW_GOT_FOCUS event.
1885  */
1886 void macdrv_window_rejected_focus(const macdrv_event *event)
1888     OnMainThread(^{
1889         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
1890     });
1893 /***********************************************************************
1894  *              macdrv_get_keyboard_layout
1896  * Returns the keyboard layout uchr data.
1897  */
1898 CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso)
1900     __block CFDataRef result = NULL;
1902     OnMainThread(^{
1903         TISInputSourceRef inputSource;
1905         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
1906         if (inputSource)
1907         {
1908             CFDataRef uchr = TISGetInputSourceProperty(inputSource,
1909                                 kTISPropertyUnicodeKeyLayoutData);
1910             result = CFDataCreateCopy(NULL, uchr);
1911             CFRelease(inputSource);
1913             *keyboard_type = [WineApplicationController sharedController].keyboardType;
1914             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
1915         }
1916     });
1918     return result;
1921 /***********************************************************************
1922  *              macdrv_beep
1924  * Play the beep sound configured by the user in System Preferences.
1925  */
1926 void macdrv_beep(void)
1928     OnMainThreadAsync(^{
1929         NSBeep();
1930     });
1933 /***********************************************************************
1934  *              macdrv_set_display_mode
1935  */
1936 int macdrv_set_display_mode(const struct macdrv_display* display,
1937                             CGDisplayModeRef display_mode)
1939     __block int ret;
1941     OnMainThread(^{
1942         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
1943     });
1945     return ret;
1948 /***********************************************************************
1949  *              macdrv_set_cursor
1951  * Set the cursor.
1953  * If name is non-NULL, it is a selector for a class method on NSCursor
1954  * identifying the cursor to set.  In that case, frames is ignored.  If
1955  * name is NULL, then frames is used.
1957  * frames is an array of dictionaries.  Each dictionary is a frame of
1958  * an animated cursor.  Under the key "image" is a CGImage for the
1959  * frame.  Under the key "duration" is a CFNumber time interval, in
1960  * seconds, for how long that frame is presented before proceeding to
1961  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
1962  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
1963  * This is the hot spot, measured in pixels down and to the right of the
1964  * top-left corner of the image.
1966  * If the array has exactly 1 element, the cursor is static, not
1967  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
1968  */
1969 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
1971     SEL sel;
1973     sel = NSSelectorFromString((NSString*)name);
1974     if (sel)
1975     {
1976         OnMainThreadAsync(^{
1977             WineApplicationController* controller = [WineApplicationController sharedController];
1978             NSCursor* cursor = [NSCursor performSelector:sel];
1979             [controller setCursorWithFrames:nil];
1980             [cursor set];
1981             [controller unhideCursor];
1982         });
1983     }
1984     else
1985     {
1986         NSArray* nsframes = (NSArray*)frames;
1987         if ([nsframes count])
1988         {
1989             OnMainThreadAsync(^{
1990                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
1991             });
1992         }
1993         else
1994         {
1995             OnMainThreadAsync(^{
1996                 WineApplicationController* controller = [WineApplicationController sharedController];
1997                 [controller setCursorWithFrames:nil];
1998                 [controller hideCursor];
1999             });
2000         }
2001     }
2004 /***********************************************************************
2005  *              macdrv_get_cursor_position
2007  * Obtains the current cursor position.  Returns zero on failure,
2008  * non-zero on success.
2009  */
2010 int macdrv_get_cursor_position(CGPoint *pos)
2012     OnMainThread(^{
2013         NSPoint location = [NSEvent mouseLocation];
2014         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2015         *pos = NSPointToCGPoint(location);
2016     });
2018     return TRUE;
2021 /***********************************************************************
2022  *              macdrv_set_cursor_position
2024  * Sets the cursor position without generating events.  Returns zero on
2025  * failure, non-zero on success.
2026  */
2027 int macdrv_set_cursor_position(CGPoint pos)
2029     __block int ret;
2031     OnMainThread(^{
2032         ret = [[WineApplicationController sharedController] setCursorPosition:pos];
2033     });
2035     return ret;
2038 /***********************************************************************
2039  *              macdrv_clip_cursor
2041  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2042  * to or larger than the whole desktop region, the cursor is unclipped.
2043  * Returns zero on failure, non-zero on success.
2044  */
2045 int macdrv_clip_cursor(CGRect rect)
2047     __block int ret;
2049     OnMainThread(^{
2050         WineApplicationController* controller = [WineApplicationController sharedController];
2051         BOOL clipping = FALSE;
2053         if (!CGRectIsInfinite(rect))
2054         {
2055             NSRect nsrect = NSRectFromCGRect(rect);
2056             NSScreen* screen;
2058             /* Convert the rectangle from top-down coords to bottom-up. */
2059             [controller flipRect:&nsrect];
2061             clipping = FALSE;
2062             for (screen in [NSScreen screens])
2063             {
2064                 if (!NSContainsRect(nsrect, [screen frame]))
2065                 {
2066                     clipping = TRUE;
2067                     break;
2068                 }
2069             }
2070         }
2072         if (clipping)
2073             ret = [controller startClippingCursor:rect];
2074         else
2075             ret = [controller stopClippingCursor];
2076     });
2078     return ret;
2081 /***********************************************************************
2082  *              macdrv_set_application_icon
2084  * Set the application icon.  The images array contains CGImages.  If
2085  * there are more than one, then they represent different sizes or
2086  * color depths from the icon resource.  If images is NULL or empty,
2087  * restores the default application image.
2088  */
2089 void macdrv_set_application_icon(CFArrayRef images)
2091     NSArray* imageArray = (NSArray*)images;
2093     OnMainThreadAsync(^{
2094         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2095     });
2098 /***********************************************************************
2099  *              macdrv_quit_reply
2100  */
2101 void macdrv_quit_reply(int reply)
2103     OnMainThread(^{
2104         [NSApp replyToApplicationShouldTerminate:reply];
2105     });
2108 /***********************************************************************
2109  *              macdrv_using_input_method
2110  */
2111 int macdrv_using_input_method(void)
2113     __block BOOL ret;
2115     OnMainThread(^{
2116         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2117     });
2119     return ret;
2122 /***********************************************************************
2123  *              macdrv_set_mouse_capture_window
2124  */
2125 void macdrv_set_mouse_capture_window(macdrv_window window)
2127     WineWindow* w = (WineWindow*)window;
2129     OnMainThread(^{
2130         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2131     });