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