Release 1.5.29.
[wine] / dlls / winemac.drv / cocoa_app.m
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  */
20
21 #import <Carbon/Carbon.h>
22 #include <dlfcn.h>
23
24 #import "cocoa_app.h"
25 #import "cocoa_event.h"
26 #import "cocoa_window.h"
27
28
29 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
30
31
32 int macdrv_err_on;
33
34
35 @implementation WineApplication
36
37 @synthesize wineController;
38
39     - (void) sendEvent:(NSEvent*)anEvent
40     {
41         if (![wineController handleEvent:anEvent])
42         {
43             [super sendEvent:anEvent];
44             [wineController didSendEvent:anEvent];
45         }
46     }
47
48     - (void) setWineController:(WineApplicationController*)newController
49     {
50         wineController = newController;
51         [self setDelegate:wineController];
52     }
53
54 @end
55
56
57 @interface WarpRecord : NSObject
58 {
59     CGEventTimestamp timeBefore, timeAfter;
60     CGPoint from, to;
61 }
62
63 @property (nonatomic) CGEventTimestamp timeBefore;
64 @property (nonatomic) CGEventTimestamp timeAfter;
65 @property (nonatomic) CGPoint from;
66 @property (nonatomic) CGPoint to;
67
68 @end
69
70
71 @implementation WarpRecord
72
73 @synthesize timeBefore, timeAfter, from, to;
74
75 @end;
76
77
78 @interface WineApplicationController ()
79
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
86     - (void) setupObservations;
87     - (void) applicationDidBecomeActive:(NSNotification *)notification;
88
89     static void PerformRequest(void *info);
90
91 @end
92
93
94 @implementation WineApplicationController
95
96     @synthesize keyboardType, lastFlagsChanged;
97     @synthesize orderedWineWindows, applicationIcon;
98     @synthesize cursorFrames, cursorTimer;
99
100     + (void) initialize
101     {
102         if (self == [WineApplicationController class])
103         {
104             NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
105                                       @"", @"NSQuotedKeystrokeBinding",
106                                       @"", @"NSRepeatCountBinding",
107                                       [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
108                                       nil];
109             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
110         }
111     }
112
113     + (WineApplicationController*) sharedController
114     {
115         static WineApplicationController* sharedController;
116         static dispatch_once_t once;
117
118         dispatch_once(&once, ^{
119             sharedController = [[self alloc] init];
120         });
121
122         return sharedController;
123     }
124
125     - (id) init
126     {
127         self = [super init];
128         if (self != nil)
129         {
130             CFRunLoopSourceContext context = { 0 };
131             context.perform = PerformRequest;
132             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
133             if (!requestSource)
134             {
135                 [self release];
136                 return nil;
137             }
138             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
139             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
140
141             requests =  [[NSMutableArray alloc] init];
142             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
143
144             eventQueues = [[NSMutableArray alloc] init];
145             eventQueuesLock = [[NSLock alloc] init];
146
147             keyWindows = [[NSMutableArray alloc] init];
148             orderedWineWindows = [[NSMutableArray alloc] init];
149
150             originalDisplayModes = [[NSMutableDictionary alloc] init];
151
152             warpRecords = [[NSMutableArray alloc] init];
153
154             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
155                 !keyWindows || !orderedWineWindows || !originalDisplayModes || !warpRecords)
156             {
157                 [self release];
158                 return nil;
159             }
160
161             [self setupObservations];
162
163             keyboardType = LMGetKbdType();
164
165             if ([NSApp isActive])
166                 [self applicationDidBecomeActive:nil];
167         }
168         return self;
169     }
170
171     - (void) dealloc
172     {
173         [applicationIcon release];
174         [warpRecords release];
175         [cursorTimer release];
176         [cursorFrames release];
177         [originalDisplayModes release];
178         [orderedWineWindows release];
179         [keyWindows release];
180         [eventQueues release];
181         [eventQueuesLock release];
182         if (requestsManipQueue) dispatch_release(requestsManipQueue);
183         [requests release];
184         if (requestSource)
185         {
186             CFRunLoopSourceInvalidate(requestSource);
187             CFRelease(requestSource);
188         }
189         [super dealloc];
190     }
191
192     - (void) transformProcessToForeground
193     {
194         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
195         {
196             NSMenu* mainMenu;
197             NSMenu* submenu;
198             NSString* bundleName;
199             NSString* title;
200             NSMenuItem* item;
201
202             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
203             [NSApp activateIgnoringOtherApps:YES];
204
205             mainMenu = [[[NSMenu alloc] init] autorelease];
206
207             submenu = [[[NSMenu alloc] initWithTitle:@"Wine"] autorelease];
208             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
209             if ([bundleName length])
210                 title = [NSString stringWithFormat:@"Quit %@", bundleName];
211             else
212                 title = @"Quit";
213             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
214             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
215             item = [[[NSMenuItem alloc] init] autorelease];
216             [item setTitle:@"Wine"];
217             [item setSubmenu:submenu];
218             [mainMenu addItem:item];
219
220             submenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
221             [submenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
222             [submenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
223             [submenu addItem:[NSMenuItem separatorItem]];
224             [submenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
225             item = [[[NSMenuItem alloc] init] autorelease];
226             [item setTitle:@"Window"];
227             [item setSubmenu:submenu];
228             [mainMenu addItem:item];
229
230             [NSApp setMainMenu:mainMenu];
231             [NSApp setWindowsMenu:submenu];
232
233             [NSApp setApplicationIconImage:self.applicationIcon];
234         }
235     }
236
237     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
238     {
239         PerformRequest(NULL);
240
241         do
242         {
243             if (processEvents)
244             {
245                 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
246                 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
247                                                     untilDate:timeout
248                                                        inMode:NSDefaultRunLoopMode
249                                                       dequeue:YES];
250                 if (event)
251                     [NSApp sendEvent:event];
252                 [pool release];
253             }
254             else
255                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
256         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
257
258         return *done;
259     }
260
261     - (BOOL) registerEventQueue:(WineEventQueue*)queue
262     {
263         [eventQueuesLock lock];
264         [eventQueues addObject:queue];
265         [eventQueuesLock unlock];
266         return TRUE;
267     }
268
269     - (void) unregisterEventQueue:(WineEventQueue*)queue
270     {
271         [eventQueuesLock lock];
272         [eventQueues removeObjectIdenticalTo:queue];
273         [eventQueuesLock unlock];
274     }
275
276     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
277     {
278         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
279     }
280
281     - (double) ticksForEventTime:(NSTimeInterval)eventTime
282     {
283         return (eventTime + eventTimeAdjustment) * 1000;
284     }
285
286     /* Invalidate old focus offers across all queues. */
287     - (void) invalidateGotFocusEvents
288     {
289         WineEventQueue* queue;
290
291         windowFocusSerial++;
292
293         [eventQueuesLock lock];
294         for (queue in eventQueues)
295         {
296             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
297                                    forWindow:nil];
298         }
299         [eventQueuesLock unlock];
300     }
301
302     - (void) windowGotFocus:(WineWindow*)window
303     {
304         macdrv_event* event;
305
306         [self invalidateGotFocusEvents];
307
308         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
309         event->window_got_focus.serial = windowFocusSerial;
310         if (triedWindows)
311             event->window_got_focus.tried_windows = [triedWindows retain];
312         else
313             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
314         [window.queue postEvent:event];
315         macdrv_release_event(event);
316     }
317
318     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
319     {
320         if (event->window_got_focus.serial == windowFocusSerial)
321         {
322             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
323             [triedWindows addObject:(WineWindow*)event->window];
324             for (NSWindow* window in [keyWindows arrayByAddingObjectsFromArray:[self orderedWineWindows]])
325             {
326                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
327                 {
328                     [window makeKeyWindow];
329                     break;
330                 }
331             }
332             triedWindows = nil;
333         }
334     }
335
336     - (void) keyboardSelectionDidChange
337     {
338         TISInputSourceRef inputSource;
339
340         inputSourceIsInputMethodValid = FALSE;
341
342         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
343         if (inputSource)
344         {
345             CFDataRef uchr;
346             uchr = TISGetInputSourceProperty(inputSource,
347                     kTISPropertyUnicodeKeyLayoutData);
348             if (uchr)
349             {
350                 macdrv_event* event;
351                 WineEventQueue* queue;
352
353                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
354                 event->keyboard_changed.keyboard_type = self.keyboardType;
355                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
356                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
357
358                 if (event->keyboard_changed.uchr)
359                 {
360                     [eventQueuesLock lock];
361
362                     for (queue in eventQueues)
363                         [queue postEvent:event];
364
365                     [eventQueuesLock unlock];
366                 }
367
368                 macdrv_release_event(event);
369             }
370
371             CFRelease(inputSource);
372         }
373     }
374
375     - (CGFloat) primaryScreenHeight
376     {
377         if (!primaryScreenHeightValid)
378         {
379             NSArray* screens = [NSScreen screens];
380             if ([screens count])
381             {
382                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
383                 primaryScreenHeightValid = TRUE;
384             }
385             else
386                 return 1280; /* arbitrary value */
387         }
388
389         return primaryScreenHeight;
390     }
391
392     - (NSPoint) flippedMouseLocation:(NSPoint)point
393     {
394         /* This relies on the fact that Cocoa's mouse location points are
395            actually off by one (precisely because they were flipped from
396            Quartz screen coordinates using this same technique). */
397         point.y = [self primaryScreenHeight] - point.y;
398         return point;
399     }
400
401     - (void) flipRect:(NSRect*)rect
402     {
403         // We don't use -primaryScreenHeight here so there's no chance of having
404         // out-of-date cached info.  This method is called infrequently enough
405         // that getting the screen height each time is not prohibitively expensive.
406         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
407     }
408
409     - (void) wineWindow:(WineWindow*)window
410                 ordered:(NSWindowOrderingMode)order
411              relativeTo:(WineWindow*)otherWindow
412     {
413         NSUInteger index;
414
415         switch (order)
416         {
417             case NSWindowAbove:
418                 [window retain];
419                 [orderedWineWindows removeObjectIdenticalTo:window];
420                 if (otherWindow)
421                 {
422                     index = [orderedWineWindows indexOfObjectIdenticalTo:otherWindow];
423                     if (index == NSNotFound)
424                         index = 0;
425                 }
426                 else
427                 {
428                     index = 0;
429                     for (otherWindow in orderedWineWindows)
430                     {
431                         if ([otherWindow levelWhenActive] <= [window levelWhenActive])
432                             break;
433                         index++;
434                     }
435                 }
436                 [orderedWineWindows insertObject:window atIndex:index];
437                 [window release];
438                 break;
439             case NSWindowBelow:
440                 [window retain];
441                 [orderedWineWindows removeObjectIdenticalTo:window];
442                 if (otherWindow)
443                 {
444                     index = [orderedWineWindows indexOfObjectIdenticalTo:otherWindow];
445                     if (index == NSNotFound)
446                         index = [orderedWineWindows count];
447                 }
448                 else
449                 {
450                     index = 0;
451                     for (otherWindow in orderedWineWindows)
452                     {
453                         if ([otherWindow levelWhenActive] < [window levelWhenActive])
454                             break;
455                         index++;
456                     }
457                 }
458                 [orderedWineWindows insertObject:window atIndex:index];
459                 [window release];
460                 break;
461             case NSWindowOut:
462             default:
463                 break;
464         }
465     }
466
467     - (void) sendDisplaysChanged:(BOOL)activating
468     {
469         macdrv_event* event;
470         WineEventQueue* queue;
471
472         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
473         event->displays_changed.activating = activating;
474
475         [eventQueuesLock lock];
476
477         // If we're activating, then we just need one of our threads to get the
478         // event, so it can send it directly to the desktop window.  Otherwise,
479         // we need all of the threads to get it because we don't know which owns
480         // the desktop window and only that one will do anything with it.
481         if (activating) event->deliver = 1;
482
483         for (queue in eventQueues)
484             [queue postEvent:event];
485         [eventQueuesLock unlock];
486
487         macdrv_release_event(event);
488     }
489
490     // We can compare two modes directly using CFEqual, but that may require that
491     // they are identical to a level that we don't need.  In particular, when the
492     // OS switches between the integrated and discrete GPUs, the set of display
493     // modes can change in subtle ways.  We're interested in whether two modes
494     // match in their most salient features, even if they aren't identical.
495     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
496     {
497         NSString *encoding1, *encoding2;
498         uint32_t ioflags1, ioflags2, different;
499         double refresh1, refresh2;
500
501         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
502         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
503
504         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
505         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
506         if (![encoding1 isEqualToString:encoding2]) return FALSE;
507
508         ioflags1 = CGDisplayModeGetIOFlags(mode1);
509         ioflags2 = CGDisplayModeGetIOFlags(mode2);
510         different = ioflags1 ^ ioflags2;
511         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
512                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
513             return FALSE;
514
515         refresh1 = CGDisplayModeGetRefreshRate(mode1);
516         if (refresh1 == 0) refresh1 = 60;
517         refresh2 = CGDisplayModeGetRefreshRate(mode2);
518         if (refresh2 == 0) refresh2 = 60;
519         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
520
521         return TRUE;
522     }
523
524     - (CGDisplayModeRef)modeMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
525     {
526         CGDisplayModeRef ret = NULL;
527         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, NULL) autorelease];
528         for (id candidateModeObject in modes)
529         {
530             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
531             if ([self mode:candidateMode matchesMode:mode])
532             {
533                 ret = candidateMode;
534                 break;
535             }
536         }
537         return ret;
538     }
539
540     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
541     {
542         BOOL ret = FALSE;
543         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
544         CGDisplayModeRef currentMode, originalMode;
545
546         currentMode = CGDisplayCopyDisplayMode(displayID);
547         if (!currentMode) // Invalid display ID
548             return FALSE;
549
550         if ([self mode:mode matchesMode:currentMode]) // Already there!
551         {
552             CGDisplayModeRelease(currentMode);
553             return TRUE;
554         }
555
556         mode = [self modeMatchingMode:mode forDisplay:displayID];
557         if (!mode)
558         {
559             CGDisplayModeRelease(currentMode);
560             return FALSE;
561         }
562
563         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
564         if (!originalMode)
565             originalMode = currentMode;
566
567         if ([self mode:mode matchesMode:originalMode])
568         {
569             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
570             {
571                 CGRestorePermanentDisplayConfiguration();
572                 CGReleaseAllDisplays();
573                 [originalDisplayModes removeAllObjects];
574                 ret = TRUE;
575             }
576             else // ... otherwise, try to restore just the one display
577             {
578                 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
579                 {
580                     [originalDisplayModes removeObjectForKey:displayIDKey];
581                     ret = TRUE;
582                 }
583             }
584         }
585         else
586         {
587             if ([originalDisplayModes count] || CGCaptureAllDisplays() == CGDisplayNoErr)
588             {
589                 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
590                 {
591                     [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
592                     ret = TRUE;
593                 }
594                 else if (![originalDisplayModes count])
595                 {
596                     CGRestorePermanentDisplayConfiguration();
597                     CGReleaseAllDisplays();
598                 }
599             }
600         }
601
602         CGDisplayModeRelease(currentMode);
603
604         if (ret)
605         {
606             [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
607                 [(WineWindow*)obj adjustWindowLevel];
608             }];
609         }
610
611         return ret;
612     }
613
614     - (BOOL) areDisplaysCaptured
615     {
616         return ([originalDisplayModes count] > 0);
617     }
618
619     - (void) hideCursor
620     {
621         if (!cursorHidden)
622         {
623             [NSCursor hide];
624             cursorHidden = TRUE;
625         }
626     }
627
628     - (void) unhideCursor
629     {
630         if (cursorHidden)
631         {
632             [NSCursor unhide];
633             cursorHidden = FALSE;
634         }
635     }
636
637     - (void) setCursor
638     {
639         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
640         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
641         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
642         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
643         CGPoint hotSpot;
644         NSCursor* cursor;
645
646         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
647             hotSpot = CGPointZero;
648         cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)];
649         [image release];
650         [cursor set];
651         [self unhideCursor];
652         [cursor release];
653     }
654
655     - (void) nextCursorFrame:(NSTimer*)theTimer
656     {
657         NSDictionary* frame;
658         NSTimeInterval duration;
659         NSDate* date;
660
661         cursorFrame++;
662         if (cursorFrame >= [cursorFrames count])
663             cursorFrame = 0;
664         [self setCursor];
665
666         frame = [cursorFrames objectAtIndex:cursorFrame];
667         duration = [[frame objectForKey:@"duration"] doubleValue];
668         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
669         [cursorTimer setFireDate:date];
670     }
671
672     - (void) setCursorWithFrames:(NSArray*)frames
673     {
674         if (self.cursorFrames == frames)
675             return;
676
677         self.cursorFrames = frames;
678         cursorFrame = 0;
679         [cursorTimer invalidate];
680         self.cursorTimer = nil;
681
682         if ([frames count])
683         {
684             if ([frames count] > 1)
685             {
686                 NSDictionary* frame = [frames objectAtIndex:0];
687                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
688                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
689                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
690                                                              interval:1000000
691                                                                target:self
692                                                              selector:@selector(nextCursorFrame:)
693                                                              userInfo:nil
694                                                               repeats:YES] autorelease];
695                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
696             }
697
698             [self setCursor];
699         }
700     }
701
702     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
703     {
704         NSImage* nsimage = nil;
705
706         if ([images count])
707         {
708             NSSize bestSize = NSZeroSize;
709             id image;
710
711             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
712
713             for (image in images)
714             {
715                 CGImageRef cgimage = (CGImageRef)image;
716                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
717                 if (imageRep)
718                 {
719                     NSSize size = [imageRep size];
720
721                     [nsimage addRepresentation:imageRep];
722                     [imageRep release];
723
724                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
725                         bestSize = size;
726                 }
727             }
728
729             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
730                 [nsimage setSize:bestSize];
731             else
732                 nsimage = nil;
733         }
734
735         self.applicationIcon = nsimage;
736         [NSApp setApplicationIconImage:nsimage];
737     }
738
739     - (void) handleCommandTab
740     {
741         if ([NSApp isActive])
742         {
743             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
744             NSRunningApplication* app;
745             NSRunningApplication* otherValidApp = nil;
746
747             if ([originalDisplayModes count])
748             {
749                 CGRestorePermanentDisplayConfiguration();
750                 CGReleaseAllDisplays();
751                 [originalDisplayModes removeAllObjects];
752             }
753
754             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
755             {
756                 if (![app isEqual:thisApp] && !app.terminated &&
757                     app.activationPolicy == NSApplicationActivationPolicyRegular)
758                 {
759                     if (!app.hidden)
760                     {
761                         // There's another visible app.  Just hide ourselves and let
762                         // the system activate the other app.
763                         [NSApp hide:self];
764                         return;
765                     }
766
767                     if (!otherValidApp)
768                         otherValidApp = app;
769                 }
770             }
771
772             // Didn't find a visible GUI app.  Try the Finder or, if that's not
773             // running, the first hidden GUI app.  If even that doesn't work, we
774             // just fail to switch and remain the active app.
775             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
776             if (!app) app = otherValidApp;
777             [app unhide];
778             [app activateWithOptions:0];
779         }
780     }
781
782     /*
783      * ---------- Cursor clipping methods ----------
784      *
785      * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
786      * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
787      * equivalent: CGAssociateMouseAndMouseCursorPosition(false).  For the
788      * general case, we leverage that.  We disassociate mouse movements from
789      * the cursor position and then move the cursor manually, keeping it within
790      * the clipping rectangle.
791      *
792      * Moving the cursor manually isn't enough.  We need to modify the event
793      * stream so that the events have the new location, too.  We need to do
794      * this at a point before the events enter Cocoa, so that Cocoa will assign
795      * the correct window to the event.  So, we install a Quartz event tap to
796      * do that.
797      *
798      * Also, there's a complication when we move the cursor.  We use
799      * CGWarpMouseCursorPosition().  That doesn't generate mouse movement
800      * events, but the change of cursor position is incorporated into the
801      * deltas of the next mouse move event.  When the mouse is disassociated
802      * from the cursor position, we need the deltas to only reflect actual
803      * device movement, not programmatic changes.  So, the event tap cancels
804      * out the change caused by our calls to CGWarpMouseCursorPosition().
805      */
806     - (void) clipCursorLocation:(CGPoint*)location
807     {
808         if (location->x < CGRectGetMinX(cursorClipRect))
809             location->x = CGRectGetMinX(cursorClipRect);
810         if (location->y < CGRectGetMinY(cursorClipRect))
811             location->y = CGRectGetMinY(cursorClipRect);
812         if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
813             location->x = CGRectGetMaxX(cursorClipRect) - 1;
814         if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
815             location->y = CGRectGetMaxY(cursorClipRect) - 1;
816     }
817
818     - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
819     {
820         CGPoint oldLocation;
821
822         if (currentLocation)
823             oldLocation = *currentLocation;
824         else
825             oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
826
827         if (!CGPointEqualToPoint(oldLocation, *newLocation))
828         {
829             WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
830             CGError err;
831
832             warpRecord.from = oldLocation;
833             warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
834
835             /* Actually move the cursor. */
836             err = CGWarpMouseCursorPosition(*newLocation);
837             if (err != kCGErrorSuccess)
838                 return FALSE;
839
840             warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
841             *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
842
843             if (!CGPointEqualToPoint(oldLocation, *newLocation))
844             {
845                 warpRecord.to = *newLocation;
846                 [warpRecords addObject:warpRecord];
847             }
848         }
849
850         return TRUE;
851     }
852
853     - (BOOL) isMouseMoveEventType:(CGEventType)type
854     {
855         switch(type)
856         {
857         case kCGEventMouseMoved:
858         case kCGEventLeftMouseDragged:
859         case kCGEventRightMouseDragged:
860         case kCGEventOtherMouseDragged:
861             return TRUE;
862         }
863
864         return FALSE;
865     }
866
867     - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
868     {
869         int warpsFinished = 0;
870         for (WarpRecord* warpRecord in warpRecords)
871         {
872             if (warpRecord.timeAfter < eventTime ||
873                 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
874                 warpsFinished++;
875             else
876                 break;
877         }
878
879         return warpsFinished;
880     }
881
882     - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
883                                 type:(CGEventType)type
884                                event:(CGEventRef)event
885     {
886         CGEventTimestamp eventTime;
887         CGPoint eventLocation, cursorLocation;
888
889         if (type == kCGEventTapDisabledByUserInput)
890             return event;
891         if (type == kCGEventTapDisabledByTimeout)
892         {
893             CGEventTapEnable(cursorClippingEventTap, TRUE);
894             return event;
895         }
896
897         if (!clippingCursor)
898             return event;
899
900         eventTime = CGEventGetTimestamp(event);
901         lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
902
903         eventLocation = CGEventGetLocation(event);
904
905         cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
906
907         if ([self isMouseMoveEventType:type])
908         {
909             double deltaX, deltaY;
910             int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
911             int i;
912
913             deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
914             deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
915
916             for (i = 0; i < warpsFinished; i++)
917             {
918                 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
919                 deltaX -= warpRecord.to.x - warpRecord.from.x;
920                 deltaY -= warpRecord.to.y - warpRecord.from.y;
921                 [warpRecords removeObjectAtIndex:0];
922             }
923
924             if (warpsFinished)
925             {
926                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
927                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
928             }
929
930             synthesizedLocation.x += deltaX;
931             synthesizedLocation.y += deltaY;
932         }
933
934         // If the event is destined for another process, don't clip it.  This may
935         // happen if the user activates Exposé or Mission Control.  In that case,
936         // our app does not resign active status, so clipping is still in effect,
937         // but the cursor should not actually be clipped.
938         //
939         // In addition, the fact that mouse moves may have been delivered to a
940         // different process means we have to treat the next one we receive as
941         // absolute rather than relative.
942         if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
943             [self clipCursorLocation:&synthesizedLocation];
944         else
945             lastSetCursorPositionTime = lastEventTapEventTime;
946
947         [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
948         if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
949             CGEventSetLocation(event, synthesizedLocation);
950
951         return event;
952     }
953
954     CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
955                                        CGEventRef event, void *refcon)
956     {
957         WineApplicationController* controller = refcon;
958         return [controller eventTapWithProxy:proxy type:type event:event];
959     }
960
961     - (BOOL) installEventTap
962     {
963         ProcessSerialNumber psn;
964         OSErr err;
965         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
966                            CGEventMaskBit(kCGEventLeftMouseUp)          |
967                            CGEventMaskBit(kCGEventRightMouseDown)       |
968                            CGEventMaskBit(kCGEventRightMouseUp)         |
969                            CGEventMaskBit(kCGEventMouseMoved)           |
970                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
971                            CGEventMaskBit(kCGEventRightMouseDragged)    |
972                            CGEventMaskBit(kCGEventOtherMouseDown)       |
973                            CGEventMaskBit(kCGEventOtherMouseUp)         |
974                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
975                            CGEventMaskBit(kCGEventScrollWheel);
976         CFRunLoopSourceRef source;
977         void* appServices;
978         OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
979
980         if (cursorClippingEventTap)
981             return TRUE;
982
983         // We need to get the Mac GetCurrentProcess() from the ApplicationServices
984         // framework with dlsym() because the Win32 function of the same name
985         // obscures it.
986         appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
987         if (!appServices)
988             return FALSE;
989
990         pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
991         if (!pGetCurrentProcess)
992         {
993             dlclose(appServices);
994             return FALSE;
995         }
996
997         err = pGetCurrentProcess(&psn);
998         dlclose(appServices);
999         if (err != noErr)
1000             return FALSE;
1001
1002         // We create an annotated session event tap rather than a process-specific
1003         // event tap because we need to programmatically move the cursor even when
1004         // mouse moves are directed to other processes.  We disable our tap when
1005         // other processes are active, but things like Exposé are handled by other
1006         // processes even when we remain active.
1007         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1008             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1009         if (!cursorClippingEventTap)
1010             return FALSE;
1011
1012         CGEventTapEnable(cursorClippingEventTap, FALSE);
1013
1014         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1015         if (!source)
1016         {
1017             CFRelease(cursorClippingEventTap);
1018             cursorClippingEventTap = NULL;
1019             return FALSE;
1020         }
1021
1022         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1023         CFRelease(source);
1024         return TRUE;
1025     }
1026
1027     - (BOOL) setCursorPosition:(CGPoint)pos
1028     {
1029         BOOL ret;
1030
1031         if (clippingCursor)
1032         {
1033             [self clipCursorLocation:&pos];
1034
1035             ret = [self warpCursorTo:&pos from:NULL];
1036             synthesizedLocation = pos;
1037             if (ret)
1038             {
1039                 // We want to discard mouse-move events that have already been
1040                 // through the event tap, because it's too late to account for
1041                 // the setting of the cursor position with them.  However, the
1042                 // events that may be queued with times after that but before
1043                 // the above warp can still be used.  So, use the last event
1044                 // tap event time so that -sendEvent: doesn't discard them.
1045                 lastSetCursorPositionTime = lastEventTapEventTime;
1046             }
1047         }
1048         else
1049         {
1050             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1051             if (ret)
1052             {
1053                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1054
1055                 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1056                 // the mouse from the cursor position for 0.25 seconds.  This means
1057                 // that mouse movement during that interval doesn't move the cursor
1058                 // and events carry a constant location (the warped-to position)
1059                 // even though they have delta values.  This screws us up because
1060                 // the accumulated deltas we send to Wine don't match any eventual
1061                 // absolute position we send (like with a button press).  We can
1062                 // work around this by simply forcibly reassociating the mouse and
1063                 // cursor position.
1064                 CGAssociateMouseAndMouseCursorPosition(true);
1065             }
1066         }
1067
1068         if (ret)
1069         {
1070             WineEventQueue* queue;
1071
1072             // Discard all pending mouse move events.
1073             [eventQueuesLock lock];
1074             for (queue in eventQueues)
1075             {
1076                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1077                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1078                                        forWindow:nil];
1079                 [queue resetMouseEventPositions:pos];
1080             }
1081             [eventQueuesLock unlock];
1082         }
1083
1084         return ret;
1085     }
1086
1087     - (void) activateCursorClipping
1088     {
1089         if (clippingCursor)
1090         {
1091             CGEventTapEnable(cursorClippingEventTap, TRUE);
1092             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1093         }
1094     }
1095
1096     - (void) deactivateCursorClipping
1097     {
1098         if (clippingCursor)
1099         {
1100             CGEventTapEnable(cursorClippingEventTap, FALSE);
1101             [warpRecords removeAllObjects];
1102             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1103         }
1104     }
1105
1106     - (BOOL) startClippingCursor:(CGRect)rect
1107     {
1108         CGError err;
1109
1110         if (!cursorClippingEventTap && ![self installEventTap])
1111             return FALSE;
1112
1113         err = CGAssociateMouseAndMouseCursorPosition(false);
1114         if (err != kCGErrorSuccess)
1115             return FALSE;
1116
1117         clippingCursor = TRUE;
1118         cursorClipRect = rect;
1119         if ([NSApp isActive])
1120             [self activateCursorClipping];
1121
1122         return TRUE;
1123     }
1124
1125     - (BOOL) stopClippingCursor
1126     {
1127         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1128         if (err != kCGErrorSuccess)
1129             return FALSE;
1130
1131         [self deactivateCursorClipping];
1132         clippingCursor = FALSE;
1133
1134         return TRUE;
1135     }
1136
1137
1138     // Returns TRUE if the event was handled and caller should do nothing more
1139     // with it.  Returns FALSE if the caller should process it as normal and
1140     // then call -didSendEvent:.
1141     - (BOOL) handleEvent:(NSEvent*)anEvent
1142     {
1143         if ([anEvent type] == NSFlagsChanged)
1144             self.lastFlagsChanged = anEvent;
1145         return FALSE;
1146     }
1147
1148     - (void) didSendEvent:(NSEvent*)anEvent
1149     {
1150         NSEventType type = [anEvent type];
1151
1152         if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1153             type == NSRightMouseDragged || type == NSOtherMouseDragged)
1154         {
1155             WineWindow* targetWindow;
1156
1157             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1158                event indicates its window is the main window, even if the cursor is
1159                over a different window.  Find the actual WineWindow that is under the
1160                cursor and post the event as being for that window. */
1161             if (type == NSMouseMoved)
1162             {
1163                 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1164                 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1165                 NSInteger windowUnderNumber;
1166
1167                 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1168                                       belowWindowWithWindowNumber:0];
1169                 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1170             }
1171             else
1172                 targetWindow = (WineWindow*)[anEvent window];
1173
1174             if ([targetWindow isKindOfClass:[WineWindow class]])
1175             {
1176                 BOOL absolute = forceNextMouseMoveAbsolute || (targetWindow != lastTargetWindow);
1177                 forceNextMouseMoveAbsolute = FALSE;
1178
1179                 // If we recently warped the cursor (other than in our cursor-clipping
1180                 // event tap), discard mouse move events until we see an event which is
1181                 // later than that time.
1182                 if (lastSetCursorPositionTime)
1183                 {
1184                     if ([anEvent timestamp] <= lastSetCursorPositionTime)
1185                         return;
1186
1187                     lastSetCursorPositionTime = 0;
1188                     absolute = TRUE;
1189                 }
1190
1191                 [targetWindow postMouseMovedEvent:anEvent absolute:absolute];
1192                 lastTargetWindow = targetWindow;
1193             }
1194             else if (lastTargetWindow)
1195             {
1196                 [[NSCursor arrowCursor] set];
1197                 [self unhideCursor];
1198                 lastTargetWindow = nil;
1199             }
1200         }
1201         else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1202                  type == NSRightMouseDown || type == NSRightMouseUp ||
1203                  type == NSOtherMouseDown || type == NSOtherMouseUp ||
1204                  type == NSScrollWheel)
1205         {
1206             // Since mouse button and scroll wheel events deliver absolute cursor
1207             // position, the accumulating delta from move events is invalidated.
1208             // Make sure next mouse move event starts over from an absolute baseline.
1209             forceNextMouseMoveAbsolute = TRUE;
1210         }
1211         else if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1212         {
1213             NSUInteger modifiers = [anEvent modifierFlags];
1214             if ((modifiers & NSCommandKeyMask) &&
1215                 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
1216             {
1217                 // Command-Tab and Command-Shift-Tab would normally be intercepted
1218                 // by the system to switch applications.  If we're seeing it, it's
1219                 // presumably because we've captured the displays, preventing
1220                 // normal application switching.  Do it manually.
1221                 [self handleCommandTab];
1222             }
1223         }
1224     }
1225
1226     - (void) setupObservations
1227     {
1228         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1229
1230         [nc addObserverForName:NSWindowDidBecomeKeyNotification
1231                         object:nil
1232                          queue:nil
1233                     usingBlock:^(NSNotification *note){
1234             NSWindow* window = [note object];
1235             [keyWindows removeObjectIdenticalTo:window];
1236             [keyWindows insertObject:window atIndex:0];
1237         }];
1238
1239         [nc addObserverForName:NSWindowWillCloseNotification
1240                         object:nil
1241                          queue:[NSOperationQueue mainQueue]
1242                     usingBlock:^(NSNotification *note){
1243             NSWindow* window = [note object];
1244             [keyWindows removeObjectIdenticalTo:window];
1245             [orderedWineWindows removeObjectIdenticalTo:window];
1246             if (window == lastTargetWindow)
1247                 lastTargetWindow = nil;
1248         }];
1249
1250         [nc addObserver:self
1251                selector:@selector(keyboardSelectionDidChange)
1252                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
1253                  object:nil];
1254
1255         /* The above notification isn't sent unless the NSTextInputContext
1256            class has initialized itself.  Poke it. */
1257         [NSTextInputContext self];
1258     }
1259
1260     - (BOOL) inputSourceIsInputMethod
1261     {
1262         if (!inputSourceIsInputMethodValid)
1263         {
1264             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
1265             if (inputSource)
1266             {
1267                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
1268                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
1269                 CFRelease(inputSource);
1270             }
1271             else
1272                 inputSourceIsInputMethod = FALSE;
1273             inputSourceIsInputMethodValid = TRUE;
1274         }
1275
1276         return inputSourceIsInputMethod;
1277     }
1278
1279
1280     /*
1281      * ---------- NSApplicationDelegate methods ----------
1282      */
1283     - (void)applicationDidBecomeActive:(NSNotification *)notification
1284     {
1285         [self activateCursorClipping];
1286
1287         [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1288             WineWindow* window = obj;
1289             if ([window levelWhenActive] != [window level])
1290                 [window setLevel:[window levelWhenActive]];
1291         }];
1292
1293         // If a Wine process terminates abruptly while it has the display captured
1294         // and switched to a different resolution, Mac OS X will uncapture the
1295         // displays and switch their resolutions back.  However, the other Wine
1296         // processes won't have their notion of the desktop rect changed back.
1297         // This can lead them to refuse to draw or acknowledge clicks in certain
1298         // portions of their windows.
1299         //
1300         // To solve this, we synthesize a displays-changed event whenever we're
1301         // activated.  This will provoke a re-synchronization of Wine's notion of
1302         // the desktop rect with the actual state.
1303         [self sendDisplaysChanged:TRUE];
1304
1305         // The cursor probably moved while we were inactive.  Accumulated mouse
1306         // movement deltas are invalidated.  Make sure the next mouse move event
1307         // starts over from an absolute baseline.
1308         forceNextMouseMoveAbsolute = TRUE;
1309     }
1310
1311     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
1312     {
1313         primaryScreenHeightValid = FALSE;
1314         [self sendDisplaysChanged:FALSE];
1315
1316         // When the display configuration changes, the cursor position may jump.
1317         // Accumulated mouse movement deltas are invalidated.  Make sure the next
1318         // mouse move event starts over from an absolute baseline.
1319         forceNextMouseMoveAbsolute = TRUE;
1320     }
1321
1322     - (void)applicationDidResignActive:(NSNotification *)notification
1323     {
1324         macdrv_event* event;
1325         WineEventQueue* queue;
1326
1327         [self invalidateGotFocusEvents];
1328
1329         event = macdrv_create_event(APP_DEACTIVATED, nil);
1330
1331         [eventQueuesLock lock];
1332         for (queue in eventQueues)
1333             [queue postEvent:event];
1334         [eventQueuesLock unlock];
1335
1336         macdrv_release_event(event);
1337     }
1338
1339     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
1340     {
1341         NSApplicationTerminateReply ret = NSTerminateNow;
1342         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
1343         NSAppleEventDescriptor* desc = [m currentAppleEvent];
1344         macdrv_event* event;
1345         WineEventQueue* queue;
1346
1347         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
1348         event->deliver = 1;
1349         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
1350         {
1351             case kAELogOut:
1352             case kAEReallyLogOut:
1353                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
1354                 break;
1355             case kAEShowRestartDialog:
1356                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
1357                 break;
1358             case kAEShowShutdownDialog:
1359                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
1360                 break;
1361             default:
1362                 event->app_quit_requested.reason = QUIT_REASON_NONE;
1363                 break;
1364         }
1365
1366         [eventQueuesLock lock];
1367
1368         if ([eventQueues count])
1369         {
1370             for (queue in eventQueues)
1371                 [queue postEvent:event];
1372             ret = NSTerminateLater;
1373         }
1374
1375         [eventQueuesLock unlock];
1376
1377         macdrv_release_event(event);
1378
1379         return ret;
1380     }
1381
1382     - (void)applicationWillResignActive:(NSNotification *)notification
1383     {
1384         [self deactivateCursorClipping];
1385
1386         [orderedWineWindows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1387             WineWindow* window = obj;
1388             NSInteger level = window.floating ? NSFloatingWindowLevel : NSNormalWindowLevel;
1389             if ([window level] > level)
1390                 [window setLevel:level];
1391         }];
1392     }
1393
1394 /***********************************************************************
1395  *              PerformRequest
1396  *
1397  * Run-loop-source perform callback.  Pull request blocks from the
1398  * array of queued requests and invoke them.
1399  */
1400 static void PerformRequest(void *info)
1401 {
1402     WineApplicationController* controller = [WineApplicationController sharedController];
1403
1404     for (;;)
1405     {
1406         __block dispatch_block_t block;
1407
1408         dispatch_sync(controller->requestsManipQueue, ^{
1409             if ([controller->requests count])
1410             {
1411                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
1412                 [controller->requests removeObjectAtIndex:0];
1413             }
1414             else
1415                 block = nil;
1416         });
1417
1418         if (!block)
1419             break;
1420
1421         block();
1422         [block release];
1423     }
1424 }
1425
1426 /***********************************************************************
1427  *              OnMainThreadAsync
1428  *
1429  * Run a block on the main thread asynchronously.
1430  */
1431 void OnMainThreadAsync(dispatch_block_t block)
1432 {
1433     WineApplicationController* controller = [WineApplicationController sharedController];
1434
1435     block = [block copy];
1436     dispatch_sync(controller->requestsManipQueue, ^{
1437         [controller->requests addObject:block];
1438     });
1439     [block release];
1440     CFRunLoopSourceSignal(controller->requestSource);
1441     CFRunLoopWakeUp(CFRunLoopGetMain());
1442 }
1443
1444 @end
1445
1446 /***********************************************************************
1447  *              LogError
1448  */
1449 void LogError(const char* func, NSString* format, ...)
1450 {
1451     va_list args;
1452     va_start(args, format);
1453     LogErrorv(func, format, args);
1454     va_end(args);
1455 }
1456
1457 /***********************************************************************
1458  *              LogErrorv
1459  */
1460 void LogErrorv(const char* func, NSString* format, va_list args)
1461 {
1462     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1463
1464     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
1465     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
1466     [message release];
1467
1468     [pool release];
1469 }
1470
1471 /***********************************************************************
1472  *              macdrv_window_rejected_focus
1473  *
1474  * Pass focus to the next window that hasn't already rejected this same
1475  * WINDOW_GOT_FOCUS event.
1476  */
1477 void macdrv_window_rejected_focus(const macdrv_event *event)
1478 {
1479     OnMainThread(^{
1480         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
1481     });
1482 }
1483
1484 /***********************************************************************
1485  *              macdrv_get_keyboard_layout
1486  *
1487  * Returns the keyboard layout uchr data.
1488  */
1489 CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso)
1490 {
1491     __block CFDataRef result = NULL;
1492
1493     OnMainThread(^{
1494         TISInputSourceRef inputSource;
1495
1496         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
1497         if (inputSource)
1498         {
1499             CFDataRef uchr = TISGetInputSourceProperty(inputSource,
1500                                 kTISPropertyUnicodeKeyLayoutData);
1501             result = CFDataCreateCopy(NULL, uchr);
1502             CFRelease(inputSource);
1503
1504             *keyboard_type = [WineApplicationController sharedController].keyboardType;
1505             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
1506         }
1507     });
1508
1509     return result;
1510 }
1511
1512 /***********************************************************************
1513  *              macdrv_beep
1514  *
1515  * Play the beep sound configured by the user in System Preferences.
1516  */
1517 void macdrv_beep(void)
1518 {
1519     OnMainThreadAsync(^{
1520         NSBeep();
1521     });
1522 }
1523
1524 /***********************************************************************
1525  *              macdrv_set_display_mode
1526  */
1527 int macdrv_set_display_mode(const struct macdrv_display* display,
1528                             CGDisplayModeRef display_mode)
1529 {
1530     __block int ret;
1531
1532     OnMainThread(^{
1533         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
1534     });
1535
1536     return ret;
1537 }
1538
1539 /***********************************************************************
1540  *              macdrv_set_cursor
1541  *
1542  * Set the cursor.
1543  *
1544  * If name is non-NULL, it is a selector for a class method on NSCursor
1545  * identifying the cursor to set.  In that case, frames is ignored.  If
1546  * name is NULL, then frames is used.
1547  *
1548  * frames is an array of dictionaries.  Each dictionary is a frame of
1549  * an animated cursor.  Under the key "image" is a CGImage for the
1550  * frame.  Under the key "duration" is a CFNumber time interval, in
1551  * seconds, for how long that frame is presented before proceeding to
1552  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
1553  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
1554  * This is the hot spot, measured in pixels down and to the right of the
1555  * top-left corner of the image.
1556  *
1557  * If the array has exactly 1 element, the cursor is static, not
1558  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
1559  */
1560 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
1561 {
1562     SEL sel;
1563
1564     sel = NSSelectorFromString((NSString*)name);
1565     if (sel)
1566     {
1567         OnMainThreadAsync(^{
1568             WineApplicationController* controller = [WineApplicationController sharedController];
1569             NSCursor* cursor = [NSCursor performSelector:sel];
1570             [controller setCursorWithFrames:nil];
1571             [cursor set];
1572             [controller unhideCursor];
1573         });
1574     }
1575     else
1576     {
1577         NSArray* nsframes = (NSArray*)frames;
1578         if ([nsframes count])
1579         {
1580             OnMainThreadAsync(^{
1581                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
1582             });
1583         }
1584         else
1585         {
1586             OnMainThreadAsync(^{
1587                 WineApplicationController* controller = [WineApplicationController sharedController];
1588                 [controller setCursorWithFrames:nil];
1589                 [controller hideCursor];
1590             });
1591         }
1592     }
1593 }
1594
1595 /***********************************************************************
1596  *              macdrv_get_cursor_position
1597  *
1598  * Obtains the current cursor position.  Returns zero on failure,
1599  * non-zero on success.
1600  */
1601 int macdrv_get_cursor_position(CGPoint *pos)
1602 {
1603     OnMainThread(^{
1604         NSPoint location = [NSEvent mouseLocation];
1605         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
1606         *pos = NSPointToCGPoint(location);
1607     });
1608
1609     return TRUE;
1610 }
1611
1612 /***********************************************************************
1613  *              macdrv_set_cursor_position
1614  *
1615  * Sets the cursor position without generating events.  Returns zero on
1616  * failure, non-zero on success.
1617  */
1618 int macdrv_set_cursor_position(CGPoint pos)
1619 {
1620     __block int ret;
1621
1622     OnMainThread(^{
1623         ret = [[WineApplicationController sharedController] setCursorPosition:pos];
1624     });
1625
1626     return ret;
1627 }
1628
1629 /***********************************************************************
1630  *              macdrv_clip_cursor
1631  *
1632  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
1633  * to or larger than the whole desktop region, the cursor is unclipped.
1634  * Returns zero on failure, non-zero on success.
1635  */
1636 int macdrv_clip_cursor(CGRect rect)
1637 {
1638     __block int ret;
1639
1640     OnMainThread(^{
1641         WineApplicationController* controller = [WineApplicationController sharedController];
1642         BOOL clipping = FALSE;
1643
1644         if (!CGRectIsInfinite(rect))
1645         {
1646             NSRect nsrect = NSRectFromCGRect(rect);
1647             NSScreen* screen;
1648
1649             /* Convert the rectangle from top-down coords to bottom-up. */
1650             [controller flipRect:&nsrect];
1651
1652             clipping = FALSE;
1653             for (screen in [NSScreen screens])
1654             {
1655                 if (!NSContainsRect(nsrect, [screen frame]))
1656                 {
1657                     clipping = TRUE;
1658                     break;
1659                 }
1660             }
1661         }
1662
1663         if (clipping)
1664             ret = [controller startClippingCursor:rect];
1665         else
1666             ret = [controller stopClippingCursor];
1667     });
1668
1669     return ret;
1670 }
1671
1672 /***********************************************************************
1673  *              macdrv_set_application_icon
1674  *
1675  * Set the application icon.  The images array contains CGImages.  If
1676  * there are more than one, then they represent different sizes or
1677  * color depths from the icon resource.  If images is NULL or empty,
1678  * restores the default application image.
1679  */
1680 void macdrv_set_application_icon(CFArrayRef images)
1681 {
1682     NSArray* imageArray = (NSArray*)images;
1683
1684     OnMainThreadAsync(^{
1685         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
1686     });
1687 }
1688
1689 /***********************************************************************
1690  *              macdrv_quit_reply
1691  */
1692 void macdrv_quit_reply(int reply)
1693 {
1694     OnMainThread(^{
1695         [NSApp replyToApplicationShouldTerminate:reply];
1696     });
1697 }
1698
1699 /***********************************************************************
1700  *              macdrv_using_input_method
1701  */
1702 int macdrv_using_input_method(void)
1703 {
1704     __block BOOL ret;
1705
1706     OnMainThread(^{
1707         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
1708     });
1709
1710     return ret;
1711 }