po: Fix the 'All key' Korean translation.
[wine.git] / dlls / winemac.drv / cocoa_app.m
blob8de68bb1618203361e1299de5b14f9a893399878
1 /*
2  * MACDRV Cocoa application class
3  *
4  * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
21 #import <Carbon/Carbon.h>
22 #include <dlfcn.h>
24 #import "cocoa_app.h"
25 #import "cocoa_event.h"
26 #import "cocoa_window.h"
29 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
32 int macdrv_err_on;
35 @implementation WineApplication
37 @synthesize wineController;
39     - (void) sendEvent:(NSEvent*)anEvent
40     {
41         if (![wineController handleEvent:anEvent])
42         {
43             [super sendEvent:anEvent];
44             [wineController didSendEvent:anEvent];
45         }
46     }
48     - (void) setWineController:(WineApplicationController*)newController
49     {
50         wineController = newController;
51         [self setDelegate:wineController];
52     }
54 @end
57 @interface WarpRecord : NSObject
59     CGEventTimestamp timeBefore, timeAfter;
60     CGPoint from, to;
63 @property (nonatomic) CGEventTimestamp timeBefore;
64 @property (nonatomic) CGEventTimestamp timeAfter;
65 @property (nonatomic) CGPoint from;
66 @property (nonatomic) CGPoint to;
68 @end
71 @implementation WarpRecord
73 @synthesize timeBefore, timeAfter, from, to;
75 @end;
78 @interface WineApplicationController ()
80 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
81 @property (copy, nonatomic) NSArray* cursorFrames;
82 @property (retain, nonatomic) NSTimer* cursorTimer;
83 @property (retain, nonatomic) NSImage* applicationIcon;
84 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
85 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
87     - (void) setupObservations;
88     - (void) applicationDidBecomeActive:(NSNotification *)notification;
90     static void PerformRequest(void *info);
92 @end
95 @implementation WineApplicationController
97     @synthesize keyboardType, lastFlagsChanged;
98     @synthesize applicationIcon;
99     @synthesize cursorFrames, cursorTimer;
100     @synthesize mouseCaptureWindow;
102     + (void) initialize
103     {
104         if (self == [WineApplicationController class])
105         {
106             NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
107                                       @"", @"NSQuotedKeystrokeBinding",
108                                       @"", @"NSRepeatCountBinding",
109                                       [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
110                                       nil];
111             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
112         }
113     }
115     + (WineApplicationController*) sharedController
116     {
117         static WineApplicationController* sharedController;
118         static dispatch_once_t once;
120         dispatch_once(&once, ^{
121             sharedController = [[self alloc] init];
122         });
124         return sharedController;
125     }
127     - (id) init
128     {
129         self = [super init];
130         if (self != nil)
131         {
132             CFRunLoopSourceContext context = { 0 };
133             context.perform = PerformRequest;
134             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
135             if (!requestSource)
136             {
137                 [self release];
138                 return nil;
139             }
140             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
141             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
143             requests =  [[NSMutableArray alloc] init];
144             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
146             eventQueues = [[NSMutableArray alloc] init];
147             eventQueuesLock = [[NSLock alloc] init];
149             keyWindows = [[NSMutableArray alloc] init];
151             originalDisplayModes = [[NSMutableDictionary alloc] init];
153             warpRecords = [[NSMutableArray alloc] init];
155             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
156                 !keyWindows || !originalDisplayModes || !warpRecords)
157             {
158                 [self release];
159                 return nil;
160             }
162             [self setupObservations];
164             keyboardType = LMGetKbdType();
166             if ([NSApp isActive])
167                 [self applicationDidBecomeActive:nil];
168         }
169         return self;
170     }
172     - (void) dealloc
173     {
174         [screenFrameCGRects release];
175         [applicationIcon release];
176         [warpRecords release];
177         [cursorTimer release];
178         [cursorFrames release];
179         [originalDisplayModes release];
180         [keyWindows release];
181         [eventQueues release];
182         [eventQueuesLock release];
183         if (requestsManipQueue) dispatch_release(requestsManipQueue);
184         [requests release];
185         if (requestSource)
186         {
187             CFRunLoopSourceInvalidate(requestSource);
188             CFRelease(requestSource);
189         }
190         [super dealloc];
191     }
193     - (void) transformProcessToForeground
194     {
195         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
196         {
197             NSMenu* mainMenu;
198             NSMenu* submenu;
199             NSString* bundleName;
200             NSString* title;
201             NSMenuItem* item;
203             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
204             [NSApp activateIgnoringOtherApps:YES];
206             mainMenu = [[[NSMenu alloc] init] autorelease];
208             // Application menu
209             submenu = [[[NSMenu alloc] initWithTitle:@"Wine"] autorelease];
210             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
212             if ([bundleName length])
213                 title = [NSString stringWithFormat:@"Hide %@", bundleName];
214             else
215                 title = @"Hide";
216             item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
218             item = [submenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
219             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
221             item = [submenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
223             [submenu addItem:[NSMenuItem separatorItem]];
225             if ([bundleName length])
226                 title = [NSString stringWithFormat:@"Quit %@", bundleName];
227             else
228                 title = @"Quit";
229             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
230             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
231             item = [[[NSMenuItem alloc] init] autorelease];
232             [item setTitle:@"Wine"];
233             [item setSubmenu:submenu];
234             [mainMenu addItem:item];
236             // Window menu
237             submenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
238             [submenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
239             [submenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
240             [submenu addItem:[NSMenuItem separatorItem]];
241             [submenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
242             item = [[[NSMenuItem alloc] init] autorelease];
243             [item setTitle:@"Window"];
244             [item setSubmenu:submenu];
245             [mainMenu addItem:item];
247             [NSApp setMainMenu:mainMenu];
248             [NSApp setWindowsMenu:submenu];
250             [NSApp setApplicationIconImage:self.applicationIcon];
251         }
252     }
254     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
255     {
256         PerformRequest(NULL);
258         do
259         {
260             if (processEvents)
261             {
262                 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
263                 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
264                                                     untilDate:timeout
265                                                        inMode:NSDefaultRunLoopMode
266                                                       dequeue:YES];
267                 if (event)
268                     [NSApp sendEvent:event];
269                 [pool release];
270             }
271             else
272                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
273         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
275         return *done;
276     }
278     - (BOOL) registerEventQueue:(WineEventQueue*)queue
279     {
280         [eventQueuesLock lock];
281         [eventQueues addObject:queue];
282         [eventQueuesLock unlock];
283         return TRUE;
284     }
286     - (void) unregisterEventQueue:(WineEventQueue*)queue
287     {
288         [eventQueuesLock lock];
289         [eventQueues removeObjectIdenticalTo:queue];
290         [eventQueuesLock unlock];
291     }
293     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
294     {
295         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
296     }
298     - (double) ticksForEventTime:(NSTimeInterval)eventTime
299     {
300         return (eventTime + eventTimeAdjustment) * 1000;
301     }
303     /* Invalidate old focus offers across all queues. */
304     - (void) invalidateGotFocusEvents
305     {
306         WineEventQueue* queue;
308         windowFocusSerial++;
310         [eventQueuesLock lock];
311         for (queue in eventQueues)
312         {
313             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
314                                    forWindow:nil];
315         }
316         [eventQueuesLock unlock];
317     }
319     - (void) windowGotFocus:(WineWindow*)window
320     {
321         macdrv_event* event;
323         [self invalidateGotFocusEvents];
325         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
326         event->window_got_focus.serial = windowFocusSerial;
327         if (triedWindows)
328             event->window_got_focus.tried_windows = [triedWindows retain];
329         else
330             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
331         [window.queue postEvent:event];
332         macdrv_release_event(event);
333     }
335     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
336     {
337         if (event->window_got_focus.serial == windowFocusSerial)
338         {
339             NSMutableArray* windows = [keyWindows mutableCopy];
340             NSNumber* windowNumber;
341             WineWindow* window;
343             for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
344             {
345                 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
346                 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
347                     ![windows containsObject:window])
348                     [windows addObject:window];
349             }
351             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
352             [triedWindows addObject:(WineWindow*)event->window];
353             for (window in windows)
354             {
355                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
356                 {
357                     [window makeKeyWindow];
358                     break;
359                 }
360             }
361             triedWindows = nil;
362             [windows release];
363         }
364     }
366     - (void) keyboardSelectionDidChange
367     {
368         TISInputSourceRef inputSource;
370         inputSourceIsInputMethodValid = FALSE;
372         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
373         if (inputSource)
374         {
375             CFDataRef uchr;
376             uchr = TISGetInputSourceProperty(inputSource,
377                     kTISPropertyUnicodeKeyLayoutData);
378             if (uchr)
379             {
380                 macdrv_event* event;
381                 WineEventQueue* queue;
383                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
384                 event->keyboard_changed.keyboard_type = self.keyboardType;
385                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
386                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
388                 if (event->keyboard_changed.uchr)
389                 {
390                     [eventQueuesLock lock];
392                     for (queue in eventQueues)
393                         [queue postEvent:event];
395                     [eventQueuesLock unlock];
396                 }
398                 macdrv_release_event(event);
399             }
401             CFRelease(inputSource);
402         }
403     }
405     - (CGFloat) primaryScreenHeight
406     {
407         if (!primaryScreenHeightValid)
408         {
409             NSArray* screens = [NSScreen screens];
410             NSUInteger count = [screens count];
411             if (count)
412             {
413                 NSUInteger size;
414                 CGRect* rect;
415                 NSScreen* screen;
417                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
418                 primaryScreenHeightValid = TRUE;
420                 size = count * sizeof(CGRect);
421                 if (!screenFrameCGRects)
422                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
423                 else
424                     [screenFrameCGRects setLength:size];
426                 rect = [screenFrameCGRects mutableBytes];
427                 for (screen in screens)
428                 {
429                     CGRect temp = NSRectToCGRect([screen frame]);
430                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
431                     *rect++ = temp;
432                 }
433             }
434             else
435                 return 1280; /* arbitrary value */
436         }
438         return primaryScreenHeight;
439     }
441     - (NSPoint) flippedMouseLocation:(NSPoint)point
442     {
443         /* This relies on the fact that Cocoa's mouse location points are
444            actually off by one (precisely because they were flipped from
445            Quartz screen coordinates using this same technique). */
446         point.y = [self primaryScreenHeight] - point.y;
447         return point;
448     }
450     - (void) flipRect:(NSRect*)rect
451     {
452         // We don't use -primaryScreenHeight here so there's no chance of having
453         // out-of-date cached info.  This method is called infrequently enough
454         // that getting the screen height each time is not prohibitively expensive.
455         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
456     }
458     - (WineWindow*) frontWineWindow
459     {
460         NSNumber* windowNumber;
461         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
462         {
463             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
464             if ([window isKindOfClass:[WineWindow class]] && [window screen])
465                 return (WineWindow*)window;
466         }
468         return nil;
469     }
471     - (void) adjustWindowLevels:(BOOL)active
472     {
473         NSArray* windowNumbers = [NSWindow windowNumbersWithOptions:0];
474         NSMutableArray* wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
475         NSNumber* windowNumber;
476         NSUInteger nextFloatingIndex = 0;
477         __block NSInteger maxLevel = NSIntegerMin;
478         __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
479         __block WineWindow* prev = nil;
480         WineWindow* window;
482         // For the most part, we rely on the window server's ordering of the windows
483         // to be authoritative.  The one exception is if the "floating" property of
484         // one of the windows has been changed, it may be in the wrong level and thus
485         // in the order.  This method is what's supposed to fix that up.  So build
486         // a list of Wine windows sorted first by floating-ness and then by order
487         // as indicated by the window server.
488         for (windowNumber in windowNumbers)
489         {
490             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
491             if ([window isKindOfClass:[WineWindow class]])
492             {
493                 if (window.floating)
494                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
495                 else
496                     [wineWindows addObject:window];
497             }
498         }
500         NSDisableScreenUpdates();
502         // Go from back to front so that all windows in front of one which is
503         // elevated for full-screen are also elevated.
504         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
505                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
506             WineWindow* window = (WineWindow*)obj;
507             NSInteger origLevel = [window level];
508             NSInteger newLevel = [window minimumLevelForActive:active];
510             if (newLevel < maxLevel)
511                 newLevel = maxLevel;
512             else
513                 maxLevel = newLevel;
515             if (!window.floating && maxNonfloatingLevel < newLevel)
516                 maxNonfloatingLevel = newLevel;
518             if (newLevel != origLevel)
519             {
520                 [window setLevel:newLevel];
522                 // -setLevel: puts the window at the front of its new level.  If
523                 // we decreased the level, that's good (it was in front of that
524                 // level before, so it should still be now).  But if we increased
525                 // the level, the window should be toward the back (but still
526                 // ahead of the previous windows we did this to).
527                 if (origLevel < newLevel)
528                 {
529                     if (prev)
530                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
531                     else
532                         [window orderBack:nil];
533                 }
534             }
536             prev = window;
537         }];
539         NSEnableScreenUpdates();
541         [wineWindows release];
543         // The above took care of the visible windows on the current space.  That
544         // leaves windows on other spaces, minimized windows, and windows which
545         // are not ordered in.  We want to leave windows on other spaces alone
546         // so the space remains just as they left it (when viewed in Exposé or
547         // Mission Control, for example).  We'll adjust the window levels again
548         // after we switch to another space, anyway.  Windows which aren't
549         // ordered in will be handled when we order them in.  Minimized windows
550         // on the current space should be set to the level they would have gotten
551         // if they were at the front of the windows with the same floating-ness,
552         // because that's where they'll go if/when they are unminimized.  Again,
553         // for good measure we'll adjust window levels again when a window is
554         // unminimized, too.
555         for (window in [NSApp windows])
556         {
557             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
558                 [window isOnActiveSpace])
559             {
560                 NSInteger origLevel = [window level];
561                 NSInteger newLevel = [window minimumLevelForActive:YES];
562                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
564                 if (newLevel < maxLevelForType)
565                     newLevel = maxLevelForType;
567                 if (newLevel != origLevel)
568                     [window setLevel:newLevel];
569             }
570         }
571     }
573     - (void) adjustWindowLevels
574     {
575         [self adjustWindowLevels:[NSApp isActive]];
576     }
578     - (void) updateFullscreenWindows
579     {
580         if (capture_displays_for_fullscreen && [NSApp isActive])
581         {
582             BOOL anyFullscreen = FALSE;
583             NSNumber* windowNumber;
584             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
585             {
586                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
587                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
588                 {
589                     anyFullscreen = TRUE;
590                     break;
591                 }
592             }
594             if (anyFullscreen)
595             {
596                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
597                     displaysCapturedForFullscreen = TRUE;
598             }
599             else if (displaysCapturedForFullscreen)
600             {
601                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
602                     displaysCapturedForFullscreen = FALSE;
603             }
604         }
605     }
607     - (void) activeSpaceDidChange
608     {
609         [self updateFullscreenWindows];
610         [self adjustWindowLevels];
611     }
613     - (void) sendDisplaysChanged:(BOOL)activating
614     {
615         macdrv_event* event;
616         WineEventQueue* queue;
618         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
619         event->displays_changed.activating = activating;
621         [eventQueuesLock lock];
623         // If we're activating, then we just need one of our threads to get the
624         // event, so it can send it directly to the desktop window.  Otherwise,
625         // we need all of the threads to get it because we don't know which owns
626         // the desktop window and only that one will do anything with it.
627         if (activating) event->deliver = 1;
629         for (queue in eventQueues)
630             [queue postEvent:event];
631         [eventQueuesLock unlock];
633         macdrv_release_event(event);
634     }
636     // We can compare two modes directly using CFEqual, but that may require that
637     // they are identical to a level that we don't need.  In particular, when the
638     // OS switches between the integrated and discrete GPUs, the set of display
639     // modes can change in subtle ways.  We're interested in whether two modes
640     // match in their most salient features, even if they aren't identical.
641     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
642     {
643         NSString *encoding1, *encoding2;
644         uint32_t ioflags1, ioflags2, different;
645         double refresh1, refresh2;
647         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
648         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
650         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
651         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
652         if (![encoding1 isEqualToString:encoding2]) return FALSE;
654         ioflags1 = CGDisplayModeGetIOFlags(mode1);
655         ioflags2 = CGDisplayModeGetIOFlags(mode2);
656         different = ioflags1 ^ ioflags2;
657         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
658                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
659             return FALSE;
661         refresh1 = CGDisplayModeGetRefreshRate(mode1);
662         if (refresh1 == 0) refresh1 = 60;
663         refresh2 = CGDisplayModeGetRefreshRate(mode2);
664         if (refresh2 == 0) refresh2 = 60;
665         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
667         return TRUE;
668     }
670     - (CGDisplayModeRef)modeMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
671     {
672         CGDisplayModeRef ret = NULL;
673         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, NULL) autorelease];
674         for (id candidateModeObject in modes)
675         {
676             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
677             if ([self mode:candidateMode matchesMode:mode])
678             {
679                 ret = candidateMode;
680                 break;
681             }
682         }
683         return ret;
684     }
686     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
687     {
688         BOOL ret = FALSE;
689         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
690         CGDisplayModeRef currentMode, originalMode;
692         currentMode = CGDisplayCopyDisplayMode(displayID);
693         if (!currentMode) // Invalid display ID
694             return FALSE;
696         if ([self mode:mode matchesMode:currentMode]) // Already there!
697         {
698             CGDisplayModeRelease(currentMode);
699             return TRUE;
700         }
702         mode = [self modeMatchingMode:mode forDisplay:displayID];
703         if (!mode)
704         {
705             CGDisplayModeRelease(currentMode);
706             return FALSE;
707         }
709         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
710         if (!originalMode)
711             originalMode = currentMode;
713         if ([self mode:mode matchesMode:originalMode])
714         {
715             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
716             {
717                 CGRestorePermanentDisplayConfiguration();
718                 if (!displaysCapturedForFullscreen)
719                     CGReleaseAllDisplays();
720                 [originalDisplayModes removeAllObjects];
721                 ret = TRUE;
722             }
723             else // ... otherwise, try to restore just the one display
724             {
725                 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
726                 {
727                     [originalDisplayModes removeObjectForKey:displayIDKey];
728                     ret = TRUE;
729                 }
730             }
731         }
732         else
733         {
734             if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
735                 CGCaptureAllDisplays() == CGDisplayNoErr)
736             {
737                 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
738                 {
739                     [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
740                     ret = TRUE;
741                 }
742                 else if (![originalDisplayModes count])
743                 {
744                     CGRestorePermanentDisplayConfiguration();
745                     if (!displaysCapturedForFullscreen)
746                         CGReleaseAllDisplays();
747                 }
748             }
749         }
751         CGDisplayModeRelease(currentMode);
753         if (ret)
754             [self adjustWindowLevels];
756         return ret;
757     }
759     - (BOOL) areDisplaysCaptured
760     {
761         return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
762     }
764     - (void) hideCursor
765     {
766         if (!cursorHidden)
767         {
768             [NSCursor hide];
769             cursorHidden = TRUE;
770         }
771     }
773     - (void) unhideCursor
774     {
775         if (cursorHidden)
776         {
777             [NSCursor unhide];
778             cursorHidden = FALSE;
779         }
780     }
782     - (void) setCursor
783     {
784         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
785         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
786         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
787         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
788         CGPoint hotSpot;
789         NSCursor* cursor;
791         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
792             hotSpot = CGPointZero;
793         cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)];
794         [image release];
795         [cursor set];
796         [self unhideCursor];
797         [cursor release];
798     }
800     - (void) nextCursorFrame:(NSTimer*)theTimer
801     {
802         NSDictionary* frame;
803         NSTimeInterval duration;
804         NSDate* date;
806         cursorFrame++;
807         if (cursorFrame >= [cursorFrames count])
808             cursorFrame = 0;
809         [self setCursor];
811         frame = [cursorFrames objectAtIndex:cursorFrame];
812         duration = [[frame objectForKey:@"duration"] doubleValue];
813         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
814         [cursorTimer setFireDate:date];
815     }
817     - (void) setCursorWithFrames:(NSArray*)frames
818     {
819         if (self.cursorFrames == frames)
820             return;
822         self.cursorFrames = frames;
823         cursorFrame = 0;
824         [cursorTimer invalidate];
825         self.cursorTimer = nil;
827         if ([frames count])
828         {
829             if ([frames count] > 1)
830             {
831                 NSDictionary* frame = [frames objectAtIndex:0];
832                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
833                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
834                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
835                                                              interval:1000000
836                                                                target:self
837                                                              selector:@selector(nextCursorFrame:)
838                                                              userInfo:nil
839                                                               repeats:YES] autorelease];
840                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
841             }
843             [self setCursor];
844         }
845     }
847     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
848     {
849         NSImage* nsimage = nil;
851         if ([images count])
852         {
853             NSSize bestSize = NSZeroSize;
854             id image;
856             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
858             for (image in images)
859             {
860                 CGImageRef cgimage = (CGImageRef)image;
861                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
862                 if (imageRep)
863                 {
864                     NSSize size = [imageRep size];
866                     [nsimage addRepresentation:imageRep];
867                     [imageRep release];
869                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
870                         bestSize = size;
871                 }
872             }
874             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
875                 [nsimage setSize:bestSize];
876             else
877                 nsimage = nil;
878         }
880         self.applicationIcon = nsimage;
881         [NSApp setApplicationIconImage:nsimage];
882     }
884     - (void) handleCommandTab
885     {
886         if ([NSApp isActive])
887         {
888             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
889             NSRunningApplication* app;
890             NSRunningApplication* otherValidApp = nil;
892             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
893             {
894                 CGRestorePermanentDisplayConfiguration();
895                 CGReleaseAllDisplays();
896                 [originalDisplayModes removeAllObjects];
897                 displaysCapturedForFullscreen = FALSE;
898             }
900             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
901             {
902                 if (![app isEqual:thisApp] && !app.terminated &&
903                     app.activationPolicy == NSApplicationActivationPolicyRegular)
904                 {
905                     if (!app.hidden)
906                     {
907                         // There's another visible app.  Just hide ourselves and let
908                         // the system activate the other app.
909                         [NSApp hide:self];
910                         return;
911                     }
913                     if (!otherValidApp)
914                         otherValidApp = app;
915                 }
916             }
918             // Didn't find a visible GUI app.  Try the Finder or, if that's not
919             // running, the first hidden GUI app.  If even that doesn't work, we
920             // just fail to switch and remain the active app.
921             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
922             if (!app) app = otherValidApp;
923             [app unhide];
924             [app activateWithOptions:0];
925         }
926     }
928     /*
929      * ---------- Cursor clipping methods ----------
930      *
931      * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
932      * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
933      * equivalent: CGAssociateMouseAndMouseCursorPosition(false).  For the
934      * general case, we leverage that.  We disassociate mouse movements from
935      * the cursor position and then move the cursor manually, keeping it within
936      * the clipping rectangle.
937      *
938      * Moving the cursor manually isn't enough.  We need to modify the event
939      * stream so that the events have the new location, too.  We need to do
940      * this at a point before the events enter Cocoa, so that Cocoa will assign
941      * the correct window to the event.  So, we install a Quartz event tap to
942      * do that.
943      *
944      * Also, there's a complication when we move the cursor.  We use
945      * CGWarpMouseCursorPosition().  That doesn't generate mouse movement
946      * events, but the change of cursor position is incorporated into the
947      * deltas of the next mouse move event.  When the mouse is disassociated
948      * from the cursor position, we need the deltas to only reflect actual
949      * device movement, not programmatic changes.  So, the event tap cancels
950      * out the change caused by our calls to CGWarpMouseCursorPosition().
951      */
952     - (void) clipCursorLocation:(CGPoint*)location
953     {
954         if (location->x < CGRectGetMinX(cursorClipRect))
955             location->x = CGRectGetMinX(cursorClipRect);
956         if (location->y < CGRectGetMinY(cursorClipRect))
957             location->y = CGRectGetMinY(cursorClipRect);
958         if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
959             location->x = CGRectGetMaxX(cursorClipRect) - 1;
960         if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
961             location->y = CGRectGetMaxY(cursorClipRect) - 1;
962     }
964     - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
965     {
966         CGPoint oldLocation;
968         if (currentLocation)
969             oldLocation = *currentLocation;
970         else
971             oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
973         if (!CGPointEqualToPoint(oldLocation, *newLocation))
974         {
975             WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
976             CGError err;
978             warpRecord.from = oldLocation;
979             warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
981             /* Actually move the cursor. */
982             err = CGWarpMouseCursorPosition(*newLocation);
983             if (err != kCGErrorSuccess)
984                 return FALSE;
986             warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
987             *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
989             if (!CGPointEqualToPoint(oldLocation, *newLocation))
990             {
991                 warpRecord.to = *newLocation;
992                 [warpRecords addObject:warpRecord];
993             }
994         }
996         return TRUE;
997     }
999     - (BOOL) isMouseMoveEventType:(CGEventType)type
1000     {
1001         switch(type)
1002         {
1003         case kCGEventMouseMoved:
1004         case kCGEventLeftMouseDragged:
1005         case kCGEventRightMouseDragged:
1006         case kCGEventOtherMouseDragged:
1007             return TRUE;
1008         }
1010         return FALSE;
1011     }
1013     - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1014     {
1015         int warpsFinished = 0;
1016         for (WarpRecord* warpRecord in warpRecords)
1017         {
1018             if (warpRecord.timeAfter < eventTime ||
1019                 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1020                 warpsFinished++;
1021             else
1022                 break;
1023         }
1025         return warpsFinished;
1026     }
1028     - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1029                                 type:(CGEventType)type
1030                                event:(CGEventRef)event
1031     {
1032         CGEventTimestamp eventTime;
1033         CGPoint eventLocation, cursorLocation;
1035         if (type == kCGEventTapDisabledByUserInput)
1036             return event;
1037         if (type == kCGEventTapDisabledByTimeout)
1038         {
1039             CGEventTapEnable(cursorClippingEventTap, TRUE);
1040             return event;
1041         }
1043         if (!clippingCursor)
1044             return event;
1046         eventTime = CGEventGetTimestamp(event);
1047         lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1049         eventLocation = CGEventGetLocation(event);
1051         cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1053         if ([self isMouseMoveEventType:type])
1054         {
1055             double deltaX, deltaY;
1056             int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1057             int i;
1059             deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1060             deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1062             for (i = 0; i < warpsFinished; i++)
1063             {
1064                 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1065                 deltaX -= warpRecord.to.x - warpRecord.from.x;
1066                 deltaY -= warpRecord.to.y - warpRecord.from.y;
1067                 [warpRecords removeObjectAtIndex:0];
1068             }
1070             if (warpsFinished)
1071             {
1072                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1073                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1074             }
1076             synthesizedLocation.x += deltaX;
1077             synthesizedLocation.y += deltaY;
1078         }
1080         // If the event is destined for another process, don't clip it.  This may
1081         // happen if the user activates Exposé or Mission Control.  In that case,
1082         // our app does not resign active status, so clipping is still in effect,
1083         // but the cursor should not actually be clipped.
1084         //
1085         // In addition, the fact that mouse moves may have been delivered to a
1086         // different process means we have to treat the next one we receive as
1087         // absolute rather than relative.
1088         if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1089             [self clipCursorLocation:&synthesizedLocation];
1090         else
1091             lastSetCursorPositionTime = lastEventTapEventTime;
1093         [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1094         if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1095             CGEventSetLocation(event, synthesizedLocation);
1097         return event;
1098     }
1100     CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1101                                        CGEventRef event, void *refcon)
1102     {
1103         WineApplicationController* controller = refcon;
1104         return [controller eventTapWithProxy:proxy type:type event:event];
1105     }
1107     - (BOOL) installEventTap
1108     {
1109         ProcessSerialNumber psn;
1110         OSErr err;
1111         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
1112                            CGEventMaskBit(kCGEventLeftMouseUp)          |
1113                            CGEventMaskBit(kCGEventRightMouseDown)       |
1114                            CGEventMaskBit(kCGEventRightMouseUp)         |
1115                            CGEventMaskBit(kCGEventMouseMoved)           |
1116                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
1117                            CGEventMaskBit(kCGEventRightMouseDragged)    |
1118                            CGEventMaskBit(kCGEventOtherMouseDown)       |
1119                            CGEventMaskBit(kCGEventOtherMouseUp)         |
1120                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
1121                            CGEventMaskBit(kCGEventScrollWheel);
1122         CFRunLoopSourceRef source;
1123         void* appServices;
1124         OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1126         if (cursorClippingEventTap)
1127             return TRUE;
1129         // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1130         // framework with dlsym() because the Win32 function of the same name
1131         // obscures it.
1132         appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1133         if (!appServices)
1134             return FALSE;
1136         pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1137         if (!pGetCurrentProcess)
1138         {
1139             dlclose(appServices);
1140             return FALSE;
1141         }
1143         err = pGetCurrentProcess(&psn);
1144         dlclose(appServices);
1145         if (err != noErr)
1146             return FALSE;
1148         // We create an annotated session event tap rather than a process-specific
1149         // event tap because we need to programmatically move the cursor even when
1150         // mouse moves are directed to other processes.  We disable our tap when
1151         // other processes are active, but things like Exposé are handled by other
1152         // processes even when we remain active.
1153         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1154             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1155         if (!cursorClippingEventTap)
1156             return FALSE;
1158         CGEventTapEnable(cursorClippingEventTap, FALSE);
1160         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1161         if (!source)
1162         {
1163             CFRelease(cursorClippingEventTap);
1164             cursorClippingEventTap = NULL;
1165             return FALSE;
1166         }
1168         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1169         CFRelease(source);
1170         return TRUE;
1171     }
1173     - (BOOL) setCursorPosition:(CGPoint)pos
1174     {
1175         BOOL ret;
1177         if (clippingCursor)
1178         {
1179             [self clipCursorLocation:&pos];
1181             ret = [self warpCursorTo:&pos from:NULL];
1182             synthesizedLocation = pos;
1183             if (ret)
1184             {
1185                 // We want to discard mouse-move events that have already been
1186                 // through the event tap, because it's too late to account for
1187                 // the setting of the cursor position with them.  However, the
1188                 // events that may be queued with times after that but before
1189                 // the above warp can still be used.  So, use the last event
1190                 // tap event time so that -sendEvent: doesn't discard them.
1191                 lastSetCursorPositionTime = lastEventTapEventTime;
1192             }
1193         }
1194         else
1195         {
1196             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1197             if (ret)
1198             {
1199                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1201                 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1202                 // the mouse from the cursor position for 0.25 seconds.  This means
1203                 // that mouse movement during that interval doesn't move the cursor
1204                 // and events carry a constant location (the warped-to position)
1205                 // even though they have delta values.  This screws us up because
1206                 // the accumulated deltas we send to Wine don't match any eventual
1207                 // absolute position we send (like with a button press).  We can
1208                 // work around this by simply forcibly reassociating the mouse and
1209                 // cursor position.
1210                 CGAssociateMouseAndMouseCursorPosition(true);
1211             }
1212         }
1214         if (ret)
1215         {
1216             WineEventQueue* queue;
1218             // Discard all pending mouse move events.
1219             [eventQueuesLock lock];
1220             for (queue in eventQueues)
1221             {
1222                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1223                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1224                                        forWindow:nil];
1225                 [queue resetMouseEventPositions:pos];
1226             }
1227             [eventQueuesLock unlock];
1228         }
1230         return ret;
1231     }
1233     - (void) activateCursorClipping
1234     {
1235         if (clippingCursor)
1236         {
1237             CGEventTapEnable(cursorClippingEventTap, TRUE);
1238             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1239         }
1240     }
1242     - (void) deactivateCursorClipping
1243     {
1244         if (clippingCursor)
1245         {
1246             CGEventTapEnable(cursorClippingEventTap, FALSE);
1247             [warpRecords removeAllObjects];
1248             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1249         }
1250     }
1252     - (BOOL) startClippingCursor:(CGRect)rect
1253     {
1254         CGError err;
1256         if (!cursorClippingEventTap && ![self installEventTap])
1257             return FALSE;
1259         err = CGAssociateMouseAndMouseCursorPosition(false);
1260         if (err != kCGErrorSuccess)
1261             return FALSE;
1263         clippingCursor = TRUE;
1264         cursorClipRect = rect;
1265         if ([NSApp isActive])
1266             [self activateCursorClipping];
1268         return TRUE;
1269     }
1271     - (BOOL) stopClippingCursor
1272     {
1273         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1274         if (err != kCGErrorSuccess)
1275             return FALSE;
1277         [self deactivateCursorClipping];
1278         clippingCursor = FALSE;
1280         return TRUE;
1281     }
1283     - (BOOL) isKeyPressed:(uint16_t)keyCode
1284     {
1285         int bits = sizeof(pressedKeyCodes[0]) * 8;
1286         int index = keyCode / bits;
1287         uint32_t mask = 1 << (keyCode % bits);
1288         return (pressedKeyCodes[index] & mask) != 0;
1289     }
1291     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1292     {
1293         int bits = sizeof(pressedKeyCodes[0]) * 8;
1294         int index = keyCode / bits;
1295         uint32_t mask = 1 << (keyCode % bits);
1296         if (pressed)
1297             pressedKeyCodes[index] |= mask;
1298         else
1299             pressedKeyCodes[index] &= ~mask;
1300     }
1302     - (void) handleMouseMove:(NSEvent*)anEvent
1303     {
1304         WineWindow* targetWindow;
1305         BOOL drag = [anEvent type] != NSMouseMoved;
1307         if (mouseCaptureWindow)
1308             targetWindow = mouseCaptureWindow;
1309         else if (drag)
1310             targetWindow = (WineWindow*)[anEvent window];
1311         else
1312         {
1313             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1314                event indicates its window is the main window, even if the cursor is
1315                over a different window.  Find the actual WineWindow that is under the
1316                cursor and post the event as being for that window. */
1317             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1318             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1319             NSInteger windowUnderNumber;
1321             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1322                                   belowWindowWithWindowNumber:0];
1323             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1324         }
1326         if ([targetWindow isKindOfClass:[WineWindow class]])
1327         {
1328             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1329             macdrv_event* event;
1330             BOOL absolute;
1332             // If we recently warped the cursor (other than in our cursor-clipping
1333             // event tap), discard mouse move events until we see an event which is
1334             // later than that time.
1335             if (lastSetCursorPositionTime)
1336             {
1337                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1338                     return;
1340                 lastSetCursorPositionTime = 0;
1341                 forceNextMouseMoveAbsolute = TRUE;
1342             }
1344             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1345             {
1346                 absolute = TRUE;
1347                 forceNextMouseMoveAbsolute = FALSE;
1348             }
1349             else
1350             {
1351                 // Send absolute move events if the cursor is in the interior of
1352                 // its range.  Only send relative moves if the cursor is pinned to
1353                 // the boundaries of where it can go.  We compute the position
1354                 // that's one additional point in the direction of movement.  If
1355                 // that is outside of the clipping rect or desktop region (the
1356                 // union of the screen frames), then we figure the cursor would
1357                 // have moved outside if it could but it was pinned.
1358                 CGPoint computedPoint = point;
1359                 CGFloat deltaX = [anEvent deltaX];
1360                 CGFloat deltaY = [anEvent deltaY];
1362                 if (deltaX > 0.001)
1363                     computedPoint.x++;
1364                 else if (deltaX < -0.001)
1365                     computedPoint.x--;
1367                 if (deltaY > 0.001)
1368                     computedPoint.y++;
1369                 else if (deltaY < -0.001)
1370                     computedPoint.y--;
1372                 // Assume cursor is pinned for now
1373                 absolute = FALSE;
1374                 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1375                 {
1376                     const CGRect* rects;
1377                     NSUInteger count, i;
1379                     // Caches screenFrameCGRects if necessary
1380                     [self primaryScreenHeight];
1382                     rects = [screenFrameCGRects bytes];
1383                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1385                     for (i = 0; i < count; i++)
1386                     {
1387                         if (CGRectContainsPoint(rects[i], computedPoint))
1388                         {
1389                             absolute = TRUE;
1390                             break;
1391                         }
1392                     }
1393                 }
1394             }
1396             if (absolute)
1397             {
1398                 if (clippingCursor)
1399                     [self clipCursorLocation:&point];
1401                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1402                 event->mouse_moved.x = point.x;
1403                 event->mouse_moved.y = point.y;
1405                 mouseMoveDeltaX = 0;
1406                 mouseMoveDeltaY = 0;
1407             }
1408             else
1409             {
1410                 /* Add event delta to accumulated delta error */
1411                 /* deltaY is already flipped */
1412                 mouseMoveDeltaX += [anEvent deltaX];
1413                 mouseMoveDeltaY += [anEvent deltaY];
1415                 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1416                 event->mouse_moved.x = mouseMoveDeltaX;
1417                 event->mouse_moved.y = mouseMoveDeltaY;
1419                 /* Keep the remainder after integer truncation. */
1420                 mouseMoveDeltaX -= event->mouse_moved.x;
1421                 mouseMoveDeltaY -= event->mouse_moved.y;
1422             }
1424             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1425             {
1426                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1427                 event->mouse_moved.drag = drag;
1429                 [targetWindow.queue postEvent:event];
1430             }
1432             macdrv_release_event(event);
1434             lastTargetWindow = targetWindow;
1435         }
1436         else if (lastTargetWindow)
1437         {
1438             [[NSCursor arrowCursor] set];
1439             [self unhideCursor];
1440             lastTargetWindow = nil;
1441         }
1442     }
1444     - (void) handleMouseButton:(NSEvent*)theEvent
1445     {
1446         WineWindow* window = (WineWindow*)[theEvent window];
1447         NSEventType type = [theEvent type];
1448         BOOL broughtWindowForward = FALSE;
1450         if ([window isKindOfClass:[WineWindow class]] &&
1451             !window.disabled && !window.noActivate &&
1452             type == NSLeftMouseDown &&
1453             (([theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask| NSAlternateKeyMask | NSCommandKeyMask)) != NSCommandKeyMask))
1454         {
1455             NSWindowButton windowButton;
1457             broughtWindowForward = TRUE;
1459             /* Any left-click on our window anyplace other than the close or
1460                minimize buttons will bring it forward. */
1461             for (windowButton = NSWindowCloseButton;
1462                  windowButton <= NSWindowMiniaturizeButton;
1463                  windowButton++)
1464             {
1465                 NSButton* button = [window standardWindowButton:windowButton];
1466                 if (button)
1467                 {
1468                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1469                     if ([button mouse:point inRect:[button bounds]])
1470                     {
1471                         broughtWindowForward = FALSE;
1472                         break;
1473                     }
1474                 }
1475             }
1477             if (broughtWindowForward)
1478             {
1479                 // Clicking on a child window does not normally reorder it with
1480                 // respect to its siblings, but we want it to.  We have to do it
1481                 // manually.
1482                 NSWindow* parent = [window parentWindow];
1483                 [parent removeChildWindow:window];
1484                 [parent addChildWindow:window ordered:NSWindowAbove];
1485             }
1486         }
1488         if (mouseCaptureWindow)
1489             window = mouseCaptureWindow;
1491         if ([window isKindOfClass:[WineWindow class]])
1492         {
1493             BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1494             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1495             BOOL process;
1497             if (clippingCursor)
1498                 [self clipCursorLocation:&pt];
1500             if (pressed)
1501             {
1502                 if (mouseCaptureWindow)
1503                     process = TRUE;
1504                 else
1505                 {
1506                     // Test if the click was in the window's content area.
1507                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1508                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1509                     process = NSPointInRect(nspoint, contentRect);
1510                     if (process && [window styleMask] & NSResizableWindowMask)
1511                     {
1512                         // Ignore clicks in the grow box (resize widget).
1513                         HIPoint origin = { 0, 0 };
1514                         HIThemeGrowBoxDrawInfo info = { 0 };
1515                         HIRect bounds;
1516                         OSStatus status;
1518                         info.kind = kHIThemeGrowBoxKindNormal;
1519                         info.direction = kThemeGrowRight | kThemeGrowDown;
1520                         if ([window styleMask] & NSUtilityWindowMask)
1521                             info.size = kHIThemeGrowBoxSizeSmall;
1522                         else
1523                             info.size = kHIThemeGrowBoxSizeNormal;
1525                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1526                         if (status == noErr)
1527                         {
1528                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1529                                                         NSMinY(contentRect),
1530                                                         bounds.size.width,
1531                                                         bounds.size.height);
1532                             process = !NSPointInRect(nspoint, growBox);
1533                         }
1534                     }
1535                 }
1536                 if (process)
1537                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1538             }
1539             else
1540             {
1541                 NSEventType downType = type - 1;
1542                 NSUInteger downMask = NSEventMaskFromType(downType);
1543                 process = (unmatchedMouseDowns & downMask) != 0;
1544                 unmatchedMouseDowns &= ~downMask;
1545             }
1547             if (process)
1548             {
1549                 macdrv_event* event;
1551                 event = macdrv_create_event(MOUSE_BUTTON, window);
1552                 event->mouse_button.button = [theEvent buttonNumber];
1553                 event->mouse_button.pressed = pressed;
1554                 event->mouse_button.x = pt.x;
1555                 event->mouse_button.y = pt.y;
1556                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1558                 [window.queue postEvent:event];
1560                 macdrv_release_event(event);
1561             }
1562             else if (broughtWindowForward && ![window isKeyWindow])
1563                 [self windowGotFocus:window];
1564         }
1566         // Since mouse button events deliver absolute cursor position, the
1567         // accumulating delta from move events is invalidated.  Make sure
1568         // next mouse move event starts over from an absolute baseline.
1569         // Also, it's at least possible that the title bar widgets (e.g. close
1570         // button, etc.) could enter an internal event loop on a mouse down that
1571         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1572         // dragged events and, after that, any notion of the cursor position
1573         // computed from accumulating deltas would be wrong.
1574         forceNextMouseMoveAbsolute = TRUE;
1575     }
1577     - (void) handleScrollWheel:(NSEvent*)theEvent
1578     {
1579         WineWindow* window;
1581         if (mouseCaptureWindow)
1582             window = mouseCaptureWindow;
1583         else
1584             window = (WineWindow*)[theEvent window];
1586         if ([window isKindOfClass:[WineWindow class]])
1587         {
1588             CGEventRef cgevent = [theEvent CGEvent];
1589             CGPoint pt = CGEventGetLocation(cgevent);
1590             BOOL process;
1592             if (clippingCursor)
1593                 [self clipCursorLocation:&pt];
1595             if (mouseCaptureWindow)
1596                 process = TRUE;
1597             else
1598             {
1599                 // Only process the event if it was in the window's content area.
1600                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1601                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1602                 process = NSPointInRect(nspoint, contentRect);
1603             }
1605             if (process)
1606             {
1607                 macdrv_event* event;
1608                 CGFloat x, y;
1609                 BOOL continuous = FALSE;
1611                 event = macdrv_create_event(MOUSE_SCROLL, window);
1612                 event->mouse_scroll.x = pt.x;
1613                 event->mouse_scroll.y = pt.y;
1614                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1616                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1617                 {
1618                     continuous = TRUE;
1620                     /* Continuous scroll wheel events come from high-precision scrolling
1621                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1622                        For these, we can get more precise data from the CGEvent API. */
1623                     /* Axis 1 is vertical, axis 2 is horizontal. */
1624                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1625                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1626                 }
1627                 else
1628                 {
1629                     double pixelsPerLine = 10;
1630                     CGEventSourceRef source;
1632                     /* The non-continuous values are in units of "lines", not pixels. */
1633                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1634                     {
1635                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1636                         CFRelease(source);
1637                     }
1639                     x = pixelsPerLine * [theEvent deltaX];
1640                     y = pixelsPerLine * [theEvent deltaY];
1641                 }
1643                 /* Mac: negative is right or down, positive is left or up.
1644                    Win32: negative is left or down, positive is right or up.
1645                    So, negate the X scroll value to translate. */
1646                 x = -x;
1648                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1649                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1650                    6 times the pixel value. */
1651                 event->mouse_scroll.x_scroll = 6 * x;
1652                 event->mouse_scroll.y_scroll = 6 * y;
1654                 if (!continuous)
1655                 {
1656                     /* For non-continuous "clicky" wheels, if there was any motion, make
1657                        sure there was at least WHEEL_DELTA motion.  This is so, at slow
1658                        speeds where the system's acceleration curve is actually reducing the
1659                        scroll distance, the user is sure to get some action out of each click.
1660                        For example, this is important for rotating though weapons in a
1661                        first-person shooter. */
1662                     if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1663                         event->mouse_scroll.x_scroll = 120;
1664                     else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1665                         event->mouse_scroll.x_scroll = -120;
1667                     if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1668                         event->mouse_scroll.y_scroll = 120;
1669                     else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1670                         event->mouse_scroll.y_scroll = -120;
1671                 }
1673                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1674                     [window.queue postEvent:event];
1676                 macdrv_release_event(event);
1678                 // Since scroll wheel events deliver absolute cursor position, the
1679                 // accumulating delta from move events is invalidated.  Make sure next
1680                 // mouse move event starts over from an absolute baseline.
1681                 forceNextMouseMoveAbsolute = TRUE;
1682             }
1683         }
1684     }
1686     // Returns TRUE if the event was handled and caller should do nothing more
1687     // with it.  Returns FALSE if the caller should process it as normal and
1688     // then call -didSendEvent:.
1689     - (BOOL) handleEvent:(NSEvent*)anEvent
1690     {
1691         BOOL ret = FALSE;
1692         NSEventType type = [anEvent type];
1694         if (type == NSFlagsChanged)
1695             self.lastFlagsChanged = anEvent;
1696         else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1697                  type == NSRightMouseDragged || type == NSOtherMouseDragged)
1698         {
1699             [self handleMouseMove:anEvent];
1700             ret = mouseCaptureWindow != nil;
1701         }
1702         else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1703                  type == NSRightMouseDown || type == NSRightMouseUp ||
1704                  type == NSOtherMouseDown || type == NSOtherMouseUp)
1705         {
1706             [self handleMouseButton:anEvent];
1707             ret = mouseCaptureWindow != nil;
1708         }
1709         else if (type == NSScrollWheel)
1710         {
1711             [self handleScrollWheel:anEvent];
1712             ret = mouseCaptureWindow != nil;
1713         }
1714         else if (type == NSKeyUp)
1715         {
1716             uint16_t keyCode = [anEvent keyCode];
1717             if ([self isKeyPressed:keyCode])
1718             {
1719                 WineWindow* window = (WineWindow*)[anEvent window];
1720                 [self noteKey:keyCode pressed:FALSE];
1721                 if ([window isKindOfClass:[WineWindow class]])
1722                     [window postKeyEvent:anEvent];
1723             }
1724         }
1726         return ret;
1727     }
1729     - (void) didSendEvent:(NSEvent*)anEvent
1730     {
1731         NSEventType type = [anEvent type];
1733         if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1734         {
1735             NSUInteger modifiers = [anEvent modifierFlags];
1736             if ((modifiers & NSCommandKeyMask) &&
1737                 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
1738             {
1739                 // Command-Tab and Command-Shift-Tab would normally be intercepted
1740                 // by the system to switch applications.  If we're seeing it, it's
1741                 // presumably because we've captured the displays, preventing
1742                 // normal application switching.  Do it manually.
1743                 [self handleCommandTab];
1744             }
1745         }
1746     }
1748     - (void) setupObservations
1749     {
1750         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1751         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1752         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
1754         [nc addObserverForName:NSWindowDidBecomeKeyNotification
1755                         object:nil
1756                          queue:nil
1757                     usingBlock:^(NSNotification *note){
1758             NSWindow* window = [note object];
1759             [keyWindows removeObjectIdenticalTo:window];
1760             [keyWindows insertObject:window atIndex:0];
1761         }];
1763         [nc addObserverForName:NSWindowWillCloseNotification
1764                         object:nil
1765                          queue:[NSOperationQueue mainQueue]
1766                     usingBlock:^(NSNotification *note){
1767             NSWindow* window = [note object];
1768             [keyWindows removeObjectIdenticalTo:window];
1769             if (window == lastTargetWindow)
1770                 lastTargetWindow = nil;
1771             if (window == self.mouseCaptureWindow)
1772                 self.mouseCaptureWindow = nil;
1773             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
1774             {
1775                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
1776                     [self updateFullscreenWindows];
1777                 });
1778             }
1779         }];
1781         [nc addObserver:self
1782                selector:@selector(keyboardSelectionDidChange)
1783                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
1784                  object:nil];
1786         /* The above notification isn't sent unless the NSTextInputContext
1787            class has initialized itself.  Poke it. */
1788         [NSTextInputContext self];
1790         [wsnc addObserver:self
1791                  selector:@selector(activeSpaceDidChange)
1792                      name:NSWorkspaceActiveSpaceDidChangeNotification
1793                    object:nil];
1795         [nc addObserver:self
1796                selector:@selector(releaseMouseCapture)
1797                    name:NSMenuDidBeginTrackingNotification
1798                  object:nil];
1800         [dnc        addObserver:self
1801                        selector:@selector(releaseMouseCapture)
1802                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
1803                          object:nil
1804              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
1805     }
1807     - (BOOL) inputSourceIsInputMethod
1808     {
1809         if (!inputSourceIsInputMethodValid)
1810         {
1811             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
1812             if (inputSource)
1813             {
1814                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
1815                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
1816                 CFRelease(inputSource);
1817             }
1818             else
1819                 inputSourceIsInputMethod = FALSE;
1820             inputSourceIsInputMethodValid = TRUE;
1821         }
1823         return inputSourceIsInputMethod;
1824     }
1826     - (void) releaseMouseCapture
1827     {
1828         // This might be invoked on a background thread by the distributed
1829         // notification center.  Shunt it to the main thread.
1830         if (![NSThread isMainThread])
1831         {
1832             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
1833             return;
1834         }
1836         if (mouseCaptureWindow)
1837         {
1838             macdrv_event* event;
1840             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
1841             [mouseCaptureWindow.queue postEvent:event];
1842             macdrv_release_event(event);
1843         }
1844     }
1847     /*
1848      * ---------- NSApplicationDelegate methods ----------
1849      */
1850     - (void)applicationDidBecomeActive:(NSNotification *)notification
1851     {
1852         [self activateCursorClipping];
1854         [self updateFullscreenWindows];
1855         [self adjustWindowLevels:YES];
1857         if (beenActive && ![self frontWineWindow])
1858         {
1859             for (WineWindow* window in [NSApp windows])
1860             {
1861                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
1862                 {
1863                     [window deminiaturize:self];
1864                     break;
1865                 }
1866             }
1867         }
1868         beenActive = TRUE;
1870         // If a Wine process terminates abruptly while it has the display captured
1871         // and switched to a different resolution, Mac OS X will uncapture the
1872         // displays and switch their resolutions back.  However, the other Wine
1873         // processes won't have their notion of the desktop rect changed back.
1874         // This can lead them to refuse to draw or acknowledge clicks in certain
1875         // portions of their windows.
1876         //
1877         // To solve this, we synthesize a displays-changed event whenever we're
1878         // activated.  This will provoke a re-synchronization of Wine's notion of
1879         // the desktop rect with the actual state.
1880         [self sendDisplaysChanged:TRUE];
1882         // The cursor probably moved while we were inactive.  Accumulated mouse
1883         // movement deltas are invalidated.  Make sure the next mouse move event
1884         // starts over from an absolute baseline.
1885         forceNextMouseMoveAbsolute = TRUE;
1886     }
1888     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
1889     {
1890         primaryScreenHeightValid = FALSE;
1891         [self sendDisplaysChanged:FALSE];
1892         [self adjustWindowLevels];
1894         // When the display configuration changes, the cursor position may jump.
1895         // Accumulated mouse movement deltas are invalidated.  Make sure the next
1896         // mouse move event starts over from an absolute baseline.
1897         forceNextMouseMoveAbsolute = TRUE;
1898     }
1900     - (void)applicationDidResignActive:(NSNotification *)notification
1901     {
1902         macdrv_event* event;
1903         WineEventQueue* queue;
1905         [self invalidateGotFocusEvents];
1907         event = macdrv_create_event(APP_DEACTIVATED, nil);
1909         [eventQueuesLock lock];
1910         for (queue in eventQueues)
1911             [queue postEvent:event];
1912         [eventQueuesLock unlock];
1914         macdrv_release_event(event);
1916         [self releaseMouseCapture];
1917     }
1919     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
1920     {
1921         NSApplicationTerminateReply ret = NSTerminateNow;
1922         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
1923         NSAppleEventDescriptor* desc = [m currentAppleEvent];
1924         macdrv_event* event;
1925         WineEventQueue* queue;
1927         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
1928         event->deliver = 1;
1929         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
1930         {
1931             case kAELogOut:
1932             case kAEReallyLogOut:
1933                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
1934                 break;
1935             case kAEShowRestartDialog:
1936                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
1937                 break;
1938             case kAEShowShutdownDialog:
1939                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
1940                 break;
1941             default:
1942                 event->app_quit_requested.reason = QUIT_REASON_NONE;
1943                 break;
1944         }
1946         [eventQueuesLock lock];
1948         if ([eventQueues count])
1949         {
1950             for (queue in eventQueues)
1951                 [queue postEvent:event];
1952             ret = NSTerminateLater;
1953         }
1955         [eventQueuesLock unlock];
1957         macdrv_release_event(event);
1959         return ret;
1960     }
1962     - (void)applicationWillResignActive:(NSNotification *)notification
1963     {
1964         [self deactivateCursorClipping];
1966         [self adjustWindowLevels:NO];
1967     }
1969 /***********************************************************************
1970  *              PerformRequest
1972  * Run-loop-source perform callback.  Pull request blocks from the
1973  * array of queued requests and invoke them.
1974  */
1975 static void PerformRequest(void *info)
1977     WineApplicationController* controller = [WineApplicationController sharedController];
1979     for (;;)
1980     {
1981         __block dispatch_block_t block;
1983         dispatch_sync(controller->requestsManipQueue, ^{
1984             if ([controller->requests count])
1985             {
1986                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
1987                 [controller->requests removeObjectAtIndex:0];
1988             }
1989             else
1990                 block = nil;
1991         });
1993         if (!block)
1994             break;
1996         block();
1997         [block release];
1998     }
2001 /***********************************************************************
2002  *              OnMainThreadAsync
2004  * Run a block on the main thread asynchronously.
2005  */
2006 void OnMainThreadAsync(dispatch_block_t block)
2008     WineApplicationController* controller = [WineApplicationController sharedController];
2010     block = [block copy];
2011     dispatch_sync(controller->requestsManipQueue, ^{
2012         [controller->requests addObject:block];
2013     });
2014     [block release];
2015     CFRunLoopSourceSignal(controller->requestSource);
2016     CFRunLoopWakeUp(CFRunLoopGetMain());
2019 @end
2021 /***********************************************************************
2022  *              LogError
2023  */
2024 void LogError(const char* func, NSString* format, ...)
2026     va_list args;
2027     va_start(args, format);
2028     LogErrorv(func, format, args);
2029     va_end(args);
2032 /***********************************************************************
2033  *              LogErrorv
2034  */
2035 void LogErrorv(const char* func, NSString* format, va_list args)
2037     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2039     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2040     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2041     [message release];
2043     [pool release];
2046 /***********************************************************************
2047  *              macdrv_window_rejected_focus
2049  * Pass focus to the next window that hasn't already rejected this same
2050  * WINDOW_GOT_FOCUS event.
2051  */
2052 void macdrv_window_rejected_focus(const macdrv_event *event)
2054     OnMainThread(^{
2055         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2056     });
2059 /***********************************************************************
2060  *              macdrv_get_keyboard_layout
2062  * Returns the keyboard layout uchr data.
2063  */
2064 CFDataRef macdrv_copy_keyboard_layout(CGEventSourceKeyboardType* keyboard_type, int* is_iso)
2066     __block CFDataRef result = NULL;
2068     OnMainThread(^{
2069         TISInputSourceRef inputSource;
2071         inputSource = TISCopyCurrentKeyboardLayoutInputSource();
2072         if (inputSource)
2073         {
2074             CFDataRef uchr = TISGetInputSourceProperty(inputSource,
2075                                 kTISPropertyUnicodeKeyLayoutData);
2076             result = CFDataCreateCopy(NULL, uchr);
2077             CFRelease(inputSource);
2079             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2080             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2081         }
2082     });
2084     return result;
2087 /***********************************************************************
2088  *              macdrv_beep
2090  * Play the beep sound configured by the user in System Preferences.
2091  */
2092 void macdrv_beep(void)
2094     OnMainThreadAsync(^{
2095         NSBeep();
2096     });
2099 /***********************************************************************
2100  *              macdrv_set_display_mode
2101  */
2102 int macdrv_set_display_mode(const struct macdrv_display* display,
2103                             CGDisplayModeRef display_mode)
2105     __block int ret;
2107     OnMainThread(^{
2108         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2109     });
2111     return ret;
2114 /***********************************************************************
2115  *              macdrv_set_cursor
2117  * Set the cursor.
2119  * If name is non-NULL, it is a selector for a class method on NSCursor
2120  * identifying the cursor to set.  In that case, frames is ignored.  If
2121  * name is NULL, then frames is used.
2123  * frames is an array of dictionaries.  Each dictionary is a frame of
2124  * an animated cursor.  Under the key "image" is a CGImage for the
2125  * frame.  Under the key "duration" is a CFNumber time interval, in
2126  * seconds, for how long that frame is presented before proceeding to
2127  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2128  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2129  * This is the hot spot, measured in pixels down and to the right of the
2130  * top-left corner of the image.
2132  * If the array has exactly 1 element, the cursor is static, not
2133  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2134  */
2135 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2137     SEL sel;
2139     sel = NSSelectorFromString((NSString*)name);
2140     if (sel)
2141     {
2142         OnMainThreadAsync(^{
2143             WineApplicationController* controller = [WineApplicationController sharedController];
2144             NSCursor* cursor = [NSCursor performSelector:sel];
2145             [controller setCursorWithFrames:nil];
2146             [cursor set];
2147             [controller unhideCursor];
2148         });
2149     }
2150     else
2151     {
2152         NSArray* nsframes = (NSArray*)frames;
2153         if ([nsframes count])
2154         {
2155             OnMainThreadAsync(^{
2156                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2157             });
2158         }
2159         else
2160         {
2161             OnMainThreadAsync(^{
2162                 WineApplicationController* controller = [WineApplicationController sharedController];
2163                 [controller setCursorWithFrames:nil];
2164                 [controller hideCursor];
2165             });
2166         }
2167     }
2170 /***********************************************************************
2171  *              macdrv_get_cursor_position
2173  * Obtains the current cursor position.  Returns zero on failure,
2174  * non-zero on success.
2175  */
2176 int macdrv_get_cursor_position(CGPoint *pos)
2178     OnMainThread(^{
2179         NSPoint location = [NSEvent mouseLocation];
2180         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2181         *pos = NSPointToCGPoint(location);
2182     });
2184     return TRUE;
2187 /***********************************************************************
2188  *              macdrv_set_cursor_position
2190  * Sets the cursor position without generating events.  Returns zero on
2191  * failure, non-zero on success.
2192  */
2193 int macdrv_set_cursor_position(CGPoint pos)
2195     __block int ret;
2197     OnMainThread(^{
2198         ret = [[WineApplicationController sharedController] setCursorPosition:pos];
2199     });
2201     return ret;
2204 /***********************************************************************
2205  *              macdrv_clip_cursor
2207  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2208  * to or larger than the whole desktop region, the cursor is unclipped.
2209  * Returns zero on failure, non-zero on success.
2210  */
2211 int macdrv_clip_cursor(CGRect rect)
2213     __block int ret;
2215     OnMainThread(^{
2216         WineApplicationController* controller = [WineApplicationController sharedController];
2217         BOOL clipping = FALSE;
2219         if (!CGRectIsInfinite(rect))
2220         {
2221             NSRect nsrect = NSRectFromCGRect(rect);
2222             NSScreen* screen;
2224             /* Convert the rectangle from top-down coords to bottom-up. */
2225             [controller flipRect:&nsrect];
2227             clipping = FALSE;
2228             for (screen in [NSScreen screens])
2229             {
2230                 if (!NSContainsRect(nsrect, [screen frame]))
2231                 {
2232                     clipping = TRUE;
2233                     break;
2234                 }
2235             }
2236         }
2238         if (clipping)
2239             ret = [controller startClippingCursor:rect];
2240         else
2241             ret = [controller stopClippingCursor];
2242     });
2244     return ret;
2247 /***********************************************************************
2248  *              macdrv_set_application_icon
2250  * Set the application icon.  The images array contains CGImages.  If
2251  * there are more than one, then they represent different sizes or
2252  * color depths from the icon resource.  If images is NULL or empty,
2253  * restores the default application image.
2254  */
2255 void macdrv_set_application_icon(CFArrayRef images)
2257     NSArray* imageArray = (NSArray*)images;
2259     OnMainThreadAsync(^{
2260         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2261     });
2264 /***********************************************************************
2265  *              macdrv_quit_reply
2266  */
2267 void macdrv_quit_reply(int reply)
2269     OnMainThread(^{
2270         [NSApp replyToApplicationShouldTerminate:reply];
2271     });
2274 /***********************************************************************
2275  *              macdrv_using_input_method
2276  */
2277 int macdrv_using_input_method(void)
2279     __block BOOL ret;
2281     OnMainThread(^{
2282         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2283     });
2285     return ret;
2288 /***********************************************************************
2289  *              macdrv_set_mouse_capture_window
2290  */
2291 void macdrv_set_mouse_capture_window(macdrv_window window)
2293     WineWindow* w = (WineWindow*)window;
2295     OnMainThread(^{
2296         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2297     });