server: Use longer int in bitshift operation (Coverity).
[wine.git] / dlls / winemac.drv / cocoa_app.m
blobdff69772f8dfe4e74fff5ac34582266465b5d4b2
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) NSCursor* cursor;
84 @property (retain, nonatomic) NSImage* applicationIcon;
85 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
86 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
88     - (void) setupObservations;
89     - (void) applicationDidBecomeActive:(NSNotification *)notification;
91     static void PerformRequest(void *info);
93 @end
96 @implementation WineApplicationController
98     @synthesize keyboardType, lastFlagsChanged;
99     @synthesize applicationIcon;
100     @synthesize cursorFrames, cursorTimer, cursor;
101     @synthesize mouseCaptureWindow;
103     + (void) initialize
104     {
105         if (self == [WineApplicationController class])
106         {
107             NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
108                                       @"", @"NSQuotedKeystrokeBinding",
109                                       @"", @"NSRepeatCountBinding",
110                                       [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
111                                       nil];
112             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
113         }
114     }
116     + (WineApplicationController*) sharedController
117     {
118         static WineApplicationController* sharedController;
119         static dispatch_once_t once;
121         dispatch_once(&once, ^{
122             sharedController = [[self alloc] init];
123         });
125         return sharedController;
126     }
128     - (id) init
129     {
130         self = [super init];
131         if (self != nil)
132         {
133             CFRunLoopSourceContext context = { 0 };
134             context.perform = PerformRequest;
135             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
136             if (!requestSource)
137             {
138                 [self release];
139                 return nil;
140             }
141             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
142             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
144             requests =  [[NSMutableArray alloc] init];
145             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
147             eventQueues = [[NSMutableArray alloc] init];
148             eventQueuesLock = [[NSLock alloc] init];
150             keyWindows = [[NSMutableArray alloc] init];
152             originalDisplayModes = [[NSMutableDictionary alloc] init];
153             latentDisplayModes = [[NSMutableDictionary alloc] init];
155             warpRecords = [[NSMutableArray alloc] init];
157             windowsBeingDragged = [[NSMutableSet alloc] init];
159             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
160                 !keyWindows || !originalDisplayModes || !latentDisplayModes || !warpRecords)
161             {
162                 [self release];
163                 return nil;
164             }
166             [self setupObservations];
168             keyboardType = LMGetKbdType();
170             if ([NSApp isActive])
171                 [self applicationDidBecomeActive:nil];
172         }
173         return self;
174     }
176     - (void) dealloc
177     {
178         [windowsBeingDragged release];
179         [cursor release];
180         [screenFrameCGRects release];
181         [applicationIcon release];
182         [warpRecords release];
183         [cursorTimer release];
184         [cursorFrames release];
185         [latentDisplayModes release];
186         [originalDisplayModes release];
187         [keyWindows release];
188         [eventQueues release];
189         [eventQueuesLock release];
190         if (requestsManipQueue) dispatch_release(requestsManipQueue);
191         [requests release];
192         if (requestSource)
193         {
194             CFRunLoopSourceInvalidate(requestSource);
195             CFRelease(requestSource);
196         }
197         [super dealloc];
198     }
200     - (void) transformProcessToForeground
201     {
202         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
203         {
204             NSMenu* mainMenu;
205             NSMenu* submenu;
206             NSString* bundleName;
207             NSString* title;
208             NSMenuItem* item;
210             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
211             [NSApp activateIgnoringOtherApps:YES];
213             mainMenu = [[[NSMenu alloc] init] autorelease];
215             // Application menu
216             submenu = [[[NSMenu alloc] initWithTitle:@"Wine"] autorelease];
217             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
219             if ([bundleName length])
220                 title = [NSString stringWithFormat:@"Hide %@", bundleName];
221             else
222                 title = @"Hide";
223             item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
225             item = [submenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
226             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
228             item = [submenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
230             [submenu addItem:[NSMenuItem separatorItem]];
232             if ([bundleName length])
233                 title = [NSString stringWithFormat:@"Quit %@", bundleName];
234             else
235                 title = @"Quit";
236             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
237             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
238             item = [[[NSMenuItem alloc] init] autorelease];
239             [item setTitle:@"Wine"];
240             [item setSubmenu:submenu];
241             [mainMenu addItem:item];
243             // Window menu
244             submenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
245             [submenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@""];
246             [submenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""];
247             if ([NSWindow instancesRespondToSelector:@selector(toggleFullScreen:)])
248             {
249                 item = [submenu addItemWithTitle:@"Enter Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"];
250                 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask];
251             }
252             [submenu addItem:[NSMenuItem separatorItem]];
253             [submenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""];
254             item = [[[NSMenuItem alloc] init] autorelease];
255             [item setTitle:@"Window"];
256             [item setSubmenu:submenu];
257             [mainMenu addItem:item];
259             [NSApp setMainMenu:mainMenu];
260             [NSApp setWindowsMenu:submenu];
262             [NSApp setApplicationIconImage:self.applicationIcon];
263         }
264     }
266     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
267     {
268         PerformRequest(NULL);
270         do
271         {
272             if (processEvents)
273             {
274                 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
275                 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
276                                                     untilDate:timeout
277                                                        inMode:NSDefaultRunLoopMode
278                                                       dequeue:YES];
279                 if (event)
280                     [NSApp sendEvent:event];
281                 [pool release];
282             }
283             else
284                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
285         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
287         return *done;
288     }
290     - (BOOL) registerEventQueue:(WineEventQueue*)queue
291     {
292         [eventQueuesLock lock];
293         [eventQueues addObject:queue];
294         [eventQueuesLock unlock];
295         return TRUE;
296     }
298     - (void) unregisterEventQueue:(WineEventQueue*)queue
299     {
300         [eventQueuesLock lock];
301         [eventQueues removeObjectIdenticalTo:queue];
302         [eventQueuesLock unlock];
303     }
305     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
306     {
307         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
308     }
310     - (double) ticksForEventTime:(NSTimeInterval)eventTime
311     {
312         return (eventTime + eventTimeAdjustment) * 1000;
313     }
315     /* Invalidate old focus offers across all queues. */
316     - (void) invalidateGotFocusEvents
317     {
318         WineEventQueue* queue;
320         windowFocusSerial++;
322         [eventQueuesLock lock];
323         for (queue in eventQueues)
324         {
325             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
326                                    forWindow:nil];
327         }
328         [eventQueuesLock unlock];
329     }
331     - (void) windowGotFocus:(WineWindow*)window
332     {
333         macdrv_event* event;
335         [self invalidateGotFocusEvents];
337         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
338         event->window_got_focus.serial = windowFocusSerial;
339         if (triedWindows)
340             event->window_got_focus.tried_windows = [triedWindows retain];
341         else
342             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
343         [window.queue postEvent:event];
344         macdrv_release_event(event);
345     }
347     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
348     {
349         if (event->window_got_focus.serial == windowFocusSerial)
350         {
351             NSMutableArray* windows = [keyWindows mutableCopy];
352             NSNumber* windowNumber;
353             WineWindow* window;
355             for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
356             {
357                 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
358                 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
359                     ![windows containsObject:window])
360                     [windows addObject:window];
361             }
363             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
364             [triedWindows addObject:(WineWindow*)event->window];
365             for (window in windows)
366             {
367                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
368                 {
369                     [window makeKeyWindow];
370                     break;
371                 }
372             }
373             triedWindows = nil;
374             [windows release];
375         }
376     }
378     - (void) keyboardSelectionDidChange
379     {
380         TISInputSourceRef inputSourceLayout;
382         inputSourceIsInputMethodValid = FALSE;
384         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
385         if (inputSourceLayout)
386         {
387             CFDataRef uchr;
388             uchr = TISGetInputSourceProperty(inputSourceLayout,
389                     kTISPropertyUnicodeKeyLayoutData);
390             if (uchr)
391             {
392                 macdrv_event* event;
393                 WineEventQueue* queue;
395                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
396                 event->keyboard_changed.keyboard_type = self.keyboardType;
397                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
398                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
399                 event->keyboard_changed.input_source = TISCopyCurrentKeyboardInputSource();
401                 if (event->keyboard_changed.uchr)
402                 {
403                     [eventQueuesLock lock];
405                     for (queue in eventQueues)
406                         [queue postEvent:event];
408                     [eventQueuesLock unlock];
409                 }
411                 macdrv_release_event(event);
412             }
414             CFRelease(inputSourceLayout);
415         }
416     }
418     - (void) enabledKeyboardInputSourcesChanged
419     {
420         macdrv_layout_list_needs_update = TRUE;
421     }
423     - (CGFloat) primaryScreenHeight
424     {
425         if (!primaryScreenHeightValid)
426         {
427             NSArray* screens = [NSScreen screens];
428             NSUInteger count = [screens count];
429             if (count)
430             {
431                 NSUInteger size;
432                 CGRect* rect;
433                 NSScreen* screen;
435                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
436                 primaryScreenHeightValid = TRUE;
438                 size = count * sizeof(CGRect);
439                 if (!screenFrameCGRects)
440                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
441                 else
442                     [screenFrameCGRects setLength:size];
444                 rect = [screenFrameCGRects mutableBytes];
445                 for (screen in screens)
446                 {
447                     CGRect temp = NSRectToCGRect([screen frame]);
448                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
449                     *rect++ = temp;
450                 }
451             }
452             else
453                 return 1280; /* arbitrary value */
454         }
456         return primaryScreenHeight;
457     }
459     - (NSPoint) flippedMouseLocation:(NSPoint)point
460     {
461         /* This relies on the fact that Cocoa's mouse location points are
462            actually off by one (precisely because they were flipped from
463            Quartz screen coordinates using this same technique). */
464         point.y = [self primaryScreenHeight] - point.y;
465         return point;
466     }
468     - (void) flipRect:(NSRect*)rect
469     {
470         // We don't use -primaryScreenHeight here so there's no chance of having
471         // out-of-date cached info.  This method is called infrequently enough
472         // that getting the screen height each time is not prohibitively expensive.
473         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
474     }
476     - (WineWindow*) frontWineWindow
477     {
478         NSNumber* windowNumber;
479         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
480         {
481             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
482             if ([window isKindOfClass:[WineWindow class]] && [window screen])
483                 return (WineWindow*)window;
484         }
486         return nil;
487     }
489     - (void) adjustWindowLevels:(BOOL)active
490     {
491         NSArray* windowNumbers;
492         NSMutableArray* wineWindows;
493         NSNumber* windowNumber;
494         NSUInteger nextFloatingIndex = 0;
495         __block NSInteger maxLevel = NSIntegerMin;
496         __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
497         __block WineWindow* prev = nil;
498         WineWindow* window;
500         if ([NSApp isHidden]) return;
502         windowNumbers = [NSWindow windowNumbersWithOptions:0];
503         wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
505         // For the most part, we rely on the window server's ordering of the windows
506         // to be authoritative.  The one exception is if the "floating" property of
507         // one of the windows has been changed, it may be in the wrong level and thus
508         // in the order.  This method is what's supposed to fix that up.  So build
509         // a list of Wine windows sorted first by floating-ness and then by order
510         // as indicated by the window server.
511         for (windowNumber in windowNumbers)
512         {
513             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
514             if ([window isKindOfClass:[WineWindow class]])
515             {
516                 if (window.floating)
517                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
518                 else
519                     [wineWindows addObject:window];
520             }
521         }
523         NSDisableScreenUpdates();
525         // Go from back to front so that all windows in front of one which is
526         // elevated for full-screen are also elevated.
527         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
528                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
529             WineWindow* window = (WineWindow*)obj;
530             NSInteger origLevel = [window level];
531             NSInteger newLevel = [window minimumLevelForActive:active];
533             if (newLevel < maxLevel)
534                 newLevel = maxLevel;
535             else
536                 maxLevel = newLevel;
538             if (!window.floating && maxNonfloatingLevel < newLevel)
539                 maxNonfloatingLevel = newLevel;
541             if (newLevel != origLevel)
542             {
543                 [window setLevel:newLevel];
545                 // -setLevel: puts the window at the front of its new level.  If
546                 // we decreased the level, that's good (it was in front of that
547                 // level before, so it should still be now).  But if we increased
548                 // the level, the window should be toward the back (but still
549                 // ahead of the previous windows we did this to).
550                 if (origLevel < newLevel)
551                 {
552                     if (prev)
553                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
554                     else
555                         [window orderBack:nil];
556                 }
557             }
559             prev = window;
560         }];
562         NSEnableScreenUpdates();
564         [wineWindows release];
566         // The above took care of the visible windows on the current space.  That
567         // leaves windows on other spaces, minimized windows, and windows which
568         // are not ordered in.  We want to leave windows on other spaces alone
569         // so the space remains just as they left it (when viewed in Exposé or
570         // Mission Control, for example).  We'll adjust the window levels again
571         // after we switch to another space, anyway.  Windows which aren't
572         // ordered in will be handled when we order them in.  Minimized windows
573         // on the current space should be set to the level they would have gotten
574         // if they were at the front of the windows with the same floating-ness,
575         // because that's where they'll go if/when they are unminimized.  Again,
576         // for good measure we'll adjust window levels again when a window is
577         // unminimized, too.
578         for (window in [NSApp windows])
579         {
580             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
581                 [window isOnActiveSpace])
582             {
583                 NSInteger origLevel = [window level];
584                 NSInteger newLevel = [window minimumLevelForActive:YES];
585                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
587                 if (newLevel < maxLevelForType)
588                     newLevel = maxLevelForType;
590                 if (newLevel != origLevel)
591                     [window setLevel:newLevel];
592             }
593         }
594     }
596     - (void) adjustWindowLevels
597     {
598         [self adjustWindowLevels:[NSApp isActive]];
599     }
601     - (void) updateFullscreenWindows
602     {
603         if (capture_displays_for_fullscreen && [NSApp isActive])
604         {
605             BOOL anyFullscreen = FALSE;
606             NSNumber* windowNumber;
607             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
608             {
609                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
610                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
611                 {
612                     anyFullscreen = TRUE;
613                     break;
614                 }
615             }
617             if (anyFullscreen)
618             {
619                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
620                     displaysCapturedForFullscreen = TRUE;
621             }
622             else if (displaysCapturedForFullscreen)
623             {
624                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
625                     displaysCapturedForFullscreen = FALSE;
626             }
627         }
628     }
630     - (void) activeSpaceDidChange
631     {
632         [self updateFullscreenWindows];
633         [self adjustWindowLevels];
634     }
636     - (void) sendDisplaysChanged:(BOOL)activating
637     {
638         macdrv_event* event;
639         WineEventQueue* queue;
641         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
642         event->displays_changed.activating = activating;
644         [eventQueuesLock lock];
646         // If we're activating, then we just need one of our threads to get the
647         // event, so it can send it directly to the desktop window.  Otherwise,
648         // we need all of the threads to get it because we don't know which owns
649         // the desktop window and only that one will do anything with it.
650         if (activating) event->deliver = 1;
652         for (queue in eventQueues)
653             [queue postEvent:event];
654         [eventQueuesLock unlock];
656         macdrv_release_event(event);
657     }
659     // We can compare two modes directly using CFEqual, but that may require that
660     // they are identical to a level that we don't need.  In particular, when the
661     // OS switches between the integrated and discrete GPUs, the set of display
662     // modes can change in subtle ways.  We're interested in whether two modes
663     // match in their most salient features, even if they aren't identical.
664     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
665     {
666         NSString *encoding1, *encoding2;
667         uint32_t ioflags1, ioflags2, different;
668         double refresh1, refresh2;
670         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
671         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
673         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
674         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
675         if (![encoding1 isEqualToString:encoding2]) return FALSE;
677         ioflags1 = CGDisplayModeGetIOFlags(mode1);
678         ioflags2 = CGDisplayModeGetIOFlags(mode2);
679         different = ioflags1 ^ ioflags2;
680         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
681                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
682             return FALSE;
684         refresh1 = CGDisplayModeGetRefreshRate(mode1);
685         if (refresh1 == 0) refresh1 = 60;
686         refresh2 = CGDisplayModeGetRefreshRate(mode2);
687         if (refresh2 == 0) refresh2 = 60;
688         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
690         return TRUE;
691     }
693     - (CGDisplayModeRef)modeMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
694     {
695         CGDisplayModeRef ret = NULL;
696         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, NULL) autorelease];
697         for (id candidateModeObject in modes)
698         {
699             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
700             if ([self mode:candidateMode matchesMode:mode])
701             {
702                 ret = candidateMode;
703                 break;
704             }
705         }
706         return ret;
707     }
709     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
710     {
711         BOOL ret = FALSE;
712         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
713         CGDisplayModeRef originalMode;
715         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
717         if (originalMode && [self mode:mode matchesMode:originalMode])
718         {
719             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
720             {
721                 CGRestorePermanentDisplayConfiguration();
722                 if (!displaysCapturedForFullscreen)
723                     CGReleaseAllDisplays();
724                 [originalDisplayModes removeAllObjects];
725                 ret = TRUE;
726             }
727             else // ... otherwise, try to restore just the one display
728             {
729                 mode = [self modeMatchingMode:mode forDisplay:displayID];
730                 if (mode && CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
731                 {
732                     [originalDisplayModes removeObjectForKey:displayIDKey];
733                     ret = TRUE;
734                 }
735             }
736         }
737         else
738         {
739             BOOL active = [NSApp isActive];
740             CGDisplayModeRef currentMode;
742             currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
743             if (!currentMode)
744                 currentMode = CGDisplayCopyDisplayMode(displayID);
745             if (!currentMode) // Invalid display ID
746                 return FALSE;
748             if ([self mode:mode matchesMode:currentMode]) // Already there!
749             {
750                 CGDisplayModeRelease(currentMode);
751                 return TRUE;
752             }
754             CGDisplayModeRelease(currentMode);
755             currentMode = NULL;
757             mode = [self modeMatchingMode:mode forDisplay:displayID];
758             if (!mode)
759                 return FALSE;
761             if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
762                 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
763             {
764                 if (active)
765                 {
766                     // If we get here, we have the displays captured.  If we don't
767                     // know the original mode of the display, the current mode must
768                     // be the original.  We should re-query the current mode since
769                     // another process could have changed it between when we last
770                     // checked and when we captured the displays.
771                     if (!originalMode)
772                         originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
774                     if (originalMode)
775                         ret = (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr);
776                     if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
777                         [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
778                     else if (![originalDisplayModes count])
779                     {
780                         CGRestorePermanentDisplayConfiguration();
781                         if (!displaysCapturedForFullscreen)
782                             CGReleaseAllDisplays();
783                     }
785                     if (currentMode)
786                         CGDisplayModeRelease(currentMode);
787                 }
788                 else
789                 {
790                     [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
791                     ret = TRUE;
792                 }
793             }
794         }
796         if (ret)
797             [self adjustWindowLevels];
799         return ret;
800     }
802     - (BOOL) areDisplaysCaptured
803     {
804         return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
805     }
807     - (void) updateCursor:(BOOL)force
808     {
809         if (force || lastTargetWindow)
810         {
811             if (clientWantsCursorHidden && !cursorHidden)
812             {
813                 [NSCursor hide];
814                 cursorHidden = TRUE;
815             }
817             if (!cursorIsCurrent)
818             {
819                 [cursor set];
820                 cursorIsCurrent = TRUE;
821             }
823             if (!clientWantsCursorHidden && cursorHidden)
824             {
825                 [NSCursor unhide];
826                 cursorHidden = FALSE;
827             }
828         }
829         else
830         {
831             if (cursorIsCurrent)
832             {
833                 [[NSCursor arrowCursor] set];
834                 cursorIsCurrent = FALSE;
835             }
836             if (cursorHidden)
837             {
838                 [NSCursor unhide];
839                 cursorHidden = FALSE;
840             }
841         }
842     }
844     - (void) hideCursor
845     {
846         if (!clientWantsCursorHidden)
847         {
848             clientWantsCursorHidden = TRUE;
849             [self updateCursor:TRUE];
850         }
851     }
853     - (void) unhideCursor
854     {
855         if (clientWantsCursorHidden)
856         {
857             clientWantsCursorHidden = FALSE;
858             [self updateCursor:FALSE];
859         }
860     }
862     - (void) setCursor:(NSCursor*)newCursor
863     {
864         if (newCursor != cursor)
865         {
866             [cursor release];
867             cursor = [newCursor retain];
868             cursorIsCurrent = FALSE;
869             [self updateCursor:FALSE];
870         }
871     }
873     - (void) setCursor
874     {
875         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
876         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
877         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
878         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
879         CGPoint hotSpot;
881         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
882             hotSpot = CGPointZero;
883         self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
884         [image release];
885         [self unhideCursor];
886     }
888     - (void) nextCursorFrame:(NSTimer*)theTimer
889     {
890         NSDictionary* frame;
891         NSTimeInterval duration;
892         NSDate* date;
894         cursorFrame++;
895         if (cursorFrame >= [cursorFrames count])
896             cursorFrame = 0;
897         [self setCursor];
899         frame = [cursorFrames objectAtIndex:cursorFrame];
900         duration = [[frame objectForKey:@"duration"] doubleValue];
901         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
902         [cursorTimer setFireDate:date];
903     }
905     - (void) setCursorWithFrames:(NSArray*)frames
906     {
907         if (self.cursorFrames == frames)
908             return;
910         self.cursorFrames = frames;
911         cursorFrame = 0;
912         [cursorTimer invalidate];
913         self.cursorTimer = nil;
915         if ([frames count])
916         {
917             if ([frames count] > 1)
918             {
919                 NSDictionary* frame = [frames objectAtIndex:0];
920                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
921                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
922                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
923                                                              interval:1000000
924                                                                target:self
925                                                              selector:@selector(nextCursorFrame:)
926                                                              userInfo:nil
927                                                               repeats:YES] autorelease];
928                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
929             }
931             [self setCursor];
932         }
933     }
935     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
936     {
937         NSImage* nsimage = nil;
939         if ([images count])
940         {
941             NSSize bestSize = NSZeroSize;
942             id image;
944             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
946             for (image in images)
947             {
948                 CGImageRef cgimage = (CGImageRef)image;
949                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
950                 if (imageRep)
951                 {
952                     NSSize size = [imageRep size];
954                     [nsimage addRepresentation:imageRep];
955                     [imageRep release];
957                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
958                         bestSize = size;
959                 }
960             }
962             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
963                 [nsimage setSize:bestSize];
964             else
965                 nsimage = nil;
966         }
968         self.applicationIcon = nsimage;
969     }
971     - (void) handleCommandTab
972     {
973         if ([NSApp isActive])
974         {
975             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
976             NSRunningApplication* app;
977             NSRunningApplication* otherValidApp = nil;
979             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
980             {
981                 NSNumber* displayID;
982                 for (displayID in originalDisplayModes)
983                 {
984                     CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
985                     [latentDisplayModes setObject:(id)mode forKey:displayID];
986                     CGDisplayModeRelease(mode);
987                 }
989                 CGRestorePermanentDisplayConfiguration();
990                 CGReleaseAllDisplays();
991                 [originalDisplayModes removeAllObjects];
992                 displaysCapturedForFullscreen = FALSE;
993             }
995             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
996             {
997                 if (![app isEqual:thisApp] && !app.terminated &&
998                     app.activationPolicy == NSApplicationActivationPolicyRegular)
999                 {
1000                     if (!app.hidden)
1001                     {
1002                         // There's another visible app.  Just hide ourselves and let
1003                         // the system activate the other app.
1004                         [NSApp hide:self];
1005                         return;
1006                     }
1008                     if (!otherValidApp)
1009                         otherValidApp = app;
1010                 }
1011             }
1013             // Didn't find a visible GUI app.  Try the Finder or, if that's not
1014             // running, the first hidden GUI app.  If even that doesn't work, we
1015             // just fail to switch and remain the active app.
1016             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1017             if (!app) app = otherValidApp;
1018             [app unhide];
1019             [app activateWithOptions:0];
1020         }
1021     }
1023     /*
1024      * ---------- Cursor clipping methods ----------
1025      *
1026      * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1027      * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1028      * equivalent: CGAssociateMouseAndMouseCursorPosition(false).  For the
1029      * general case, we leverage that.  We disassociate mouse movements from
1030      * the cursor position and then move the cursor manually, keeping it within
1031      * the clipping rectangle.
1032      *
1033      * Moving the cursor manually isn't enough.  We need to modify the event
1034      * stream so that the events have the new location, too.  We need to do
1035      * this at a point before the events enter Cocoa, so that Cocoa will assign
1036      * the correct window to the event.  So, we install a Quartz event tap to
1037      * do that.
1038      *
1039      * Also, there's a complication when we move the cursor.  We use
1040      * CGWarpMouseCursorPosition().  That doesn't generate mouse movement
1041      * events, but the change of cursor position is incorporated into the
1042      * deltas of the next mouse move event.  When the mouse is disassociated
1043      * from the cursor position, we need the deltas to only reflect actual
1044      * device movement, not programmatic changes.  So, the event tap cancels
1045      * out the change caused by our calls to CGWarpMouseCursorPosition().
1046      */
1047     - (void) clipCursorLocation:(CGPoint*)location
1048     {
1049         if (location->x < CGRectGetMinX(cursorClipRect))
1050             location->x = CGRectGetMinX(cursorClipRect);
1051         if (location->y < CGRectGetMinY(cursorClipRect))
1052             location->y = CGRectGetMinY(cursorClipRect);
1053         if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1054             location->x = CGRectGetMaxX(cursorClipRect) - 1;
1055         if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1056             location->y = CGRectGetMaxY(cursorClipRect) - 1;
1057     }
1059     - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1060     {
1061         CGPoint oldLocation;
1063         if (currentLocation)
1064             oldLocation = *currentLocation;
1065         else
1066             oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1068         if (!CGPointEqualToPoint(oldLocation, *newLocation))
1069         {
1070             WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1071             CGError err;
1073             warpRecord.from = oldLocation;
1074             warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1076             /* Actually move the cursor. */
1077             err = CGWarpMouseCursorPosition(*newLocation);
1078             if (err != kCGErrorSuccess)
1079                 return FALSE;
1081             warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1082             *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1084             if (!CGPointEqualToPoint(oldLocation, *newLocation))
1085             {
1086                 warpRecord.to = *newLocation;
1087                 [warpRecords addObject:warpRecord];
1088             }
1089         }
1091         return TRUE;
1092     }
1094     - (BOOL) isMouseMoveEventType:(CGEventType)type
1095     {
1096         switch(type)
1097         {
1098         case kCGEventMouseMoved:
1099         case kCGEventLeftMouseDragged:
1100         case kCGEventRightMouseDragged:
1101         case kCGEventOtherMouseDragged:
1102             return TRUE;
1103         }
1105         return FALSE;
1106     }
1108     - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1109     {
1110         int warpsFinished = 0;
1111         for (WarpRecord* warpRecord in warpRecords)
1112         {
1113             if (warpRecord.timeAfter < eventTime ||
1114                 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1115                 warpsFinished++;
1116             else
1117                 break;
1118         }
1120         return warpsFinished;
1121     }
1123     - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1124                                 type:(CGEventType)type
1125                                event:(CGEventRef)event
1126     {
1127         CGEventTimestamp eventTime;
1128         CGPoint eventLocation, cursorLocation;
1130         if (type == kCGEventTapDisabledByUserInput)
1131             return event;
1132         if (type == kCGEventTapDisabledByTimeout)
1133         {
1134             CGEventTapEnable(cursorClippingEventTap, TRUE);
1135             return event;
1136         }
1138         if (!clippingCursor)
1139             return event;
1141         eventTime = CGEventGetTimestamp(event);
1142         lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1144         eventLocation = CGEventGetLocation(event);
1146         cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1148         if ([self isMouseMoveEventType:type])
1149         {
1150             double deltaX, deltaY;
1151             int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1152             int i;
1154             deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1155             deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1157             for (i = 0; i < warpsFinished; i++)
1158             {
1159                 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1160                 deltaX -= warpRecord.to.x - warpRecord.from.x;
1161                 deltaY -= warpRecord.to.y - warpRecord.from.y;
1162                 [warpRecords removeObjectAtIndex:0];
1163             }
1165             if (warpsFinished)
1166             {
1167                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1168                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1169             }
1171             synthesizedLocation.x += deltaX;
1172             synthesizedLocation.y += deltaY;
1173         }
1175         // If the event is destined for another process, don't clip it.  This may
1176         // happen if the user activates Exposé or Mission Control.  In that case,
1177         // our app does not resign active status, so clipping is still in effect,
1178         // but the cursor should not actually be clipped.
1179         //
1180         // In addition, the fact that mouse moves may have been delivered to a
1181         // different process means we have to treat the next one we receive as
1182         // absolute rather than relative.
1183         if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1184             [self clipCursorLocation:&synthesizedLocation];
1185         else
1186             lastSetCursorPositionTime = lastEventTapEventTime;
1188         [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1189         if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1190             CGEventSetLocation(event, synthesizedLocation);
1192         return event;
1193     }
1195     CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1196                                        CGEventRef event, void *refcon)
1197     {
1198         WineApplicationController* controller = refcon;
1199         return [controller eventTapWithProxy:proxy type:type event:event];
1200     }
1202     - (BOOL) installEventTap
1203     {
1204         ProcessSerialNumber psn;
1205         OSErr err;
1206         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
1207                            CGEventMaskBit(kCGEventLeftMouseUp)          |
1208                            CGEventMaskBit(kCGEventRightMouseDown)       |
1209                            CGEventMaskBit(kCGEventRightMouseUp)         |
1210                            CGEventMaskBit(kCGEventMouseMoved)           |
1211                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
1212                            CGEventMaskBit(kCGEventRightMouseDragged)    |
1213                            CGEventMaskBit(kCGEventOtherMouseDown)       |
1214                            CGEventMaskBit(kCGEventOtherMouseUp)         |
1215                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
1216                            CGEventMaskBit(kCGEventScrollWheel);
1217         CFRunLoopSourceRef source;
1218         void* appServices;
1219         OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1221         if (cursorClippingEventTap)
1222             return TRUE;
1224         // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1225         // framework with dlsym() because the Win32 function of the same name
1226         // obscures it.
1227         appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1228         if (!appServices)
1229             return FALSE;
1231         pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1232         if (!pGetCurrentProcess)
1233         {
1234             dlclose(appServices);
1235             return FALSE;
1236         }
1238         err = pGetCurrentProcess(&psn);
1239         dlclose(appServices);
1240         if (err != noErr)
1241             return FALSE;
1243         // We create an annotated session event tap rather than a process-specific
1244         // event tap because we need to programmatically move the cursor even when
1245         // mouse moves are directed to other processes.  We disable our tap when
1246         // other processes are active, but things like Exposé are handled by other
1247         // processes even when we remain active.
1248         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1249             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1250         if (!cursorClippingEventTap)
1251             return FALSE;
1253         CGEventTapEnable(cursorClippingEventTap, FALSE);
1255         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1256         if (!source)
1257         {
1258             CFRelease(cursorClippingEventTap);
1259             cursorClippingEventTap = NULL;
1260             return FALSE;
1261         }
1263         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1264         CFRelease(source);
1265         return TRUE;
1266     }
1268     - (BOOL) setCursorPosition:(CGPoint)pos
1269     {
1270         BOOL ret;
1272         if ([windowsBeingDragged count])
1273             ret = FALSE;
1274         else if (clippingCursor)
1275         {
1276             [self clipCursorLocation:&pos];
1278             ret = [self warpCursorTo:&pos from:NULL];
1279             synthesizedLocation = pos;
1280             if (ret)
1281             {
1282                 // We want to discard mouse-move events that have already been
1283                 // through the event tap, because it's too late to account for
1284                 // the setting of the cursor position with them.  However, the
1285                 // events that may be queued with times after that but before
1286                 // the above warp can still be used.  So, use the last event
1287                 // tap event time so that -sendEvent: doesn't discard them.
1288                 lastSetCursorPositionTime = lastEventTapEventTime;
1289             }
1290         }
1291         else
1292         {
1293             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1294             if (ret)
1295             {
1296                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1298                 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1299                 // the mouse from the cursor position for 0.25 seconds.  This means
1300                 // that mouse movement during that interval doesn't move the cursor
1301                 // and events carry a constant location (the warped-to position)
1302                 // even though they have delta values.  This screws us up because
1303                 // the accumulated deltas we send to Wine don't match any eventual
1304                 // absolute position we send (like with a button press).  We can
1305                 // work around this by simply forcibly reassociating the mouse and
1306                 // cursor position.
1307                 CGAssociateMouseAndMouseCursorPosition(true);
1308             }
1309         }
1311         if (ret)
1312         {
1313             WineEventQueue* queue;
1315             // Discard all pending mouse move events.
1316             [eventQueuesLock lock];
1317             for (queue in eventQueues)
1318             {
1319                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1320                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1321                                        forWindow:nil];
1322                 [queue resetMouseEventPositions:pos];
1323             }
1324             [eventQueuesLock unlock];
1325         }
1327         return ret;
1328     }
1330     - (void) activateCursorClipping
1331     {
1332         if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1333         {
1334             CGEventTapEnable(cursorClippingEventTap, TRUE);
1335             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1336         }
1337     }
1339     - (void) deactivateCursorClipping
1340     {
1341         if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1342         {
1343             CGEventTapEnable(cursorClippingEventTap, FALSE);
1344             [warpRecords removeAllObjects];
1345             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1346         }
1347     }
1349     - (void) updateCursorClippingState
1350     {
1351         if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1352             [self activateCursorClipping];
1353         else
1354             [self deactivateCursorClipping];
1355     }
1357     - (BOOL) startClippingCursor:(CGRect)rect
1358     {
1359         CGError err;
1361         if (!cursorClippingEventTap && ![self installEventTap])
1362             return FALSE;
1364         if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1365             CGEventTapIsEnabled(cursorClippingEventTap))
1366             return TRUE;
1368         err = CGAssociateMouseAndMouseCursorPosition(false);
1369         if (err != kCGErrorSuccess)
1370             return FALSE;
1372         clippingCursor = TRUE;
1373         cursorClipRect = rect;
1374         [self updateCursorClippingState];
1376         return TRUE;
1377     }
1379     - (BOOL) stopClippingCursor
1380     {
1381         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1382         if (err != kCGErrorSuccess)
1383             return FALSE;
1385         clippingCursor = FALSE;
1386         [self updateCursorClippingState];
1388         return TRUE;
1389     }
1391     - (BOOL) isKeyPressed:(uint16_t)keyCode
1392     {
1393         int bits = sizeof(pressedKeyCodes[0]) * 8;
1394         int index = keyCode / bits;
1395         uint32_t mask = 1 << (keyCode % bits);
1396         return (pressedKeyCodes[index] & mask) != 0;
1397     }
1399     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1400     {
1401         int bits = sizeof(pressedKeyCodes[0]) * 8;
1402         int index = keyCode / bits;
1403         uint32_t mask = 1 << (keyCode % bits);
1404         if (pressed)
1405             pressedKeyCodes[index] |= mask;
1406         else
1407             pressedKeyCodes[index] &= ~mask;
1408     }
1410     - (void) handleMouseMove:(NSEvent*)anEvent
1411     {
1412         WineWindow* targetWindow;
1413         BOOL drag = [anEvent type] != NSMouseMoved;
1415         if ([windowsBeingDragged count])
1416             targetWindow = nil;
1417         else if (mouseCaptureWindow)
1418             targetWindow = mouseCaptureWindow;
1419         else if (drag)
1420             targetWindow = (WineWindow*)[anEvent window];
1421         else
1422         {
1423             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1424                event indicates its window is the main window, even if the cursor is
1425                over a different window.  Find the actual WineWindow that is under the
1426                cursor and post the event as being for that window. */
1427             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1428             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1429             NSInteger windowUnderNumber;
1431             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1432                                   belowWindowWithWindowNumber:0];
1433             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1434             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1435                 targetWindow = nil;
1436         }
1438         if ([targetWindow isKindOfClass:[WineWindow class]])
1439         {
1440             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1441             macdrv_event* event;
1442             BOOL absolute;
1444             // If we recently warped the cursor (other than in our cursor-clipping
1445             // event tap), discard mouse move events until we see an event which is
1446             // later than that time.
1447             if (lastSetCursorPositionTime)
1448             {
1449                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1450                     return;
1452                 lastSetCursorPositionTime = 0;
1453                 forceNextMouseMoveAbsolute = TRUE;
1454             }
1456             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1457             {
1458                 absolute = TRUE;
1459                 forceNextMouseMoveAbsolute = FALSE;
1460             }
1461             else
1462             {
1463                 // Send absolute move events if the cursor is in the interior of
1464                 // its range.  Only send relative moves if the cursor is pinned to
1465                 // the boundaries of where it can go.  We compute the position
1466                 // that's one additional point in the direction of movement.  If
1467                 // that is outside of the clipping rect or desktop region (the
1468                 // union of the screen frames), then we figure the cursor would
1469                 // have moved outside if it could but it was pinned.
1470                 CGPoint computedPoint = point;
1471                 CGFloat deltaX = [anEvent deltaX];
1472                 CGFloat deltaY = [anEvent deltaY];
1474                 if (deltaX > 0.001)
1475                     computedPoint.x++;
1476                 else if (deltaX < -0.001)
1477                     computedPoint.x--;
1479                 if (deltaY > 0.001)
1480                     computedPoint.y++;
1481                 else if (deltaY < -0.001)
1482                     computedPoint.y--;
1484                 // Assume cursor is pinned for now
1485                 absolute = FALSE;
1486                 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1487                 {
1488                     const CGRect* rects;
1489                     NSUInteger count, i;
1491                     // Caches screenFrameCGRects if necessary
1492                     [self primaryScreenHeight];
1494                     rects = [screenFrameCGRects bytes];
1495                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1497                     for (i = 0; i < count; i++)
1498                     {
1499                         if (CGRectContainsPoint(rects[i], computedPoint))
1500                         {
1501                             absolute = TRUE;
1502                             break;
1503                         }
1504                     }
1505                 }
1506             }
1508             if (absolute)
1509             {
1510                 if (clippingCursor)
1511                     [self clipCursorLocation:&point];
1513                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1514                 event->mouse_moved.x = point.x;
1515                 event->mouse_moved.y = point.y;
1517                 mouseMoveDeltaX = 0;
1518                 mouseMoveDeltaY = 0;
1519             }
1520             else
1521             {
1522                 /* Add event delta to accumulated delta error */
1523                 /* deltaY is already flipped */
1524                 mouseMoveDeltaX += [anEvent deltaX];
1525                 mouseMoveDeltaY += [anEvent deltaY];
1527                 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1528                 event->mouse_moved.x = mouseMoveDeltaX;
1529                 event->mouse_moved.y = mouseMoveDeltaY;
1531                 /* Keep the remainder after integer truncation. */
1532                 mouseMoveDeltaX -= event->mouse_moved.x;
1533                 mouseMoveDeltaY -= event->mouse_moved.y;
1534             }
1536             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1537             {
1538                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1539                 event->mouse_moved.drag = drag;
1541                 [targetWindow.queue postEvent:event];
1542             }
1544             macdrv_release_event(event);
1546             lastTargetWindow = targetWindow;
1547         }
1548         else
1549             lastTargetWindow = nil;
1551         [self updateCursor:FALSE];
1552     }
1554     - (void) handleMouseButton:(NSEvent*)theEvent
1555     {
1556         WineWindow* window = (WineWindow*)[theEvent window];
1557         NSEventType type = [theEvent type];
1558         BOOL broughtWindowForward = FALSE;
1560         if ([window isKindOfClass:[WineWindow class]] &&
1561             !window.disabled && !window.noActivate &&
1562             type == NSLeftMouseDown &&
1563             (([theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask| NSAlternateKeyMask | NSCommandKeyMask)) != NSCommandKeyMask))
1564         {
1565             NSWindowButton windowButton;
1567             broughtWindowForward = TRUE;
1569             /* Any left-click on our window anyplace other than the close or
1570                minimize buttons will bring it forward. */
1571             for (windowButton = NSWindowCloseButton;
1572                  windowButton <= NSWindowMiniaturizeButton;
1573                  windowButton++)
1574             {
1575                 NSButton* button = [window standardWindowButton:windowButton];
1576                 if (button)
1577                 {
1578                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1579                     if ([button mouse:point inRect:[button bounds]])
1580                     {
1581                         broughtWindowForward = FALSE;
1582                         break;
1583                     }
1584                 }
1585             }
1587             if (broughtWindowForward)
1588             {
1589                 // Clicking on a child window does not normally reorder it with
1590                 // respect to its siblings, but we want it to.  We have to do it
1591                 // manually.
1592                 NSWindow* parent = [window parentWindow];
1593                 NSInteger level = [window level];
1594                 __block BOOL needReorder = FALSE;
1595                 NSMutableArray* higherLevelSiblings = [NSMutableArray array];
1597                 // If the window is already the last child or if it's only below
1598                 // children with higher window level, then no need to reorder it.
1599                 [[parent childWindows] enumerateObjectsWithOptions:NSEnumerationReverse
1600                                                         usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
1601                     WineWindow* child = obj;
1602                     if (child == window)
1603                         *stop = TRUE;
1604                     else if ([child level] <= level)
1605                     {
1606                         needReorder = TRUE;
1607                         *stop = TRUE;
1608                     }
1609                     else
1610                         [higherLevelSiblings insertObject:child atIndex:0];
1611                 }];
1613                 if (needReorder)
1614                 {
1615                     WineWindow* sibling;
1617                     NSDisableScreenUpdates();
1619                     [parent removeChildWindow:window];
1620                     for (sibling in higherLevelSiblings)
1621                         [parent removeChildWindow:sibling];
1623                     [parent addChildWindow:window ordered:NSWindowAbove];
1624                     for (sibling in higherLevelSiblings)
1625                     {
1626                         // Setting a window as a child can reset its level to be
1627                         // the same as the parent, so save it and restore it.
1628                         // The call to -setLevel: puts the window at the front
1629                         // of its level but testing shows that that's what Cocoa
1630                         // does when you click on any window in an ownership
1631                         // hierarchy, anyway.
1632                         level = [sibling level];
1633                         [parent addChildWindow:sibling ordered:NSWindowAbove];
1634                         [sibling setLevel:level];
1635                     }
1637                     NSEnableScreenUpdates();
1638                 }
1639             }
1640         }
1642         if ([windowsBeingDragged count])
1643             window = nil;
1644         else if (mouseCaptureWindow)
1645             window = mouseCaptureWindow;
1647         if ([window isKindOfClass:[WineWindow class]])
1648         {
1649             BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1650             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1651             BOOL process;
1653             if (clippingCursor)
1654                 [self clipCursorLocation:&pt];
1656             if (pressed)
1657             {
1658                 if (mouseCaptureWindow)
1659                     process = TRUE;
1660                 else
1661                 {
1662                     // Test if the click was in the window's content area.
1663                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1664                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1665                     process = NSMouseInRect(nspoint, contentRect, NO);
1666                     if (process && [window styleMask] & NSResizableWindowMask)
1667                     {
1668                         // Ignore clicks in the grow box (resize widget).
1669                         HIPoint origin = { 0, 0 };
1670                         HIThemeGrowBoxDrawInfo info = { 0 };
1671                         HIRect bounds;
1672                         OSStatus status;
1674                         info.kind = kHIThemeGrowBoxKindNormal;
1675                         info.direction = kThemeGrowRight | kThemeGrowDown;
1676                         if ([window styleMask] & NSUtilityWindowMask)
1677                             info.size = kHIThemeGrowBoxSizeSmall;
1678                         else
1679                             info.size = kHIThemeGrowBoxSizeNormal;
1681                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1682                         if (status == noErr)
1683                         {
1684                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1685                                                         NSMinY(contentRect),
1686                                                         bounds.size.width,
1687                                                         bounds.size.height);
1688                             process = !NSMouseInRect(nspoint, growBox, NO);
1689                         }
1690                     }
1691                 }
1692                 if (process)
1693                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1694             }
1695             else
1696             {
1697                 NSEventType downType = type - 1;
1698                 NSUInteger downMask = NSEventMaskFromType(downType);
1699                 process = (unmatchedMouseDowns & downMask) != 0;
1700                 unmatchedMouseDowns &= ~downMask;
1701             }
1703             if (process)
1704             {
1705                 macdrv_event* event;
1707                 event = macdrv_create_event(MOUSE_BUTTON, window);
1708                 event->mouse_button.button = [theEvent buttonNumber];
1709                 event->mouse_button.pressed = pressed;
1710                 event->mouse_button.x = pt.x;
1711                 event->mouse_button.y = pt.y;
1712                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1714                 [window.queue postEvent:event];
1716                 macdrv_release_event(event);
1717             }
1718             else if (broughtWindowForward)
1719             {
1720                 [[window ancestorWineWindow] postBroughtForwardEvent];
1721                 if (![window isKeyWindow])
1722                     [self windowGotFocus:window];
1723             }
1724         }
1726         // Since mouse button events deliver absolute cursor position, the
1727         // accumulating delta from move events is invalidated.  Make sure
1728         // next mouse move event starts over from an absolute baseline.
1729         // Also, it's at least possible that the title bar widgets (e.g. close
1730         // button, etc.) could enter an internal event loop on a mouse down that
1731         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1732         // dragged events and, after that, any notion of the cursor position
1733         // computed from accumulating deltas would be wrong.
1734         forceNextMouseMoveAbsolute = TRUE;
1735     }
1737     - (void) handleScrollWheel:(NSEvent*)theEvent
1738     {
1739         WineWindow* window;
1741         if (mouseCaptureWindow)
1742             window = mouseCaptureWindow;
1743         else
1744             window = (WineWindow*)[theEvent window];
1746         if ([window isKindOfClass:[WineWindow class]])
1747         {
1748             CGEventRef cgevent = [theEvent CGEvent];
1749             CGPoint pt = CGEventGetLocation(cgevent);
1750             BOOL process;
1752             if (clippingCursor)
1753                 [self clipCursorLocation:&pt];
1755             if (mouseCaptureWindow)
1756                 process = TRUE;
1757             else
1758             {
1759                 // Only process the event if it was in the window's content area.
1760                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1761                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1762                 process = NSMouseInRect(nspoint, contentRect, NO);
1763             }
1765             if (process)
1766             {
1767                 macdrv_event* event;
1768                 CGFloat x, y;
1769                 BOOL continuous = FALSE;
1771                 event = macdrv_create_event(MOUSE_SCROLL, window);
1772                 event->mouse_scroll.x = pt.x;
1773                 event->mouse_scroll.y = pt.y;
1774                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1776                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1777                 {
1778                     continuous = TRUE;
1780                     /* Continuous scroll wheel events come from high-precision scrolling
1781                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1782                        For these, we can get more precise data from the CGEvent API. */
1783                     /* Axis 1 is vertical, axis 2 is horizontal. */
1784                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1785                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1786                 }
1787                 else
1788                 {
1789                     double pixelsPerLine = 10;
1790                     CGEventSourceRef source;
1792                     /* The non-continuous values are in units of "lines", not pixels. */
1793                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1794                     {
1795                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1796                         CFRelease(source);
1797                     }
1799                     x = pixelsPerLine * [theEvent deltaX];
1800                     y = pixelsPerLine * [theEvent deltaY];
1801                 }
1803                 /* Mac: negative is right or down, positive is left or up.
1804                    Win32: negative is left or down, positive is right or up.
1805                    So, negate the X scroll value to translate. */
1806                 x = -x;
1808                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1809                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1810                    6 times the pixel value. */
1811                 event->mouse_scroll.x_scroll = 6 * x;
1812                 event->mouse_scroll.y_scroll = 6 * y;
1814                 if (!continuous)
1815                 {
1816                     /* For non-continuous "clicky" wheels, if there was any motion, make
1817                        sure there was at least WHEEL_DELTA motion.  This is so, at slow
1818                        speeds where the system's acceleration curve is actually reducing the
1819                        scroll distance, the user is sure to get some action out of each click.
1820                        For example, this is important for rotating though weapons in a
1821                        first-person shooter. */
1822                     if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1823                         event->mouse_scroll.x_scroll = 120;
1824                     else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1825                         event->mouse_scroll.x_scroll = -120;
1827                     if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1828                         event->mouse_scroll.y_scroll = 120;
1829                     else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1830                         event->mouse_scroll.y_scroll = -120;
1831                 }
1833                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1834                     [window.queue postEvent:event];
1836                 macdrv_release_event(event);
1838                 // Since scroll wheel events deliver absolute cursor position, the
1839                 // accumulating delta from move events is invalidated.  Make sure next
1840                 // mouse move event starts over from an absolute baseline.
1841                 forceNextMouseMoveAbsolute = TRUE;
1842             }
1843         }
1844     }
1846     // Returns TRUE if the event was handled and caller should do nothing more
1847     // with it.  Returns FALSE if the caller should process it as normal and
1848     // then call -didSendEvent:.
1849     - (BOOL) handleEvent:(NSEvent*)anEvent
1850     {
1851         BOOL ret = FALSE;
1852         NSEventType type = [anEvent type];
1854         if (type == NSFlagsChanged)
1855             self.lastFlagsChanged = anEvent;
1856         else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1857                  type == NSRightMouseDragged || type == NSOtherMouseDragged)
1858         {
1859             [self handleMouseMove:anEvent];
1860             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1861         }
1862         else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1863                  type == NSRightMouseDown || type == NSRightMouseUp ||
1864                  type == NSOtherMouseDown || type == NSOtherMouseUp)
1865         {
1866             [self handleMouseButton:anEvent];
1867             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1868         }
1869         else if (type == NSScrollWheel)
1870         {
1871             [self handleScrollWheel:anEvent];
1872             ret = mouseCaptureWindow != nil;
1873         }
1874         else if (type == NSKeyUp)
1875         {
1876             uint16_t keyCode = [anEvent keyCode];
1877             if ([self isKeyPressed:keyCode])
1878             {
1879                 WineWindow* window = (WineWindow*)[anEvent window];
1880                 [self noteKey:keyCode pressed:FALSE];
1881                 if ([window isKindOfClass:[WineWindow class]])
1882                     [window postKeyEvent:anEvent];
1883             }
1884         }
1885         else if (type == NSAppKitDefined)
1886         {
1887             short subtype = [anEvent subtype];
1889             // These subtypes are not documented but they appear to mean
1890             // "a window is being dragged" and "a window is no longer being
1891             // dragged", respectively.
1892             if (subtype == 20 || subtype == 21)
1893             {
1894                 WineWindow* window = (WineWindow*)[anEvent window];
1895                 if ([window isKindOfClass:[WineWindow class]])
1896                 {
1897                     macdrv_event* event;
1898                     int eventType;
1900                     if (subtype == 20)
1901                     {
1902                         [windowsBeingDragged addObject:window];
1903                         eventType = WINDOW_DRAG_BEGIN;
1904                     }
1905                     else
1906                     {
1907                         [windowsBeingDragged removeObject:window];
1908                         eventType = WINDOW_DRAG_END;
1909                     }
1910                     [self updateCursorClippingState];
1912                     event = macdrv_create_event(eventType, window);
1913                     [window.queue postEvent:event];
1914                     macdrv_release_event(event);
1915                 }
1916             }
1917         }
1919         return ret;
1920     }
1922     - (void) didSendEvent:(NSEvent*)anEvent
1923     {
1924         NSEventType type = [anEvent type];
1926         if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1927         {
1928             NSUInteger modifiers = [anEvent modifierFlags];
1929             if ((modifiers & NSCommandKeyMask) &&
1930                 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
1931             {
1932                 // Command-Tab and Command-Shift-Tab would normally be intercepted
1933                 // by the system to switch applications.  If we're seeing it, it's
1934                 // presumably because we've captured the displays, preventing
1935                 // normal application switching.  Do it manually.
1936                 [self handleCommandTab];
1937             }
1938         }
1939     }
1941     - (void) setupObservations
1942     {
1943         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1944         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1945         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
1947         [nc addObserverForName:NSWindowDidBecomeKeyNotification
1948                         object:nil
1949                          queue:nil
1950                     usingBlock:^(NSNotification *note){
1951             NSWindow* window = [note object];
1952             [keyWindows removeObjectIdenticalTo:window];
1953             [keyWindows insertObject:window atIndex:0];
1954         }];
1956         [nc addObserverForName:NSWindowWillCloseNotification
1957                         object:nil
1958                          queue:[NSOperationQueue mainQueue]
1959                     usingBlock:^(NSNotification *note){
1960             NSWindow* window = [note object];
1961             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
1962                 return;
1963             [keyWindows removeObjectIdenticalTo:window];
1964             if (window == lastTargetWindow)
1965                 lastTargetWindow = nil;
1966             if (window == self.mouseCaptureWindow)
1967                 self.mouseCaptureWindow = nil;
1968             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
1969             {
1970                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
1971                     [self updateFullscreenWindows];
1972                 });
1973             }
1974             [windowsBeingDragged removeObject:window];
1975             [self updateCursorClippingState];
1976         }];
1978         [nc addObserver:self
1979                selector:@selector(keyboardSelectionDidChange)
1980                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
1981                  object:nil];
1983         /* The above notification isn't sent unless the NSTextInputContext
1984            class has initialized itself.  Poke it. */
1985         [NSTextInputContext self];
1987         [wsnc addObserver:self
1988                  selector:@selector(activeSpaceDidChange)
1989                      name:NSWorkspaceActiveSpaceDidChangeNotification
1990                    object:nil];
1992         [nc addObserver:self
1993                selector:@selector(releaseMouseCapture)
1994                    name:NSMenuDidBeginTrackingNotification
1995                  object:nil];
1997         [dnc        addObserver:self
1998                        selector:@selector(releaseMouseCapture)
1999                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2000                          object:nil
2001              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2003         [dnc addObserver:self
2004                 selector:@selector(enabledKeyboardInputSourcesChanged)
2005                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2006                   object:nil];
2007     }
2009     - (BOOL) inputSourceIsInputMethod
2010     {
2011         if (!inputSourceIsInputMethodValid)
2012         {
2013             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2014             if (inputSource)
2015             {
2016                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2017                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2018                 CFRelease(inputSource);
2019             }
2020             else
2021                 inputSourceIsInputMethod = FALSE;
2022             inputSourceIsInputMethodValid = TRUE;
2023         }
2025         return inputSourceIsInputMethod;
2026     }
2028     - (void) releaseMouseCapture
2029     {
2030         // This might be invoked on a background thread by the distributed
2031         // notification center.  Shunt it to the main thread.
2032         if (![NSThread isMainThread])
2033         {
2034             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2035             return;
2036         }
2038         if (mouseCaptureWindow)
2039         {
2040             macdrv_event* event;
2042             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2043             [mouseCaptureWindow.queue postEvent:event];
2044             macdrv_release_event(event);
2045         }
2046     }
2048     - (void) unminimizeWindowIfNoneVisible
2049     {
2050         if (![self frontWineWindow])
2051         {
2052             for (WineWindow* window in [NSApp windows])
2053             {
2054                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2055                 {
2056                     [window deminiaturize:self];
2057                     break;
2058                 }
2059             }
2060         }
2061     }
2064     /*
2065      * ---------- NSApplicationDelegate methods ----------
2066      */
2067     - (void)applicationDidBecomeActive:(NSNotification *)notification
2068     {
2069         NSNumber* displayID;
2070         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2072         latentDisplayModes = [[NSMutableDictionary alloc] init];
2073         for (displayID in modesToRealize)
2074         {
2075             CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2076             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2077         }
2079         [self updateCursorClippingState];
2081         [self updateFullscreenWindows];
2082         [self adjustWindowLevels:YES];
2084         if (beenActive)
2085             [self unminimizeWindowIfNoneVisible];
2086         beenActive = TRUE;
2088         // If a Wine process terminates abruptly while it has the display captured
2089         // and switched to a different resolution, Mac OS X will uncapture the
2090         // displays and switch their resolutions back.  However, the other Wine
2091         // processes won't have their notion of the desktop rect changed back.
2092         // This can lead them to refuse to draw or acknowledge clicks in certain
2093         // portions of their windows.
2094         //
2095         // To solve this, we synthesize a displays-changed event whenever we're
2096         // activated.  This will provoke a re-synchronization of Wine's notion of
2097         // the desktop rect with the actual state.
2098         [self sendDisplaysChanged:TRUE];
2100         // The cursor probably moved while we were inactive.  Accumulated mouse
2101         // movement deltas are invalidated.  Make sure the next mouse move event
2102         // starts over from an absolute baseline.
2103         forceNextMouseMoveAbsolute = TRUE;
2104     }
2106     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2107     {
2108         primaryScreenHeightValid = FALSE;
2109         [self sendDisplaysChanged:FALSE];
2110         [self adjustWindowLevels];
2112         // When the display configuration changes, the cursor position may jump.
2113         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2114         // mouse move event starts over from an absolute baseline.
2115         forceNextMouseMoveAbsolute = TRUE;
2116     }
2118     - (void)applicationDidResignActive:(NSNotification *)notification
2119     {
2120         macdrv_event* event;
2121         WineEventQueue* queue;
2123         [self updateCursorClippingState];
2125         [self invalidateGotFocusEvents];
2127         event = macdrv_create_event(APP_DEACTIVATED, nil);
2129         [eventQueuesLock lock];
2130         for (queue in eventQueues)
2131             [queue postEvent:event];
2132         [eventQueuesLock unlock];
2134         macdrv_release_event(event);
2136         [self releaseMouseCapture];
2137     }
2139     - (void) applicationDidUnhide:(NSNotification*)aNotification
2140     {
2141         [self adjustWindowLevels];
2142     }
2144     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2145     {
2146         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2147         // don't count as "visible windows" for this purpose.
2148         [self unminimizeWindowIfNoneVisible];
2149         return YES;
2150     }
2152     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2153     {
2154         NSApplicationTerminateReply ret = NSTerminateNow;
2155         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2156         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2157         macdrv_event* event;
2158         WineEventQueue* queue;
2160         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2161         event->deliver = 1;
2162         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2163         {
2164             case kAELogOut:
2165             case kAEReallyLogOut:
2166                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2167                 break;
2168             case kAEShowRestartDialog:
2169                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2170                 break;
2171             case kAEShowShutdownDialog:
2172                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2173                 break;
2174             default:
2175                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2176                 break;
2177         }
2179         [eventQueuesLock lock];
2181         if ([eventQueues count])
2182         {
2183             for (queue in eventQueues)
2184                 [queue postEvent:event];
2185             ret = NSTerminateLater;
2186         }
2188         [eventQueuesLock unlock];
2190         macdrv_release_event(event);
2192         return ret;
2193     }
2195     - (void)applicationWillResignActive:(NSNotification *)notification
2196     {
2197         [self adjustWindowLevels:NO];
2198     }
2200 /***********************************************************************
2201  *              PerformRequest
2203  * Run-loop-source perform callback.  Pull request blocks from the
2204  * array of queued requests and invoke them.
2205  */
2206 static void PerformRequest(void *info)
2208     WineApplicationController* controller = [WineApplicationController sharedController];
2210     for (;;)
2211     {
2212         __block dispatch_block_t block;
2214         dispatch_sync(controller->requestsManipQueue, ^{
2215             if ([controller->requests count])
2216             {
2217                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2218                 [controller->requests removeObjectAtIndex:0];
2219             }
2220             else
2221                 block = nil;
2222         });
2224         if (!block)
2225             break;
2227         block();
2228         [block release];
2229     }
2232 /***********************************************************************
2233  *              OnMainThreadAsync
2235  * Run a block on the main thread asynchronously.
2236  */
2237 void OnMainThreadAsync(dispatch_block_t block)
2239     WineApplicationController* controller = [WineApplicationController sharedController];
2241     block = [block copy];
2242     dispatch_sync(controller->requestsManipQueue, ^{
2243         [controller->requests addObject:block];
2244     });
2245     [block release];
2246     CFRunLoopSourceSignal(controller->requestSource);
2247     CFRunLoopWakeUp(CFRunLoopGetMain());
2250 @end
2252 /***********************************************************************
2253  *              LogError
2254  */
2255 void LogError(const char* func, NSString* format, ...)
2257     va_list args;
2258     va_start(args, format);
2259     LogErrorv(func, format, args);
2260     va_end(args);
2263 /***********************************************************************
2264  *              LogErrorv
2265  */
2266 void LogErrorv(const char* func, NSString* format, va_list args)
2268     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2270     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2271     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2272     [message release];
2274     [pool release];
2277 /***********************************************************************
2278  *              macdrv_window_rejected_focus
2280  * Pass focus to the next window that hasn't already rejected this same
2281  * WINDOW_GOT_FOCUS event.
2282  */
2283 void macdrv_window_rejected_focus(const macdrv_event *event)
2285     OnMainThread(^{
2286         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2287     });
2290 /***********************************************************************
2291  *              macdrv_get_input_source_info
2293  * Returns the keyboard layout uchr data, keyboard type and input source.
2294  */
2295 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2297     OnMainThread(^{
2298         TISInputSourceRef inputSourceLayout;
2300         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2301         if (inputSourceLayout)
2302         {
2303             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2304                                 kTISPropertyUnicodeKeyLayoutData);
2305             *uchr = CFDataCreateCopy(NULL, data);
2306             CFRelease(inputSourceLayout);
2308             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2309             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2310             *input_source = TISCopyCurrentKeyboardInputSource();
2311         }
2312     });
2315 /***********************************************************************
2316  *              macdrv_beep
2318  * Play the beep sound configured by the user in System Preferences.
2319  */
2320 void macdrv_beep(void)
2322     OnMainThreadAsync(^{
2323         NSBeep();
2324     });
2327 /***********************************************************************
2328  *              macdrv_set_display_mode
2329  */
2330 int macdrv_set_display_mode(const struct macdrv_display* display,
2331                             CGDisplayModeRef display_mode)
2333     __block int ret;
2335     OnMainThread(^{
2336         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2337     });
2339     return ret;
2342 /***********************************************************************
2343  *              macdrv_set_cursor
2345  * Set the cursor.
2347  * If name is non-NULL, it is a selector for a class method on NSCursor
2348  * identifying the cursor to set.  In that case, frames is ignored.  If
2349  * name is NULL, then frames is used.
2351  * frames is an array of dictionaries.  Each dictionary is a frame of
2352  * an animated cursor.  Under the key "image" is a CGImage for the
2353  * frame.  Under the key "duration" is a CFNumber time interval, in
2354  * seconds, for how long that frame is presented before proceeding to
2355  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2356  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2357  * This is the hot spot, measured in pixels down and to the right of the
2358  * top-left corner of the image.
2360  * If the array has exactly 1 element, the cursor is static, not
2361  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2362  */
2363 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2365     SEL sel;
2367     sel = NSSelectorFromString((NSString*)name);
2368     if (sel)
2369     {
2370         OnMainThreadAsync(^{
2371             WineApplicationController* controller = [WineApplicationController sharedController];
2372             [controller setCursorWithFrames:nil];
2373             controller.cursor = [NSCursor performSelector:sel];
2374             [controller unhideCursor];
2375         });
2376     }
2377     else
2378     {
2379         NSArray* nsframes = (NSArray*)frames;
2380         if ([nsframes count])
2381         {
2382             OnMainThreadAsync(^{
2383                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2384             });
2385         }
2386         else
2387         {
2388             OnMainThreadAsync(^{
2389                 WineApplicationController* controller = [WineApplicationController sharedController];
2390                 [controller setCursorWithFrames:nil];
2391                 [controller hideCursor];
2392             });
2393         }
2394     }
2397 /***********************************************************************
2398  *              macdrv_get_cursor_position
2400  * Obtains the current cursor position.  Returns zero on failure,
2401  * non-zero on success.
2402  */
2403 int macdrv_get_cursor_position(CGPoint *pos)
2405     OnMainThread(^{
2406         NSPoint location = [NSEvent mouseLocation];
2407         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2408         *pos = NSPointToCGPoint(location);
2409     });
2411     return TRUE;
2414 /***********************************************************************
2415  *              macdrv_set_cursor_position
2417  * Sets the cursor position without generating events.  Returns zero on
2418  * failure, non-zero on success.
2419  */
2420 int macdrv_set_cursor_position(CGPoint pos)
2422     __block int ret;
2424     OnMainThread(^{
2425         ret = [[WineApplicationController sharedController] setCursorPosition:pos];
2426     });
2428     return ret;
2431 /***********************************************************************
2432  *              macdrv_clip_cursor
2434  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2435  * to or larger than the whole desktop region, the cursor is unclipped.
2436  * Returns zero on failure, non-zero on success.
2437  */
2438 int macdrv_clip_cursor(CGRect rect)
2440     __block int ret;
2442     OnMainThread(^{
2443         WineApplicationController* controller = [WineApplicationController sharedController];
2444         BOOL clipping = FALSE;
2446         if (!CGRectIsInfinite(rect))
2447         {
2448             NSRect nsrect = NSRectFromCGRect(rect);
2449             NSScreen* screen;
2451             /* Convert the rectangle from top-down coords to bottom-up. */
2452             [controller flipRect:&nsrect];
2454             clipping = FALSE;
2455             for (screen in [NSScreen screens])
2456             {
2457                 if (!NSContainsRect(nsrect, [screen frame]))
2458                 {
2459                     clipping = TRUE;
2460                     break;
2461                 }
2462             }
2463         }
2465         if (clipping)
2466             ret = [controller startClippingCursor:rect];
2467         else
2468             ret = [controller stopClippingCursor];
2469     });
2471     return ret;
2474 /***********************************************************************
2475  *              macdrv_set_application_icon
2477  * Set the application icon.  The images array contains CGImages.  If
2478  * there are more than one, then they represent different sizes or
2479  * color depths from the icon resource.  If images is NULL or empty,
2480  * restores the default application image.
2481  */
2482 void macdrv_set_application_icon(CFArrayRef images)
2484     NSArray* imageArray = (NSArray*)images;
2486     OnMainThreadAsync(^{
2487         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2488     });
2491 /***********************************************************************
2492  *              macdrv_quit_reply
2493  */
2494 void macdrv_quit_reply(int reply)
2496     OnMainThread(^{
2497         [NSApp replyToApplicationShouldTerminate:reply];
2498     });
2501 /***********************************************************************
2502  *              macdrv_using_input_method
2503  */
2504 int macdrv_using_input_method(void)
2506     __block BOOL ret;
2508     OnMainThread(^{
2509         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2510     });
2512     return ret;
2515 /***********************************************************************
2516  *              macdrv_set_mouse_capture_window
2517  */
2518 void macdrv_set_mouse_capture_window(macdrv_window window)
2520     WineWindow* w = (WineWindow*)window;
2522     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2524     OnMainThread(^{
2525         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2526     });
2529 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2530 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2531 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2533 /***********************************************************************
2534  *              macdrv_create_input_source_list
2535  */
2536 CFArrayRef macdrv_create_input_source_list(void)
2538     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2540     OnMainThread(^{
2541         CFArrayRef input_list;
2542         CFDictionaryRef filter_dict;
2543         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2544         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2545         int i;
2547         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2548                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2549         input_list = TISCreateInputSourceList(filter_dict, false);
2551         for (i = 0; i < CFArrayGetCount(input_list); i++)
2552         {
2553             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2554             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2555             CFDictionaryRef entry;
2556             const void *input_keys[3] = { macdrv_input_source_input_key,
2557                                           macdrv_input_source_type_key,
2558                                           macdrv_input_source_lang_key };
2559             const void *input_values[3];
2561             input_values[0] = input;
2562             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2563             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2565             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2566                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2568             CFArrayAppendValue(ret, entry);
2569             CFRelease(entry);
2570         }
2571         CFRelease(input_list);
2572         CFRelease(filter_dict);
2573     });
2575     return ret;
2578 int macdrv_select_input_source(TISInputSourceRef input_source)
2580     __block int ret = FALSE;
2582     OnMainThread(^{
2583         ret = (TISSelectInputSource(input_source) == noErr);
2584     });
2586     return ret;