comctl32: Use SetRect() instead of open coding it.
[wine.git] / dlls / winemac.drv / cocoa_app.m
blob176d35c168c09fd7fcb47af7b7e510d75cff13a3
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 /***********************************************************************
36  *              WineLocalizedString
37  *
38  * Look up a localized string by its ID in the dictionary.
39  */
40 static NSString* WineLocalizedString(unsigned int stringID)
42     NSNumber* key = [NSNumber numberWithUnsignedInt:stringID];
43     return [(NSDictionary*)localized_strings objectForKey:key];
47 @implementation WineApplication
49 @synthesize wineController;
51     - (void) sendEvent:(NSEvent*)anEvent
52     {
53         if (![wineController handleEvent:anEvent])
54         {
55             [super sendEvent:anEvent];
56             [wineController didSendEvent:anEvent];
57         }
58     }
60     - (void) setWineController:(WineApplicationController*)newController
61     {
62         wineController = newController;
63         [self setDelegate:wineController];
64     }
66 @end
69 @interface WarpRecord : NSObject
71     CGEventTimestamp timeBefore, timeAfter;
72     CGPoint from, to;
75 @property (nonatomic) CGEventTimestamp timeBefore;
76 @property (nonatomic) CGEventTimestamp timeAfter;
77 @property (nonatomic) CGPoint from;
78 @property (nonatomic) CGPoint to;
80 @end
83 @implementation WarpRecord
85 @synthesize timeBefore, timeAfter, from, to;
87 @end;
90 @interface WineApplicationController ()
92 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
93 @property (copy, nonatomic) NSArray* cursorFrames;
94 @property (retain, nonatomic) NSTimer* cursorTimer;
95 @property (retain, nonatomic) NSCursor* cursor;
96 @property (retain, nonatomic) NSImage* applicationIcon;
97 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
98 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
100     - (void) setupObservations;
101     - (void) applicationDidBecomeActive:(NSNotification *)notification;
103     static void PerformRequest(void *info);
105 @end
108 @implementation WineApplicationController
110     @synthesize keyboardType, lastFlagsChanged;
111     @synthesize applicationIcon;
112     @synthesize cursorFrames, cursorTimer, cursor;
113     @synthesize mouseCaptureWindow;
115     @synthesize clippingCursor;
117     + (void) initialize
118     {
119         if (self == [WineApplicationController class])
120         {
121             NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
122                                       @"", @"NSQuotedKeystrokeBinding",
123                                       @"", @"NSRepeatCountBinding",
124                                       [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
125                                       nil];
126             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
127         }
128     }
130     + (WineApplicationController*) sharedController
131     {
132         static WineApplicationController* sharedController;
133         static dispatch_once_t once;
135         dispatch_once(&once, ^{
136             sharedController = [[self alloc] init];
137         });
139         return sharedController;
140     }
142     - (id) init
143     {
144         self = [super init];
145         if (self != nil)
146         {
147             CFRunLoopSourceContext context = { 0 };
148             context.perform = PerformRequest;
149             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
150             if (!requestSource)
151             {
152                 [self release];
153                 return nil;
154             }
155             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
156             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
158             requests =  [[NSMutableArray alloc] init];
159             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
161             eventQueues = [[NSMutableArray alloc] init];
162             eventQueuesLock = [[NSLock alloc] init];
164             keyWindows = [[NSMutableArray alloc] init];
166             originalDisplayModes = [[NSMutableDictionary alloc] init];
167             latentDisplayModes = [[NSMutableDictionary alloc] init];
169             warpRecords = [[NSMutableArray alloc] init];
171             windowsBeingDragged = [[NSMutableSet alloc] init];
173             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
174                 !keyWindows || !originalDisplayModes || !latentDisplayModes || !warpRecords)
175             {
176                 [self release];
177                 return nil;
178             }
180             [self setupObservations];
182             keyboardType = LMGetKbdType();
184             if ([NSApp isActive])
185                 [self applicationDidBecomeActive:nil];
186         }
187         return self;
188     }
190     - (void) dealloc
191     {
192         [windowsBeingDragged release];
193         [cursor release];
194         [screenFrameCGRects release];
195         [applicationIcon release];
196         [warpRecords release];
197         [cursorTimer release];
198         [cursorFrames release];
199         [latentDisplayModes release];
200         [originalDisplayModes release];
201         [keyWindows release];
202         [eventQueues release];
203         [eventQueuesLock release];
204         if (requestsManipQueue) dispatch_release(requestsManipQueue);
205         [requests release];
206         if (requestSource)
207         {
208             CFRunLoopSourceInvalidate(requestSource);
209             CFRelease(requestSource);
210         }
211         [super dealloc];
212     }
214     - (void) transformProcessToForeground
215     {
216         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
217         {
218             NSMenu* mainMenu;
219             NSMenu* submenu;
220             NSString* bundleName;
221             NSString* title;
222             NSMenuItem* item;
224             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
225             [NSApp activateIgnoringOtherApps:YES];
227             mainMenu = [[[NSMenu alloc] init] autorelease];
229             // Application menu
230             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
231             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
233             if ([bundleName length])
234                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
235             else
236                 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
237             item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
239             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
240                                       action:@selector(hideOtherApplications:)
241                                keyEquivalent:@"h"];
242             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
244             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
245                                       action:@selector(unhideAllApplications:)
246                                keyEquivalent:@""];
248             [submenu addItem:[NSMenuItem separatorItem]];
250             if ([bundleName length])
251                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
252             else
253                 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
254             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
255             [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
256             item = [[[NSMenuItem alloc] init] autorelease];
257             [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
258             [item setSubmenu:submenu];
259             [mainMenu addItem:item];
261             // Window menu
262             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
263             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
264                                action:@selector(performMiniaturize:)
265                         keyEquivalent:@""];
266             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
267                                action:@selector(performZoom:)
268                         keyEquivalent:@""];
269             if ([NSWindow instancesRespondToSelector:@selector(toggleFullScreen:)])
270             {
271                 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
272                                           action:@selector(toggleFullScreen:)
273                                    keyEquivalent:@"f"];
274                 [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask];
275             }
276             [submenu addItem:[NSMenuItem separatorItem]];
277             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
278                                action:@selector(arrangeInFront:)
279                         keyEquivalent:@""];
280             item = [[[NSMenuItem alloc] init] autorelease];
281             [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
282             [item setSubmenu:submenu];
283             [mainMenu addItem:item];
285             [NSApp setMainMenu:mainMenu];
286             [NSApp setWindowsMenu:submenu];
288             [NSApp setApplicationIconImage:self.applicationIcon];
289         }
290     }
292     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
293     {
294         PerformRequest(NULL);
296         do
297         {
298             if (processEvents)
299             {
300                 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
301                 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
302                                                     untilDate:timeout
303                                                        inMode:NSDefaultRunLoopMode
304                                                       dequeue:YES];
305                 if (event)
306                     [NSApp sendEvent:event];
307                 [pool release];
308             }
309             else
310                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
311         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
313         return *done;
314     }
316     - (BOOL) registerEventQueue:(WineEventQueue*)queue
317     {
318         [eventQueuesLock lock];
319         [eventQueues addObject:queue];
320         [eventQueuesLock unlock];
321         return TRUE;
322     }
324     - (void) unregisterEventQueue:(WineEventQueue*)queue
325     {
326         [eventQueuesLock lock];
327         [eventQueues removeObjectIdenticalTo:queue];
328         [eventQueuesLock unlock];
329     }
331     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
332     {
333         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
334     }
336     - (double) ticksForEventTime:(NSTimeInterval)eventTime
337     {
338         return (eventTime + eventTimeAdjustment) * 1000;
339     }
341     /* Invalidate old focus offers across all queues. */
342     - (void) invalidateGotFocusEvents
343     {
344         WineEventQueue* queue;
346         windowFocusSerial++;
348         [eventQueuesLock lock];
349         for (queue in eventQueues)
350         {
351             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
352                                    forWindow:nil];
353         }
354         [eventQueuesLock unlock];
355     }
357     - (void) windowGotFocus:(WineWindow*)window
358     {
359         macdrv_event* event;
361         [self invalidateGotFocusEvents];
363         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
364         event->window_got_focus.serial = windowFocusSerial;
365         if (triedWindows)
366             event->window_got_focus.tried_windows = [triedWindows retain];
367         else
368             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
369         [window.queue postEvent:event];
370         macdrv_release_event(event);
371     }
373     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
374     {
375         if (event->window_got_focus.serial == windowFocusSerial)
376         {
377             NSMutableArray* windows = [keyWindows mutableCopy];
378             NSNumber* windowNumber;
379             WineWindow* window;
381             for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
382             {
383                 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
384                 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
385                     ![windows containsObject:window])
386                     [windows addObject:window];
387             }
389             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
390             [triedWindows addObject:(WineWindow*)event->window];
391             for (window in windows)
392             {
393                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
394                 {
395                     [window makeKeyWindow];
396                     break;
397                 }
398             }
399             triedWindows = nil;
400             [windows release];
401         }
402     }
404     - (void) keyboardSelectionDidChange
405     {
406         TISInputSourceRef inputSourceLayout;
408         inputSourceIsInputMethodValid = FALSE;
410         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
411         if (inputSourceLayout)
412         {
413             CFDataRef uchr;
414             uchr = TISGetInputSourceProperty(inputSourceLayout,
415                     kTISPropertyUnicodeKeyLayoutData);
416             if (uchr)
417             {
418                 macdrv_event* event;
419                 WineEventQueue* queue;
421                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
422                 event->keyboard_changed.keyboard_type = self.keyboardType;
423                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
424                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
425                 event->keyboard_changed.input_source = TISCopyCurrentKeyboardInputSource();
427                 if (event->keyboard_changed.uchr)
428                 {
429                     [eventQueuesLock lock];
431                     for (queue in eventQueues)
432                         [queue postEvent:event];
434                     [eventQueuesLock unlock];
435                 }
437                 macdrv_release_event(event);
438             }
440             CFRelease(inputSourceLayout);
441         }
442     }
444     - (void) enabledKeyboardInputSourcesChanged
445     {
446         macdrv_layout_list_needs_update = TRUE;
447     }
449     - (CGFloat) primaryScreenHeight
450     {
451         if (!primaryScreenHeightValid)
452         {
453             NSArray* screens = [NSScreen screens];
454             NSUInteger count = [screens count];
455             if (count)
456             {
457                 NSUInteger size;
458                 CGRect* rect;
459                 NSScreen* screen;
461                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
462                 primaryScreenHeightValid = TRUE;
464                 size = count * sizeof(CGRect);
465                 if (!screenFrameCGRects)
466                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
467                 else
468                     [screenFrameCGRects setLength:size];
470                 rect = [screenFrameCGRects mutableBytes];
471                 for (screen in screens)
472                 {
473                     CGRect temp = NSRectToCGRect([screen frame]);
474                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
475                     *rect++ = temp;
476                 }
477             }
478             else
479                 return 1280; /* arbitrary value */
480         }
482         return primaryScreenHeight;
483     }
485     - (NSPoint) flippedMouseLocation:(NSPoint)point
486     {
487         /* This relies on the fact that Cocoa's mouse location points are
488            actually off by one (precisely because they were flipped from
489            Quartz screen coordinates using this same technique). */
490         point.y = [self primaryScreenHeight] - point.y;
491         return point;
492     }
494     - (void) flipRect:(NSRect*)rect
495     {
496         // We don't use -primaryScreenHeight here so there's no chance of having
497         // out-of-date cached info.  This method is called infrequently enough
498         // that getting the screen height each time is not prohibitively expensive.
499         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
500     }
502     - (WineWindow*) frontWineWindow
503     {
504         NSNumber* windowNumber;
505         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
506         {
507             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
508             if ([window isKindOfClass:[WineWindow class]] && [window screen])
509                 return (WineWindow*)window;
510         }
512         return nil;
513     }
515     - (void) adjustWindowLevels:(BOOL)active
516     {
517         NSArray* windowNumbers;
518         NSMutableArray* wineWindows;
519         NSNumber* windowNumber;
520         NSUInteger nextFloatingIndex = 0;
521         __block NSInteger maxLevel = NSIntegerMin;
522         __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
523         __block WineWindow* prev = nil;
524         WineWindow* window;
526         if ([NSApp isHidden]) return;
528         windowNumbers = [NSWindow windowNumbersWithOptions:0];
529         wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
531         // For the most part, we rely on the window server's ordering of the windows
532         // to be authoritative.  The one exception is if the "floating" property of
533         // one of the windows has been changed, it may be in the wrong level and thus
534         // in the order.  This method is what's supposed to fix that up.  So build
535         // a list of Wine windows sorted first by floating-ness and then by order
536         // as indicated by the window server.
537         for (windowNumber in windowNumbers)
538         {
539             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
540             if ([window isKindOfClass:[WineWindow class]])
541             {
542                 if (window.floating)
543                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
544                 else
545                     [wineWindows addObject:window];
546             }
547         }
549         NSDisableScreenUpdates();
551         // Go from back to front so that all windows in front of one which is
552         // elevated for full-screen are also elevated.
553         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
554                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
555             WineWindow* window = (WineWindow*)obj;
556             NSInteger origLevel = [window level];
557             NSInteger newLevel = [window minimumLevelForActive:active];
559             if (newLevel < maxLevel)
560                 newLevel = maxLevel;
561             else
562                 maxLevel = newLevel;
564             if (!window.floating && maxNonfloatingLevel < newLevel)
565                 maxNonfloatingLevel = newLevel;
567             if (newLevel != origLevel)
568             {
569                 [window setLevel:newLevel];
571                 // -setLevel: puts the window at the front of its new level.  If
572                 // we decreased the level, that's good (it was in front of that
573                 // level before, so it should still be now).  But if we increased
574                 // the level, the window should be toward the back (but still
575                 // ahead of the previous windows we did this to).
576                 if (origLevel < newLevel)
577                 {
578                     if (prev)
579                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
580                     else
581                         [window orderBack:nil];
582                 }
583             }
585             prev = window;
586         }];
588         NSEnableScreenUpdates();
590         [wineWindows release];
592         // The above took care of the visible windows on the current space.  That
593         // leaves windows on other spaces, minimized windows, and windows which
594         // are not ordered in.  We want to leave windows on other spaces alone
595         // so the space remains just as they left it (when viewed in Exposé or
596         // Mission Control, for example).  We'll adjust the window levels again
597         // after we switch to another space, anyway.  Windows which aren't
598         // ordered in will be handled when we order them in.  Minimized windows
599         // on the current space should be set to the level they would have gotten
600         // if they were at the front of the windows with the same floating-ness,
601         // because that's where they'll go if/when they are unminimized.  Again,
602         // for good measure we'll adjust window levels again when a window is
603         // unminimized, too.
604         for (window in [NSApp windows])
605         {
606             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
607                 [window isOnActiveSpace])
608             {
609                 NSInteger origLevel = [window level];
610                 NSInteger newLevel = [window minimumLevelForActive:YES];
611                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
613                 if (newLevel < maxLevelForType)
614                     newLevel = maxLevelForType;
616                 if (newLevel != origLevel)
617                     [window setLevel:newLevel];
618             }
619         }
620     }
622     - (void) adjustWindowLevels
623     {
624         [self adjustWindowLevels:[NSApp isActive]];
625     }
627     - (void) updateFullscreenWindows
628     {
629         if (capture_displays_for_fullscreen && [NSApp isActive])
630         {
631             BOOL anyFullscreen = FALSE;
632             NSNumber* windowNumber;
633             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
634             {
635                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
636                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
637                 {
638                     anyFullscreen = TRUE;
639                     break;
640                 }
641             }
643             if (anyFullscreen)
644             {
645                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
646                     displaysCapturedForFullscreen = TRUE;
647             }
648             else if (displaysCapturedForFullscreen)
649             {
650                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
651                     displaysCapturedForFullscreen = FALSE;
652             }
653         }
654     }
656     - (void) activeSpaceDidChange
657     {
658         [self updateFullscreenWindows];
659         [self adjustWindowLevels];
660     }
662     - (void) sendDisplaysChanged:(BOOL)activating
663     {
664         macdrv_event* event;
665         WineEventQueue* queue;
667         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
668         event->displays_changed.activating = activating;
670         [eventQueuesLock lock];
672         // If we're activating, then we just need one of our threads to get the
673         // event, so it can send it directly to the desktop window.  Otherwise,
674         // we need all of the threads to get it because we don't know which owns
675         // the desktop window and only that one will do anything with it.
676         if (activating) event->deliver = 1;
678         for (queue in eventQueues)
679             [queue postEvent:event];
680         [eventQueuesLock unlock];
682         macdrv_release_event(event);
683     }
685     // We can compare two modes directly using CFEqual, but that may require that
686     // they are identical to a level that we don't need.  In particular, when the
687     // OS switches between the integrated and discrete GPUs, the set of display
688     // modes can change in subtle ways.  We're interested in whether two modes
689     // match in their most salient features, even if they aren't identical.
690     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
691     {
692         NSString *encoding1, *encoding2;
693         uint32_t ioflags1, ioflags2, different;
694         double refresh1, refresh2;
696         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
697         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
699 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
700         if (CGDisplayModeGetPixelWidth != NULL &&
701             CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
702         if (CGDisplayModeGetPixelHeight != NULL &&
703             CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
704 #endif
706         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
707         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
708         if (![encoding1 isEqualToString:encoding2]) return FALSE;
710         ioflags1 = CGDisplayModeGetIOFlags(mode1);
711         ioflags2 = CGDisplayModeGetIOFlags(mode2);
712         different = ioflags1 ^ ioflags2;
713         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
714                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
715             return FALSE;
717         refresh1 = CGDisplayModeGetRefreshRate(mode1);
718         if (refresh1 == 0) refresh1 = 60;
719         refresh2 = CGDisplayModeGetRefreshRate(mode2);
720         if (refresh2 == 0) refresh2 = 60;
721         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
723         return TRUE;
724     }
726     - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
727     {
728         NSMutableArray* ret = [NSMutableArray array];
729         NSDictionary* options = nil;
731 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
732         if (&kCGDisplayShowDuplicateLowResolutionModes != NULL)
733             options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:TRUE]
734                                                   forKey:(NSString*)kCGDisplayShowDuplicateLowResolutionModes];
735 #endif
737         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
738         for (id candidateModeObject in modes)
739         {
740             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
741             if ([self mode:candidateMode matchesMode:mode])
742                 [ret addObject:candidateModeObject];
743         }
744         return ret;
745     }
747     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
748     {
749         BOOL ret = FALSE;
750         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
751         CGDisplayModeRef originalMode;
753         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
755         if (originalMode && [self mode:mode matchesMode:originalMode])
756         {
757             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
758             {
759                 CGRestorePermanentDisplayConfiguration();
760                 if (!displaysCapturedForFullscreen)
761                     CGReleaseAllDisplays();
762                 [originalDisplayModes removeAllObjects];
763                 ret = TRUE;
764             }
765             else // ... otherwise, try to restore just the one display
766             {
767                 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
768                 {
769                     mode = (CGDisplayModeRef)modeObject;
770                     if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
771                     {
772                         [originalDisplayModes removeObjectForKey:displayIDKey];
773                         ret = TRUE;
774                         break;
775                     }
776                 }
777             }
778         }
779         else
780         {
781             BOOL active = [NSApp isActive];
782             CGDisplayModeRef currentMode;
783             NSArray* modes;
785             currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
786             if (!currentMode)
787                 currentMode = CGDisplayCopyDisplayMode(displayID);
788             if (!currentMode) // Invalid display ID
789                 return FALSE;
791             if ([self mode:mode matchesMode:currentMode]) // Already there!
792             {
793                 CGDisplayModeRelease(currentMode);
794                 return TRUE;
795             }
797             CGDisplayModeRelease(currentMode);
798             currentMode = NULL;
800             modes = [self modesMatchingMode:mode forDisplay:displayID];
801             if (!modes.count)
802                 return FALSE;
804             if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
805                 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
806             {
807                 if (active)
808                 {
809                     // If we get here, we have the displays captured.  If we don't
810                     // know the original mode of the display, the current mode must
811                     // be the original.  We should re-query the current mode since
812                     // another process could have changed it between when we last
813                     // checked and when we captured the displays.
814                     if (!originalMode)
815                         originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
817                     if (originalMode)
818                     {
819                         for (id modeObject in modes)
820                         {
821                             mode = (CGDisplayModeRef)modeObject;
822                             if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
823                             {
824                                 ret = TRUE;
825                                 break;
826                             }
827                         }
828                     }
829                     if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
830                         [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
831                     else if (![originalDisplayModes count])
832                     {
833                         CGRestorePermanentDisplayConfiguration();
834                         if (!displaysCapturedForFullscreen)
835                             CGReleaseAllDisplays();
836                     }
838                     if (currentMode)
839                         CGDisplayModeRelease(currentMode);
840                 }
841                 else
842                 {
843                     [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
844                     ret = TRUE;
845                 }
846             }
847         }
849         if (ret)
850             [self adjustWindowLevels];
852         return ret;
853     }
855     - (BOOL) areDisplaysCaptured
856     {
857         return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
858     }
860     - (void) updateCursor:(BOOL)force
861     {
862         if (force || lastTargetWindow)
863         {
864             if (clientWantsCursorHidden && !cursorHidden)
865             {
866                 [NSCursor hide];
867                 cursorHidden = TRUE;
868             }
870             if (!cursorIsCurrent)
871             {
872                 [cursor set];
873                 cursorIsCurrent = TRUE;
874             }
876             if (!clientWantsCursorHidden && cursorHidden)
877             {
878                 [NSCursor unhide];
879                 cursorHidden = FALSE;
880             }
881         }
882         else
883         {
884             if (cursorIsCurrent)
885             {
886                 [[NSCursor arrowCursor] set];
887                 cursorIsCurrent = FALSE;
888             }
889             if (cursorHidden)
890             {
891                 [NSCursor unhide];
892                 cursorHidden = FALSE;
893             }
894         }
895     }
897     - (void) hideCursor
898     {
899         if (!clientWantsCursorHidden)
900         {
901             clientWantsCursorHidden = TRUE;
902             [self updateCursor:TRUE];
903         }
904     }
906     - (void) unhideCursor
907     {
908         if (clientWantsCursorHidden)
909         {
910             clientWantsCursorHidden = FALSE;
911             [self updateCursor:FALSE];
912         }
913     }
915     - (void) setCursor:(NSCursor*)newCursor
916     {
917         if (newCursor != cursor)
918         {
919             [cursor release];
920             cursor = [newCursor retain];
921             cursorIsCurrent = FALSE;
922             [self updateCursor:FALSE];
923         }
924     }
926     - (void) setCursor
927     {
928         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
929         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
930         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSZeroSize];
931         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
932         CGPoint hotSpot;
934         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
935             hotSpot = CGPointZero;
936         self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
937         [image release];
938         [self unhideCursor];
939     }
941     - (void) nextCursorFrame:(NSTimer*)theTimer
942     {
943         NSDictionary* frame;
944         NSTimeInterval duration;
945         NSDate* date;
947         cursorFrame++;
948         if (cursorFrame >= [cursorFrames count])
949             cursorFrame = 0;
950         [self setCursor];
952         frame = [cursorFrames objectAtIndex:cursorFrame];
953         duration = [[frame objectForKey:@"duration"] doubleValue];
954         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
955         [cursorTimer setFireDate:date];
956     }
958     - (void) setCursorWithFrames:(NSArray*)frames
959     {
960         if (self.cursorFrames == frames)
961             return;
963         self.cursorFrames = frames;
964         cursorFrame = 0;
965         [cursorTimer invalidate];
966         self.cursorTimer = nil;
968         if ([frames count])
969         {
970             if ([frames count] > 1)
971             {
972                 NSDictionary* frame = [frames objectAtIndex:0];
973                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
974                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
975                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
976                                                              interval:1000000
977                                                                target:self
978                                                              selector:@selector(nextCursorFrame:)
979                                                              userInfo:nil
980                                                               repeats:YES] autorelease];
981                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
982             }
984             [self setCursor];
985         }
986     }
988     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
989     {
990         NSImage* nsimage = nil;
992         if ([images count])
993         {
994             NSSize bestSize = NSZeroSize;
995             id image;
997             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
999             for (image in images)
1000             {
1001                 CGImageRef cgimage = (CGImageRef)image;
1002                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1003                 if (imageRep)
1004                 {
1005                     NSSize size = [imageRep size];
1007                     [nsimage addRepresentation:imageRep];
1008                     [imageRep release];
1010                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1011                         bestSize = size;
1012                 }
1013             }
1015             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1016                 [nsimage setSize:bestSize];
1017             else
1018                 nsimage = nil;
1019         }
1021         self.applicationIcon = nsimage;
1022     }
1024     - (void) handleCommandTab
1025     {
1026         if ([NSApp isActive])
1027         {
1028             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1029             NSRunningApplication* app;
1030             NSRunningApplication* otherValidApp = nil;
1032             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1033             {
1034                 NSNumber* displayID;
1035                 for (displayID in originalDisplayModes)
1036                 {
1037                     CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1038                     [latentDisplayModes setObject:(id)mode forKey:displayID];
1039                     CGDisplayModeRelease(mode);
1040                 }
1042                 CGRestorePermanentDisplayConfiguration();
1043                 CGReleaseAllDisplays();
1044                 [originalDisplayModes removeAllObjects];
1045                 displaysCapturedForFullscreen = FALSE;
1046             }
1048             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1049             {
1050                 if (![app isEqual:thisApp] && !app.terminated &&
1051                     app.activationPolicy == NSApplicationActivationPolicyRegular)
1052                 {
1053                     if (!app.hidden)
1054                     {
1055                         // There's another visible app.  Just hide ourselves and let
1056                         // the system activate the other app.
1057                         [NSApp hide:self];
1058                         return;
1059                     }
1061                     if (!otherValidApp)
1062                         otherValidApp = app;
1063                 }
1064             }
1066             // Didn't find a visible GUI app.  Try the Finder or, if that's not
1067             // running, the first hidden GUI app.  If even that doesn't work, we
1068             // just fail to switch and remain the active app.
1069             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1070             if (!app) app = otherValidApp;
1071             [app unhide];
1072             [app activateWithOptions:0];
1073         }
1074     }
1076     /*
1077      * ---------- Cursor clipping methods ----------
1078      *
1079      * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1080      * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1081      * equivalent: CGAssociateMouseAndMouseCursorPosition(false).  For the
1082      * general case, we leverage that.  We disassociate mouse movements from
1083      * the cursor position and then move the cursor manually, keeping it within
1084      * the clipping rectangle.
1085      *
1086      * Moving the cursor manually isn't enough.  We need to modify the event
1087      * stream so that the events have the new location, too.  We need to do
1088      * this at a point before the events enter Cocoa, so that Cocoa will assign
1089      * the correct window to the event.  So, we install a Quartz event tap to
1090      * do that.
1091      *
1092      * Also, there's a complication when we move the cursor.  We use
1093      * CGWarpMouseCursorPosition().  That doesn't generate mouse movement
1094      * events, but the change of cursor position is incorporated into the
1095      * deltas of the next mouse move event.  When the mouse is disassociated
1096      * from the cursor position, we need the deltas to only reflect actual
1097      * device movement, not programmatic changes.  So, the event tap cancels
1098      * out the change caused by our calls to CGWarpMouseCursorPosition().
1099      */
1100     - (void) clipCursorLocation:(CGPoint*)location
1101     {
1102         if (location->x < CGRectGetMinX(cursorClipRect))
1103             location->x = CGRectGetMinX(cursorClipRect);
1104         if (location->y < CGRectGetMinY(cursorClipRect))
1105             location->y = CGRectGetMinY(cursorClipRect);
1106         if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1107             location->x = CGRectGetMaxX(cursorClipRect) - 1;
1108         if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1109             location->y = CGRectGetMaxY(cursorClipRect) - 1;
1110     }
1112     - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1113     {
1114         CGPoint oldLocation;
1116         if (currentLocation)
1117             oldLocation = *currentLocation;
1118         else
1119             oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1121         if (!CGPointEqualToPoint(oldLocation, *newLocation))
1122         {
1123             WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1124             CGError err;
1126             warpRecord.from = oldLocation;
1127             warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1129             /* Actually move the cursor. */
1130             err = CGWarpMouseCursorPosition(*newLocation);
1131             if (err != kCGErrorSuccess)
1132                 return FALSE;
1134             warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1135             *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1137             if (!CGPointEqualToPoint(oldLocation, *newLocation))
1138             {
1139                 warpRecord.to = *newLocation;
1140                 [warpRecords addObject:warpRecord];
1141             }
1142         }
1144         return TRUE;
1145     }
1147     - (BOOL) isMouseMoveEventType:(CGEventType)type
1148     {
1149         switch(type)
1150         {
1151         case kCGEventMouseMoved:
1152         case kCGEventLeftMouseDragged:
1153         case kCGEventRightMouseDragged:
1154         case kCGEventOtherMouseDragged:
1155             return TRUE;
1156         }
1158         return FALSE;
1159     }
1161     - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1162     {
1163         int warpsFinished = 0;
1164         for (WarpRecord* warpRecord in warpRecords)
1165         {
1166             if (warpRecord.timeAfter < eventTime ||
1167                 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1168                 warpsFinished++;
1169             else
1170                 break;
1171         }
1173         return warpsFinished;
1174     }
1176     - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1177                                 type:(CGEventType)type
1178                                event:(CGEventRef)event
1179     {
1180         CGEventTimestamp eventTime;
1181         CGPoint eventLocation, cursorLocation;
1183         if (type == kCGEventTapDisabledByUserInput)
1184             return event;
1185         if (type == kCGEventTapDisabledByTimeout)
1186         {
1187             CGEventTapEnable(cursorClippingEventTap, TRUE);
1188             return event;
1189         }
1191         if (!clippingCursor)
1192             return event;
1194         eventTime = CGEventGetTimestamp(event);
1195         lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1197         eventLocation = CGEventGetLocation(event);
1199         cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1201         if ([self isMouseMoveEventType:type])
1202         {
1203             double deltaX, deltaY;
1204             int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1205             int i;
1207             deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1208             deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1210             for (i = 0; i < warpsFinished; i++)
1211             {
1212                 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1213                 deltaX -= warpRecord.to.x - warpRecord.from.x;
1214                 deltaY -= warpRecord.to.y - warpRecord.from.y;
1215                 [warpRecords removeObjectAtIndex:0];
1216             }
1218             if (warpsFinished)
1219             {
1220                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1221                 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1222             }
1224             synthesizedLocation.x += deltaX;
1225             synthesizedLocation.y += deltaY;
1226         }
1228         // If the event is destined for another process, don't clip it.  This may
1229         // happen if the user activates Exposé or Mission Control.  In that case,
1230         // our app does not resign active status, so clipping is still in effect,
1231         // but the cursor should not actually be clipped.
1232         //
1233         // In addition, the fact that mouse moves may have been delivered to a
1234         // different process means we have to treat the next one we receive as
1235         // absolute rather than relative.
1236         if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1237             [self clipCursorLocation:&synthesizedLocation];
1238         else
1239             lastSetCursorPositionTime = lastEventTapEventTime;
1241         [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1242         if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1243             CGEventSetLocation(event, synthesizedLocation);
1245         return event;
1246     }
1248     CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1249                                        CGEventRef event, void *refcon)
1250     {
1251         WineApplicationController* controller = refcon;
1252         return [controller eventTapWithProxy:proxy type:type event:event];
1253     }
1255     - (BOOL) installEventTap
1256     {
1257         ProcessSerialNumber psn;
1258         OSErr err;
1259         CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown)        |
1260                            CGEventMaskBit(kCGEventLeftMouseUp)          |
1261                            CGEventMaskBit(kCGEventRightMouseDown)       |
1262                            CGEventMaskBit(kCGEventRightMouseUp)         |
1263                            CGEventMaskBit(kCGEventMouseMoved)           |
1264                            CGEventMaskBit(kCGEventLeftMouseDragged)     |
1265                            CGEventMaskBit(kCGEventRightMouseDragged)    |
1266                            CGEventMaskBit(kCGEventOtherMouseDown)       |
1267                            CGEventMaskBit(kCGEventOtherMouseUp)         |
1268                            CGEventMaskBit(kCGEventOtherMouseDragged)    |
1269                            CGEventMaskBit(kCGEventScrollWheel);
1270         CFRunLoopSourceRef source;
1271         void* appServices;
1272         OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1274         if (cursorClippingEventTap)
1275             return TRUE;
1277         // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1278         // framework with dlsym() because the Win32 function of the same name
1279         // obscures it.
1280         appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1281         if (!appServices)
1282             return FALSE;
1284         pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1285         if (!pGetCurrentProcess)
1286         {
1287             dlclose(appServices);
1288             return FALSE;
1289         }
1291         err = pGetCurrentProcess(&psn);
1292         dlclose(appServices);
1293         if (err != noErr)
1294             return FALSE;
1296         // We create an annotated session event tap rather than a process-specific
1297         // event tap because we need to programmatically move the cursor even when
1298         // mouse moves are directed to other processes.  We disable our tap when
1299         // other processes are active, but things like Exposé are handled by other
1300         // processes even when we remain active.
1301         cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1302             kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1303         if (!cursorClippingEventTap)
1304             return FALSE;
1306         CGEventTapEnable(cursorClippingEventTap, FALSE);
1308         source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1309         if (!source)
1310         {
1311             CFRelease(cursorClippingEventTap);
1312             cursorClippingEventTap = NULL;
1313             return FALSE;
1314         }
1316         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1317         CFRelease(source);
1318         return TRUE;
1319     }
1321     - (BOOL) setCursorPosition:(CGPoint)pos
1322     {
1323         BOOL ret;
1325         if ([windowsBeingDragged count])
1326             ret = FALSE;
1327         else if (clippingCursor)
1328         {
1329             [self clipCursorLocation:&pos];
1331             ret = [self warpCursorTo:&pos from:NULL];
1332             synthesizedLocation = pos;
1333             if (ret)
1334             {
1335                 // We want to discard mouse-move events that have already been
1336                 // through the event tap, because it's too late to account for
1337                 // the setting of the cursor position with them.  However, the
1338                 // events that may be queued with times after that but before
1339                 // the above warp can still be used.  So, use the last event
1340                 // tap event time so that -sendEvent: doesn't discard them.
1341                 lastSetCursorPositionTime = lastEventTapEventTime;
1342             }
1343         }
1344         else
1345         {
1346             // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1347             // the mouse from the cursor position for 0.25 seconds.  This means
1348             // that mouse movement during that interval doesn't move the cursor
1349             // and events carry a constant location (the warped-to position)
1350             // even though they have delta values.  For apps which warp the
1351             // cursor frequently (like after every mouse move), this makes
1352             // cursor movement horribly laggy and jerky, as only a fraction of
1353             // mouse move events have any effect.
1354             //
1355             // On some versions of OS X, it's sufficient to forcibly reassociate
1356             // the mouse and cursor position.  On others, it's necessary to set
1357             // the local events suppression interval to 0 for the warp.  That's
1358             // deprecated, but I'm not aware of any other way.  For good
1359             // measure, we do both.
1360             CGSetLocalEventsSuppressionInterval(0);
1361             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1362             CGSetLocalEventsSuppressionInterval(0.25);
1363             if (ret)
1364             {
1365                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1367                 CGAssociateMouseAndMouseCursorPosition(true);
1368             }
1369         }
1371         if (ret)
1372         {
1373             WineEventQueue* queue;
1375             // Discard all pending mouse move events.
1376             [eventQueuesLock lock];
1377             for (queue in eventQueues)
1378             {
1379                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1380                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1381                                        forWindow:nil];
1382                 [queue resetMouseEventPositions:pos];
1383             }
1384             [eventQueuesLock unlock];
1385         }
1387         return ret;
1388     }
1390     - (void) activateCursorClipping
1391     {
1392         if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1393         {
1394             CGEventTapEnable(cursorClippingEventTap, TRUE);
1395             [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1396         }
1397     }
1399     - (void) deactivateCursorClipping
1400     {
1401         if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1402         {
1403             CGEventTapEnable(cursorClippingEventTap, FALSE);
1404             [warpRecords removeAllObjects];
1405             lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1406         }
1407     }
1409     - (void) updateCursorClippingState
1410     {
1411         if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1412             [self activateCursorClipping];
1413         else
1414             [self deactivateCursorClipping];
1415     }
1417     - (void) updateWindowsForCursorClipping
1418     {
1419         WineWindow* window;
1420         for (window in [NSApp windows])
1421         {
1422             if ([window isKindOfClass:[WineWindow class]])
1423                 [window updateForCursorClipping];
1424         }
1425     }
1427     - (BOOL) startClippingCursor:(CGRect)rect
1428     {
1429         CGError err;
1431         if (!cursorClippingEventTap && ![self installEventTap])
1432             return FALSE;
1434         if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1435             CGEventTapIsEnabled(cursorClippingEventTap))
1436             return TRUE;
1438         err = CGAssociateMouseAndMouseCursorPosition(false);
1439         if (err != kCGErrorSuccess)
1440             return FALSE;
1442         clippingCursor = TRUE;
1443         cursorClipRect = rect;
1444         [self updateCursorClippingState];
1445         [self updateWindowsForCursorClipping];
1447         return TRUE;
1448     }
1450     - (BOOL) stopClippingCursor
1451     {
1452         CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1453         if (err != kCGErrorSuccess)
1454             return FALSE;
1456         clippingCursor = FALSE;
1457         [self updateCursorClippingState];
1458         [self updateWindowsForCursorClipping];
1460         return TRUE;
1461     }
1463     - (BOOL) isKeyPressed:(uint16_t)keyCode
1464     {
1465         int bits = sizeof(pressedKeyCodes[0]) * 8;
1466         int index = keyCode / bits;
1467         uint32_t mask = 1 << (keyCode % bits);
1468         return (pressedKeyCodes[index] & mask) != 0;
1469     }
1471     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1472     {
1473         int bits = sizeof(pressedKeyCodes[0]) * 8;
1474         int index = keyCode / bits;
1475         uint32_t mask = 1 << (keyCode % bits);
1476         if (pressed)
1477             pressedKeyCodes[index] |= mask;
1478         else
1479             pressedKeyCodes[index] &= ~mask;
1480     }
1482     - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1483     {
1484         if (dragged)
1485             [windowsBeingDragged addObject:window];
1486         else
1487             [windowsBeingDragged removeObject:window];
1488         [self updateCursorClippingState];
1489     }
1491     - (void) handleMouseMove:(NSEvent*)anEvent
1492     {
1493         WineWindow* targetWindow;
1494         BOOL drag = [anEvent type] != NSMouseMoved;
1496         if ([windowsBeingDragged count])
1497             targetWindow = nil;
1498         else if (mouseCaptureWindow)
1499             targetWindow = mouseCaptureWindow;
1500         else if (drag)
1501             targetWindow = (WineWindow*)[anEvent window];
1502         else
1503         {
1504             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1505                event indicates its window is the main window, even if the cursor is
1506                over a different window.  Find the actual WineWindow that is under the
1507                cursor and post the event as being for that window. */
1508             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1509             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1510             NSInteger windowUnderNumber;
1512             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1513                                   belowWindowWithWindowNumber:0];
1514             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1515             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1516                 targetWindow = nil;
1517         }
1519         if ([targetWindow isKindOfClass:[WineWindow class]])
1520         {
1521             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1522             macdrv_event* event;
1523             BOOL absolute;
1525             // If we recently warped the cursor (other than in our cursor-clipping
1526             // event tap), discard mouse move events until we see an event which is
1527             // later than that time.
1528             if (lastSetCursorPositionTime)
1529             {
1530                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1531                     return;
1533                 lastSetCursorPositionTime = 0;
1534                 forceNextMouseMoveAbsolute = TRUE;
1535             }
1537             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1538             {
1539                 absolute = TRUE;
1540                 forceNextMouseMoveAbsolute = FALSE;
1541             }
1542             else
1543             {
1544                 // Send absolute move events if the cursor is in the interior of
1545                 // its range.  Only send relative moves if the cursor is pinned to
1546                 // the boundaries of where it can go.  We compute the position
1547                 // that's one additional point in the direction of movement.  If
1548                 // that is outside of the clipping rect or desktop region (the
1549                 // union of the screen frames), then we figure the cursor would
1550                 // have moved outside if it could but it was pinned.
1551                 CGPoint computedPoint = point;
1552                 CGFloat deltaX = [anEvent deltaX];
1553                 CGFloat deltaY = [anEvent deltaY];
1555                 if (deltaX > 0.001)
1556                     computedPoint.x++;
1557                 else if (deltaX < -0.001)
1558                     computedPoint.x--;
1560                 if (deltaY > 0.001)
1561                     computedPoint.y++;
1562                 else if (deltaY < -0.001)
1563                     computedPoint.y--;
1565                 // Assume cursor is pinned for now
1566                 absolute = FALSE;
1567                 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1568                 {
1569                     const CGRect* rects;
1570                     NSUInteger count, i;
1572                     // Caches screenFrameCGRects if necessary
1573                     [self primaryScreenHeight];
1575                     rects = [screenFrameCGRects bytes];
1576                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1578                     for (i = 0; i < count; i++)
1579                     {
1580                         if (CGRectContainsPoint(rects[i], computedPoint))
1581                         {
1582                             absolute = TRUE;
1583                             break;
1584                         }
1585                     }
1586                 }
1587             }
1589             if (absolute)
1590             {
1591                 if (clippingCursor)
1592                     [self clipCursorLocation:&point];
1594                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1595                 event->mouse_moved.x = point.x;
1596                 event->mouse_moved.y = point.y;
1598                 mouseMoveDeltaX = 0;
1599                 mouseMoveDeltaY = 0;
1600             }
1601             else
1602             {
1603                 /* Add event delta to accumulated delta error */
1604                 /* deltaY is already flipped */
1605                 mouseMoveDeltaX += [anEvent deltaX];
1606                 mouseMoveDeltaY += [anEvent deltaY];
1608                 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1609                 event->mouse_moved.x = mouseMoveDeltaX;
1610                 event->mouse_moved.y = mouseMoveDeltaY;
1612                 /* Keep the remainder after integer truncation. */
1613                 mouseMoveDeltaX -= event->mouse_moved.x;
1614                 mouseMoveDeltaY -= event->mouse_moved.y;
1615             }
1617             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1618             {
1619                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1620                 event->mouse_moved.drag = drag;
1622                 [targetWindow.queue postEvent:event];
1623             }
1625             macdrv_release_event(event);
1627             lastTargetWindow = targetWindow;
1628         }
1629         else
1630             lastTargetWindow = nil;
1632         [self updateCursor:FALSE];
1633     }
1635     - (void) handleMouseButton:(NSEvent*)theEvent
1636     {
1637         WineWindow* window = (WineWindow*)[theEvent window];
1638         NSEventType type = [theEvent type];
1639         WineWindow* windowBroughtForward = nil;
1640         BOOL process = FALSE;
1642         if ([window isKindOfClass:[WineWindow class]] &&
1643             type == NSLeftMouseDown &&
1644             (([theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask| NSAlternateKeyMask | NSCommandKeyMask)) != NSCommandKeyMask))
1645         {
1646             NSWindowButton windowButton;
1648             windowBroughtForward = window;
1650             /* Any left-click on our window anyplace other than the close or
1651                minimize buttons will bring it forward. */
1652             for (windowButton = NSWindowCloseButton;
1653                  windowButton <= NSWindowMiniaturizeButton;
1654                  windowButton++)
1655             {
1656                 NSButton* button = [window standardWindowButton:windowButton];
1657                 if (button)
1658                 {
1659                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1660                     if ([button mouse:point inRect:[button bounds]])
1661                     {
1662                         windowBroughtForward = nil;
1663                         break;
1664                     }
1665                 }
1666             }
1667         }
1669         if ([windowsBeingDragged count])
1670             window = nil;
1671         else if (mouseCaptureWindow)
1672             window = mouseCaptureWindow;
1674         if ([window isKindOfClass:[WineWindow class]])
1675         {
1676             BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1677             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1679             if (clippingCursor)
1680                 [self clipCursorLocation:&pt];
1682             if (pressed)
1683             {
1684                 if (mouseCaptureWindow)
1685                     process = TRUE;
1686                 else
1687                 {
1688                     // Test if the click was in the window's content area.
1689                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1690                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1691                     process = NSMouseInRect(nspoint, contentRect, NO);
1692                     if (process && [window styleMask] & NSResizableWindowMask)
1693                     {
1694                         // Ignore clicks in the grow box (resize widget).
1695                         HIPoint origin = { 0, 0 };
1696                         HIThemeGrowBoxDrawInfo info = { 0 };
1697                         HIRect bounds;
1698                         OSStatus status;
1700                         info.kind = kHIThemeGrowBoxKindNormal;
1701                         info.direction = kThemeGrowRight | kThemeGrowDown;
1702                         if ([window styleMask] & NSUtilityWindowMask)
1703                             info.size = kHIThemeGrowBoxSizeSmall;
1704                         else
1705                             info.size = kHIThemeGrowBoxSizeNormal;
1707                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1708                         if (status == noErr)
1709                         {
1710                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1711                                                         NSMinY(contentRect),
1712                                                         bounds.size.width,
1713                                                         bounds.size.height);
1714                             process = !NSMouseInRect(nspoint, growBox, NO);
1715                         }
1716                     }
1717                 }
1718                 if (process)
1719                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1720             }
1721             else
1722             {
1723                 NSEventType downType = type - 1;
1724                 NSUInteger downMask = NSEventMaskFromType(downType);
1725                 process = (unmatchedMouseDowns & downMask) != 0;
1726                 unmatchedMouseDowns &= ~downMask;
1727             }
1729             if (process)
1730             {
1731                 macdrv_event* event;
1733                 event = macdrv_create_event(MOUSE_BUTTON, window);
1734                 event->mouse_button.button = [theEvent buttonNumber];
1735                 event->mouse_button.pressed = pressed;
1736                 event->mouse_button.x = pt.x;
1737                 event->mouse_button.y = pt.y;
1738                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1740                 [window.queue postEvent:event];
1742                 macdrv_release_event(event);
1743             }
1744         }
1746         if (windowBroughtForward)
1747         {
1748             WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1749             NSInteger ancestorNumber = [ancestor windowNumber];
1750             NSInteger ancestorLevel = [ancestor level];
1752             for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1753             {
1754                 NSInteger windowNumber = [windowNumberObject integerValue];
1755                 if (windowNumber == ancestorNumber)
1756                     break;
1757                 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1758                 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1759                     [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1760                 {
1761                     [ancestor postBroughtForwardEvent];
1762                     break;
1763                 }
1764             }
1765             if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate)
1766                 [self windowGotFocus:windowBroughtForward];
1767         }
1769         // Since mouse button events deliver absolute cursor position, the
1770         // accumulating delta from move events is invalidated.  Make sure
1771         // next mouse move event starts over from an absolute baseline.
1772         // Also, it's at least possible that the title bar widgets (e.g. close
1773         // button, etc.) could enter an internal event loop on a mouse down that
1774         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1775         // dragged events and, after that, any notion of the cursor position
1776         // computed from accumulating deltas would be wrong.
1777         forceNextMouseMoveAbsolute = TRUE;
1778     }
1780     - (void) handleScrollWheel:(NSEvent*)theEvent
1781     {
1782         WineWindow* window;
1784         if (mouseCaptureWindow)
1785             window = mouseCaptureWindow;
1786         else
1787             window = (WineWindow*)[theEvent window];
1789         if ([window isKindOfClass:[WineWindow class]])
1790         {
1791             CGEventRef cgevent = [theEvent CGEvent];
1792             CGPoint pt = CGEventGetLocation(cgevent);
1793             BOOL process;
1795             if (clippingCursor)
1796                 [self clipCursorLocation:&pt];
1798             if (mouseCaptureWindow)
1799                 process = TRUE;
1800             else
1801             {
1802                 // Only process the event if it was in the window's content area.
1803                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1804                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1805                 process = NSMouseInRect(nspoint, contentRect, NO);
1806             }
1808             if (process)
1809             {
1810                 macdrv_event* event;
1811                 double x, y;
1812                 BOOL continuous = FALSE;
1814                 event = macdrv_create_event(MOUSE_SCROLL, window);
1815                 event->mouse_scroll.x = pt.x;
1816                 event->mouse_scroll.y = pt.y;
1817                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1819                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1820                 {
1821                     continuous = TRUE;
1823                     /* Continuous scroll wheel events come from high-precision scrolling
1824                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1825                        For these, we can get more precise data from the CGEvent API. */
1826                     /* Axis 1 is vertical, axis 2 is horizontal. */
1827                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1828                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1829                 }
1830                 else
1831                 {
1832                     double pixelsPerLine = 10;
1833                     CGEventSourceRef source;
1835                     /* The non-continuous values are in units of "lines", not pixels. */
1836                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1837                     {
1838                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1839                         CFRelease(source);
1840                     }
1842                     x = pixelsPerLine * [theEvent deltaX];
1843                     y = pixelsPerLine * [theEvent deltaY];
1844                 }
1846                 /* Mac: negative is right or down, positive is left or up.
1847                    Win32: negative is left or down, positive is right or up.
1848                    So, negate the X scroll value to translate. */
1849                 x = -x;
1851                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1852                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1853                    6 times the pixel value. */
1854                 x *= 6;
1855                 y *= 6;
1857                 if (use_precise_scrolling)
1858                 {
1859                     event->mouse_scroll.x_scroll = x;
1860                     event->mouse_scroll.y_scroll = y;
1862                     if (!continuous)
1863                     {
1864                         /* For non-continuous "clicky" wheels, if there was any motion, make
1865                            sure there was at least WHEEL_DELTA motion.  This is so, at slow
1866                            speeds where the system's acceleration curve is actually reducing the
1867                            scroll distance, the user is sure to get some action out of each click.
1868                            For example, this is important for rotating though weapons in a
1869                            first-person shooter. */
1870                         if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1871                             event->mouse_scroll.x_scroll = 120;
1872                         else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1873                             event->mouse_scroll.x_scroll = -120;
1875                         if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1876                             event->mouse_scroll.y_scroll = 120;
1877                         else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1878                             event->mouse_scroll.y_scroll = -120;
1879                     }
1880                 }
1881                 else
1882                 {
1883                     /* If it's been a while since the last scroll event or if the scrolling has
1884                        reversed direction, reset the accumulated scroll value. */
1885                     if ([theEvent timestamp] - lastScrollTime > 1)
1886                         accumScrollX = accumScrollY = 0;
1887                     else
1888                     {
1889                         /* The accumulated scroll value is in the opposite direction/sign of the last
1890                            scroll.  That's because it's the "debt" resulting from over-scrolling in
1891                            that direction.  We accumulate by adding in the scroll amount and then, if
1892                            it has the same sign as the scroll value, we subtract any whole or partial
1893                            WHEEL_DELTAs, leaving it 0 or the opposite sign.  So, the user switched
1894                            scroll direction if the accumulated debt and the new scroll value have the
1895                            same sign. */
1896                         if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1897                             accumScrollX = 0;
1898                         if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1899                             accumScrollY = 0;
1900                     }
1901                     lastScrollTime = [theEvent timestamp];
1903                     accumScrollX += x;
1904                     accumScrollY += y;
1906                     if (accumScrollX > 0 && x > 0)
1907                         event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1908                     if (accumScrollX < 0 && x < 0)
1909                         event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1910                     if (accumScrollY > 0 && y > 0)
1911                         event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1912                     if (accumScrollY < 0 && y < 0)
1913                         event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1915                     accumScrollX -= event->mouse_scroll.x_scroll;
1916                     accumScrollY -= event->mouse_scroll.y_scroll;
1917                 }
1919                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1920                     [window.queue postEvent:event];
1922                 macdrv_release_event(event);
1924                 // Since scroll wheel events deliver absolute cursor position, the
1925                 // accumulating delta from move events is invalidated.  Make sure next
1926                 // mouse move event starts over from an absolute baseline.
1927                 forceNextMouseMoveAbsolute = TRUE;
1928             }
1929         }
1930     }
1932     // Returns TRUE if the event was handled and caller should do nothing more
1933     // with it.  Returns FALSE if the caller should process it as normal and
1934     // then call -didSendEvent:.
1935     - (BOOL) handleEvent:(NSEvent*)anEvent
1936     {
1937         BOOL ret = FALSE;
1938         NSEventType type = [anEvent type];
1940         if (type == NSFlagsChanged)
1941             self.lastFlagsChanged = anEvent;
1942         else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
1943                  type == NSRightMouseDragged || type == NSOtherMouseDragged)
1944         {
1945             [self handleMouseMove:anEvent];
1946             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1947         }
1948         else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
1949                  type == NSRightMouseDown || type == NSRightMouseUp ||
1950                  type == NSOtherMouseDown || type == NSOtherMouseUp)
1951         {
1952             [self handleMouseButton:anEvent];
1953             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1954         }
1955         else if (type == NSScrollWheel)
1956         {
1957             [self handleScrollWheel:anEvent];
1958             ret = mouseCaptureWindow != nil;
1959         }
1960         else if (type == NSKeyUp)
1961         {
1962             uint16_t keyCode = [anEvent keyCode];
1963             if ([self isKeyPressed:keyCode])
1964             {
1965                 WineWindow* window = (WineWindow*)[anEvent window];
1966                 [self noteKey:keyCode pressed:FALSE];
1967                 if ([window isKindOfClass:[WineWindow class]])
1968                     [window postKeyEvent:anEvent];
1969             }
1970         }
1971         else if (type == NSAppKitDefined)
1972         {
1973             short subtype = [anEvent subtype];
1975             // These subtypes are not documented but they appear to mean
1976             // "a window is being dragged" and "a window is no longer being
1977             // dragged", respectively.
1978             if (subtype == 20 || subtype == 21)
1979             {
1980                 WineWindow* window = (WineWindow*)[anEvent window];
1981                 if ([window isKindOfClass:[WineWindow class]])
1982                 {
1983                     macdrv_event* event;
1984                     int eventType;
1986                     if (subtype == 20)
1987                     {
1988                         [windowsBeingDragged addObject:window];
1989                         eventType = WINDOW_DRAG_BEGIN;
1990                     }
1991                     else
1992                     {
1993                         [windowsBeingDragged removeObject:window];
1994                         eventType = WINDOW_DRAG_END;
1995                     }
1996                     [self updateCursorClippingState];
1998                     event = macdrv_create_event(eventType, window);
1999                     [window.queue postEvent:event];
2000                     macdrv_release_event(event);
2001                 }
2002             }
2003         }
2005         return ret;
2006     }
2008     - (void) didSendEvent:(NSEvent*)anEvent
2009     {
2010         NSEventType type = [anEvent type];
2012         if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
2013         {
2014             NSUInteger modifiers = [anEvent modifierFlags];
2015             if ((modifiers & NSCommandKeyMask) &&
2016                 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
2017             {
2018                 // Command-Tab and Command-Shift-Tab would normally be intercepted
2019                 // by the system to switch applications.  If we're seeing it, it's
2020                 // presumably because we've captured the displays, preventing
2021                 // normal application switching.  Do it manually.
2022                 [self handleCommandTab];
2023             }
2024         }
2025     }
2027     - (void) setupObservations
2028     {
2029         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2030         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
2031         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
2033         [nc addObserverForName:NSWindowDidBecomeKeyNotification
2034                         object:nil
2035                          queue:nil
2036                     usingBlock:^(NSNotification *note){
2037             NSWindow* window = [note object];
2038             [keyWindows removeObjectIdenticalTo:window];
2039             [keyWindows insertObject:window atIndex:0];
2040         }];
2042         [nc addObserverForName:NSWindowWillCloseNotification
2043                         object:nil
2044                          queue:[NSOperationQueue mainQueue]
2045                     usingBlock:^(NSNotification *note){
2046             NSWindow* window = [note object];
2047             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
2048                 return;
2049             [keyWindows removeObjectIdenticalTo:window];
2050             if (window == lastTargetWindow)
2051                 lastTargetWindow = nil;
2052             if (window == self.mouseCaptureWindow)
2053                 self.mouseCaptureWindow = nil;
2054             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
2055             {
2056                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
2057                     [self updateFullscreenWindows];
2058                 });
2059             }
2060             [windowsBeingDragged removeObject:window];
2061             [self updateCursorClippingState];
2062         }];
2064         [nc addObserver:self
2065                selector:@selector(keyboardSelectionDidChange)
2066                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
2067                  object:nil];
2069         /* The above notification isn't sent unless the NSTextInputContext
2070            class has initialized itself.  Poke it. */
2071         [NSTextInputContext self];
2073         [wsnc addObserver:self
2074                  selector:@selector(activeSpaceDidChange)
2075                      name:NSWorkspaceActiveSpaceDidChangeNotification
2076                    object:nil];
2078         [nc addObserver:self
2079                selector:@selector(releaseMouseCapture)
2080                    name:NSMenuDidBeginTrackingNotification
2081                  object:nil];
2083         [dnc        addObserver:self
2084                        selector:@selector(releaseMouseCapture)
2085                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2086                          object:nil
2087              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2089         [dnc addObserver:self
2090                 selector:@selector(enabledKeyboardInputSourcesChanged)
2091                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2092                   object:nil];
2093     }
2095     - (BOOL) inputSourceIsInputMethod
2096     {
2097         if (!inputSourceIsInputMethodValid)
2098         {
2099             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2100             if (inputSource)
2101             {
2102                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2103                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2104                 CFRelease(inputSource);
2105             }
2106             else
2107                 inputSourceIsInputMethod = FALSE;
2108             inputSourceIsInputMethodValid = TRUE;
2109         }
2111         return inputSourceIsInputMethod;
2112     }
2114     - (void) releaseMouseCapture
2115     {
2116         // This might be invoked on a background thread by the distributed
2117         // notification center.  Shunt it to the main thread.
2118         if (![NSThread isMainThread])
2119         {
2120             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2121             return;
2122         }
2124         if (mouseCaptureWindow)
2125         {
2126             macdrv_event* event;
2128             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2129             [mouseCaptureWindow.queue postEvent:event];
2130             macdrv_release_event(event);
2131         }
2132     }
2134     - (void) unminimizeWindowIfNoneVisible
2135     {
2136         if (![self frontWineWindow])
2137         {
2138             for (WineWindow* window in [NSApp windows])
2139             {
2140                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2141                 {
2142                     [window deminiaturize:self];
2143                     break;
2144                 }
2145             }
2146         }
2147     }
2150     /*
2151      * ---------- NSApplicationDelegate methods ----------
2152      */
2153     - (void)applicationDidBecomeActive:(NSNotification *)notification
2154     {
2155         NSNumber* displayID;
2156         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2158         latentDisplayModes = [[NSMutableDictionary alloc] init];
2159         for (displayID in modesToRealize)
2160         {
2161             CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2162             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2163         }
2165         [self updateCursorClippingState];
2167         [self updateFullscreenWindows];
2168         [self adjustWindowLevels:YES];
2170         if (beenActive)
2171             [self unminimizeWindowIfNoneVisible];
2172         beenActive = TRUE;
2174         // If a Wine process terminates abruptly while it has the display captured
2175         // and switched to a different resolution, Mac OS X will uncapture the
2176         // displays and switch their resolutions back.  However, the other Wine
2177         // processes won't have their notion of the desktop rect changed back.
2178         // This can lead them to refuse to draw or acknowledge clicks in certain
2179         // portions of their windows.
2180         //
2181         // To solve this, we synthesize a displays-changed event whenever we're
2182         // activated.  This will provoke a re-synchronization of Wine's notion of
2183         // the desktop rect with the actual state.
2184         [self sendDisplaysChanged:TRUE];
2186         // The cursor probably moved while we were inactive.  Accumulated mouse
2187         // movement deltas are invalidated.  Make sure the next mouse move event
2188         // starts over from an absolute baseline.
2189         forceNextMouseMoveAbsolute = TRUE;
2190     }
2192     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2193     {
2194         primaryScreenHeightValid = FALSE;
2195         [self sendDisplaysChanged:FALSE];
2196         [self adjustWindowLevels];
2198         // When the display configuration changes, the cursor position may jump.
2199         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2200         // mouse move event starts over from an absolute baseline.
2201         forceNextMouseMoveAbsolute = TRUE;
2202     }
2204     - (void)applicationDidResignActive:(NSNotification *)notification
2205     {
2206         macdrv_event* event;
2207         WineEventQueue* queue;
2209         [self updateCursorClippingState];
2211         [self invalidateGotFocusEvents];
2213         event = macdrv_create_event(APP_DEACTIVATED, nil);
2215         [eventQueuesLock lock];
2216         for (queue in eventQueues)
2217             [queue postEvent:event];
2218         [eventQueuesLock unlock];
2220         macdrv_release_event(event);
2222         [self releaseMouseCapture];
2223     }
2225     - (void) applicationDidUnhide:(NSNotification*)aNotification
2226     {
2227         [self adjustWindowLevels];
2228     }
2230     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2231     {
2232         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2233         // don't count as "visible windows" for this purpose.
2234         [self unminimizeWindowIfNoneVisible];
2235         return YES;
2236     }
2238     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2239     {
2240         NSApplicationTerminateReply ret = NSTerminateNow;
2241         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2242         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2243         macdrv_event* event;
2244         WineEventQueue* queue;
2246         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2247         event->deliver = 1;
2248         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2249         {
2250             case kAELogOut:
2251             case kAEReallyLogOut:
2252                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2253                 break;
2254             case kAEShowRestartDialog:
2255                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2256                 break;
2257             case kAEShowShutdownDialog:
2258                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2259                 break;
2260             default:
2261                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2262                 break;
2263         }
2265         [eventQueuesLock lock];
2267         if ([eventQueues count])
2268         {
2269             for (queue in eventQueues)
2270                 [queue postEvent:event];
2271             ret = NSTerminateLater;
2272         }
2274         [eventQueuesLock unlock];
2276         macdrv_release_event(event);
2278         return ret;
2279     }
2281     - (void)applicationWillResignActive:(NSNotification *)notification
2282     {
2283         [self adjustWindowLevels:NO];
2284     }
2286 /***********************************************************************
2287  *              PerformRequest
2289  * Run-loop-source perform callback.  Pull request blocks from the
2290  * array of queued requests and invoke them.
2291  */
2292 static void PerformRequest(void *info)
2294     WineApplicationController* controller = [WineApplicationController sharedController];
2296     for (;;)
2297     {
2298         NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2299         __block dispatch_block_t block;
2301         dispatch_sync(controller->requestsManipQueue, ^{
2302             if ([controller->requests count])
2303             {
2304                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2305                 [controller->requests removeObjectAtIndex:0];
2306             }
2307             else
2308                 block = nil;
2309         });
2311         if (!block)
2312         {
2313             [pool release];
2314             break;
2315         }
2317         block();
2318         [block release];
2319         [pool release];
2320     }
2323 /***********************************************************************
2324  *              OnMainThreadAsync
2326  * Run a block on the main thread asynchronously.
2327  */
2328 void OnMainThreadAsync(dispatch_block_t block)
2330     WineApplicationController* controller = [WineApplicationController sharedController];
2332     block = [block copy];
2333     dispatch_sync(controller->requestsManipQueue, ^{
2334         [controller->requests addObject:block];
2335     });
2336     [block release];
2337     CFRunLoopSourceSignal(controller->requestSource);
2338     CFRunLoopWakeUp(CFRunLoopGetMain());
2341 @end
2343 /***********************************************************************
2344  *              LogError
2345  */
2346 void LogError(const char* func, NSString* format, ...)
2348     va_list args;
2349     va_start(args, format);
2350     LogErrorv(func, format, args);
2351     va_end(args);
2354 /***********************************************************************
2355  *              LogErrorv
2356  */
2357 void LogErrorv(const char* func, NSString* format, va_list args)
2359     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2361     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2362     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2363     [message release];
2365     [pool release];
2368 /***********************************************************************
2369  *              macdrv_window_rejected_focus
2371  * Pass focus to the next window that hasn't already rejected this same
2372  * WINDOW_GOT_FOCUS event.
2373  */
2374 void macdrv_window_rejected_focus(const macdrv_event *event)
2376     OnMainThread(^{
2377         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2378     });
2381 /***********************************************************************
2382  *              macdrv_get_input_source_info
2384  * Returns the keyboard layout uchr data, keyboard type and input source.
2385  */
2386 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2388     OnMainThread(^{
2389         TISInputSourceRef inputSourceLayout;
2391         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2392         if (inputSourceLayout)
2393         {
2394             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2395                                 kTISPropertyUnicodeKeyLayoutData);
2396             *uchr = CFDataCreateCopy(NULL, data);
2397             CFRelease(inputSourceLayout);
2399             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2400             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2401             *input_source = TISCopyCurrentKeyboardInputSource();
2402         }
2403     });
2406 /***********************************************************************
2407  *              macdrv_beep
2409  * Play the beep sound configured by the user in System Preferences.
2410  */
2411 void macdrv_beep(void)
2413     OnMainThreadAsync(^{
2414         NSBeep();
2415     });
2418 /***********************************************************************
2419  *              macdrv_set_display_mode
2420  */
2421 int macdrv_set_display_mode(const struct macdrv_display* display,
2422                             CGDisplayModeRef display_mode)
2424     __block int ret;
2426     OnMainThread(^{
2427         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2428     });
2430     return ret;
2433 /***********************************************************************
2434  *              macdrv_set_cursor
2436  * Set the cursor.
2438  * If name is non-NULL, it is a selector for a class method on NSCursor
2439  * identifying the cursor to set.  In that case, frames is ignored.  If
2440  * name is NULL, then frames is used.
2442  * frames is an array of dictionaries.  Each dictionary is a frame of
2443  * an animated cursor.  Under the key "image" is a CGImage for the
2444  * frame.  Under the key "duration" is a CFNumber time interval, in
2445  * seconds, for how long that frame is presented before proceeding to
2446  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2447  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2448  * This is the hot spot, measured in pixels down and to the right of the
2449  * top-left corner of the image.
2451  * If the array has exactly 1 element, the cursor is static, not
2452  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2453  */
2454 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2456     SEL sel;
2458     sel = NSSelectorFromString((NSString*)name);
2459     if (sel)
2460     {
2461         OnMainThreadAsync(^{
2462             WineApplicationController* controller = [WineApplicationController sharedController];
2463             [controller setCursorWithFrames:nil];
2464             controller.cursor = [NSCursor performSelector:sel];
2465             [controller unhideCursor];
2466         });
2467     }
2468     else
2469     {
2470         NSArray* nsframes = (NSArray*)frames;
2471         if ([nsframes count])
2472         {
2473             OnMainThreadAsync(^{
2474                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2475             });
2476         }
2477         else
2478         {
2479             OnMainThreadAsync(^{
2480                 WineApplicationController* controller = [WineApplicationController sharedController];
2481                 [controller setCursorWithFrames:nil];
2482                 [controller hideCursor];
2483             });
2484         }
2485     }
2488 /***********************************************************************
2489  *              macdrv_get_cursor_position
2491  * Obtains the current cursor position.  Returns zero on failure,
2492  * non-zero on success.
2493  */
2494 int macdrv_get_cursor_position(CGPoint *pos)
2496     OnMainThread(^{
2497         NSPoint location = [NSEvent mouseLocation];
2498         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2499         *pos = NSPointToCGPoint(location);
2500     });
2502     return TRUE;
2505 /***********************************************************************
2506  *              macdrv_set_cursor_position
2508  * Sets the cursor position without generating events.  Returns zero on
2509  * failure, non-zero on success.
2510  */
2511 int macdrv_set_cursor_position(CGPoint pos)
2513     __block int ret;
2515     OnMainThread(^{
2516         ret = [[WineApplicationController sharedController] setCursorPosition:pos];
2517     });
2519     return ret;
2522 /***********************************************************************
2523  *              macdrv_clip_cursor
2525  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2526  * to or larger than the whole desktop region, the cursor is unclipped.
2527  * Returns zero on failure, non-zero on success.
2528  */
2529 int macdrv_clip_cursor(CGRect rect)
2531     __block int ret;
2533     OnMainThread(^{
2534         WineApplicationController* controller = [WineApplicationController sharedController];
2535         BOOL clipping = FALSE;
2537         if (!CGRectIsInfinite(rect))
2538         {
2539             NSRect nsrect = NSRectFromCGRect(rect);
2540             NSScreen* screen;
2542             /* Convert the rectangle from top-down coords to bottom-up. */
2543             [controller flipRect:&nsrect];
2545             clipping = FALSE;
2546             for (screen in [NSScreen screens])
2547             {
2548                 if (!NSContainsRect(nsrect, [screen frame]))
2549                 {
2550                     clipping = TRUE;
2551                     break;
2552                 }
2553             }
2554         }
2556         if (clipping)
2557             ret = [controller startClippingCursor:rect];
2558         else
2559             ret = [controller stopClippingCursor];
2560     });
2562     return ret;
2565 /***********************************************************************
2566  *              macdrv_set_application_icon
2568  * Set the application icon.  The images array contains CGImages.  If
2569  * there are more than one, then they represent different sizes or
2570  * color depths from the icon resource.  If images is NULL or empty,
2571  * restores the default application image.
2572  */
2573 void macdrv_set_application_icon(CFArrayRef images)
2575     NSArray* imageArray = (NSArray*)images;
2577     OnMainThreadAsync(^{
2578         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2579     });
2582 /***********************************************************************
2583  *              macdrv_quit_reply
2584  */
2585 void macdrv_quit_reply(int reply)
2587     OnMainThread(^{
2588         [NSApp replyToApplicationShouldTerminate:reply];
2589     });
2592 /***********************************************************************
2593  *              macdrv_using_input_method
2594  */
2595 int macdrv_using_input_method(void)
2597     __block BOOL ret;
2599     OnMainThread(^{
2600         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2601     });
2603     return ret;
2606 /***********************************************************************
2607  *              macdrv_set_mouse_capture_window
2608  */
2609 void macdrv_set_mouse_capture_window(macdrv_window window)
2611     WineWindow* w = (WineWindow*)window;
2613     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2615     OnMainThread(^{
2616         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2617     });
2620 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2621 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2622 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2624 /***********************************************************************
2625  *              macdrv_create_input_source_list
2626  */
2627 CFArrayRef macdrv_create_input_source_list(void)
2629     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2631     OnMainThread(^{
2632         CFArrayRef input_list;
2633         CFDictionaryRef filter_dict;
2634         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2635         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2636         int i;
2638         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2639                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2640         input_list = TISCreateInputSourceList(filter_dict, false);
2642         for (i = 0; i < CFArrayGetCount(input_list); i++)
2643         {
2644             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2645             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2646             CFDictionaryRef entry;
2647             const void *input_keys[3] = { macdrv_input_source_input_key,
2648                                           macdrv_input_source_type_key,
2649                                           macdrv_input_source_lang_key };
2650             const void *input_values[3];
2652             input_values[0] = input;
2653             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2654             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2656             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2657                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2659             CFArrayAppendValue(ret, entry);
2660             CFRelease(entry);
2661         }
2662         CFRelease(input_list);
2663         CFRelease(filter_dict);
2664     });
2666     return ret;
2669 int macdrv_select_input_source(TISInputSourceRef input_source)
2671     __block int ret = FALSE;
2673     OnMainThread(^{
2674         ret = (TISSelectInputSource(input_source) == noErr);
2675     });
2677     return ret;