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