dbghelp: Properly fail on PDB files generated by MSVC compiler version 14.31.
[wine.git] / dlls / winemac.drv / cocoa_app.m
blobb5a3059382e9478ea2e0970de75cb198af9bf00f
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>
23 #import "cocoa_app.h"
24 #import "cocoa_cursorclipping.h"
25 #import "cocoa_event.h"
26 #import "cocoa_window.h"
29 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
31 // Private notifications that are reliably dispatched when a window is moved by dragging its titlebar.
32 // The object of the notification is the window being dragged.
33 // Available in macOS 10.12+
34 static NSString* const NSWindowWillStartDraggingNotification = @"NSWindowWillStartDraggingNotification";
35 static NSString* const NSWindowDidEndDraggingNotification = @"NSWindowDidEndDraggingNotification";
38 int macdrv_err_on;
41 #if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
42 @interface NSWindow (WineAutoTabbingExtensions)
44     + (void) setAllowsAutomaticWindowTabbing:(BOOL)allows;
46 @end
47 #endif
50 /***********************************************************************
51  *              WineLocalizedString
52  *
53  * Look up a localized string by its ID in the dictionary.
54  */
55 static NSString* WineLocalizedString(unsigned int stringID)
57     NSNumber* key = [NSNumber numberWithUnsignedInt:stringID];
58     return [(NSDictionary*)localized_strings objectForKey:key];
62 @implementation WineApplication
64 @synthesize wineController;
66     - (void) sendEvent:(NSEvent*)anEvent
67     {
68         if (![wineController handleEvent:anEvent])
69         {
70             [super sendEvent:anEvent];
71             [wineController didSendEvent:anEvent];
72         }
73     }
75     - (void) setWineController:(WineApplicationController*)newController
76     {
77         wineController = newController;
78         [self setDelegate:wineController];
79     }
81 @end
84 @interface WineApplicationController ()
86 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
87 @property (copy, nonatomic) NSArray* cursorFrames;
88 @property (retain, nonatomic) NSTimer* cursorTimer;
89 @property (retain, nonatomic) NSCursor* cursor;
90 @property (retain, nonatomic) NSImage* applicationIcon;
91 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
92 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
94     - (void) setupObservations;
95     - (void) applicationDidBecomeActive:(NSNotification *)notification;
97     static void PerformRequest(void *info);
99 @end
102 @implementation WineApplicationController
104     @synthesize keyboardType, lastFlagsChanged;
105     @synthesize applicationIcon;
106     @synthesize cursorFrames, cursorTimer, cursor;
107     @synthesize mouseCaptureWindow;
108     @synthesize lastSetCursorPositionTime;
110     + (void) initialize
111     {
112         if (self == [WineApplicationController class])
113         {
114             NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
115                                       @"", @"NSQuotedKeystrokeBinding",
116                                       @"", @"NSRepeatCountBinding",
117                                       [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
118                                       nil];
119             [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
121             if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
122                 [NSWindow setAllowsAutomaticWindowTabbing:NO];
123         }
124     }
126     + (WineApplicationController*) sharedController
127     {
128         static WineApplicationController* sharedController;
129         static dispatch_once_t once;
131         dispatch_once(&once, ^{
132             sharedController = [[self alloc] init];
133         });
135         return sharedController;
136     }
138     - (id) init
139     {
140         self = [super init];
141         if (self != nil)
142         {
143             CFRunLoopSourceContext context = { 0 };
144             context.perform = PerformRequest;
145             requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
146             if (!requestSource)
147             {
148                 [self release];
149                 return nil;
150             }
151             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
152             CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
154             requests =  [[NSMutableArray alloc] init];
155             requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
157             eventQueues = [[NSMutableArray alloc] init];
158             eventQueuesLock = [[NSLock alloc] init];
160             keyWindows = [[NSMutableArray alloc] init];
162             originalDisplayModes = [[NSMutableDictionary alloc] init];
163             latentDisplayModes = [[NSMutableDictionary alloc] init];
165             windowsBeingDragged = [[NSMutableSet alloc] init];
167             // On macOS 10.12+, use notifications to more reliably detect when windows are being dragged.
168             if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)])
169             {
170                 NSOperatingSystemVersion requiredVersion = { 10, 12, 0 };
171                 useDragNotifications = [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:requiredVersion];
172             }
173             else
174                 useDragNotifications = NO;
176             if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
177                 !keyWindows || !originalDisplayModes || !latentDisplayModes)
178             {
179                 [self release];
180                 return nil;
181             }
183             [self setupObservations];
185             keyboardType = LMGetKbdType();
187             if ([NSApp isActive])
188                 [self applicationDidBecomeActive:nil];
189         }
190         return self;
191     }
193     - (void) dealloc
194     {
195         [windowsBeingDragged release];
196         [cursor release];
197         [screenFrameCGRects release];
198         [applicationIcon release];
199         [clipCursorHandler release];
200         [cursorTimer release];
201         [cursorFrames release];
202         [latentDisplayModes release];
203         [originalDisplayModes release];
204         [keyWindows release];
205         [eventQueues release];
206         [eventQueuesLock release];
207         if (requestsManipQueue) dispatch_release(requestsManipQueue);
208         [requests release];
209         if (requestSource)
210         {
211             CFRunLoopSourceInvalidate(requestSource);
212             CFRelease(requestSource);
213         }
214         [super dealloc];
215     }
217     - (void) transformProcessToForeground:(BOOL)activateIfTransformed
218     {
219         if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
220         {
221             NSMenu* mainMenu;
222             NSMenu* submenu;
223             NSString* bundleName;
224             NSString* title;
225             NSMenuItem* item;
227             [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
229             if (activateIfTransformed)
230                 [NSApp activateIgnoringOtherApps:YES];
232 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
233             if (!enable_app_nap && [NSProcessInfo instancesRespondToSelector:@selector(beginActivityWithOptions:reason:)])
234             {
235                 [[[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep
236                                                                 reason:@"Running Windows program"] retain]; // intentional leak
237             }
238 #endif
240             mainMenu = [[[NSMenu alloc] init] autorelease];
242             // Application menu
243             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
244             bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
246             if ([bundleName length])
247                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
248             else
249                 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
250             item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
252             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
253                                       action:@selector(hideOtherApplications:)
254                                keyEquivalent:@"h"];
255             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
257             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
258                                       action:@selector(unhideAllApplications:)
259                                keyEquivalent:@""];
261             [submenu addItem:[NSMenuItem separatorItem]];
263             if ([bundleName length])
264                 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
265             else
266                 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
267             item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
268             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
269             item = [[[NSMenuItem alloc] init] autorelease];
270             [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
271             [item setSubmenu:submenu];
272             [mainMenu addItem:item];
274             // Window menu
275             submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
276             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
277                                action:@selector(performMiniaturize:)
278                         keyEquivalent:@""];
279             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
280                                action:@selector(performZoom:)
281                         keyEquivalent:@""];
282             item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
283                                       action:@selector(toggleFullScreen:)
284                                keyEquivalent:@"f"];
285             [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand |
286                                                NSEventModifierFlagOption |
287                                                NSEventModifierFlagControl];
288             [submenu addItem:[NSMenuItem separatorItem]];
289             [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
290                                action:@selector(arrangeInFront:)
291                         keyEquivalent:@""];
292             item = [[[NSMenuItem alloc] init] autorelease];
293             [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
294             [item setSubmenu:submenu];
295             [mainMenu addItem:item];
297             [NSApp setMainMenu:mainMenu];
298             [NSApp setWindowsMenu:submenu];
300             [NSApp setApplicationIconImage:self.applicationIcon];
301         }
302     }
304     - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
305     {
306         PerformRequest(NULL);
308         do
309         {
310             if (processEvents)
311             {
312                 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
313                 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
314                                                     untilDate:timeout
315                                                        inMode:NSDefaultRunLoopMode
316                                                       dequeue:YES];
317                 if (event)
318                     [NSApp sendEvent:event];
319                 [pool release];
320             }
321             else
322                 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
323         } while (!*done && [timeout timeIntervalSinceNow] >= 0);
325         return *done;
326     }
328     - (BOOL) registerEventQueue:(WineEventQueue*)queue
329     {
330         [eventQueuesLock lock];
331         [eventQueues addObject:queue];
332         [eventQueuesLock unlock];
333         return TRUE;
334     }
336     - (void) unregisterEventQueue:(WineEventQueue*)queue
337     {
338         [eventQueuesLock lock];
339         [eventQueues removeObjectIdenticalTo:queue];
340         [eventQueuesLock unlock];
341     }
343     - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
344     {
345         eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
346     }
348     - (double) ticksForEventTime:(NSTimeInterval)eventTime
349     {
350         return (eventTime + eventTimeAdjustment) * 1000;
351     }
353     /* Invalidate old focus offers across all queues. */
354     - (void) invalidateGotFocusEvents
355     {
356         WineEventQueue* queue;
358         windowFocusSerial++;
360         [eventQueuesLock lock];
361         for (queue in eventQueues)
362         {
363             [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
364                                    forWindow:nil];
365         }
366         [eventQueuesLock unlock];
367     }
369     - (void) windowGotFocus:(WineWindow*)window
370     {
371         macdrv_event* event;
373         [self invalidateGotFocusEvents];
375         event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
376         event->window_got_focus.serial = windowFocusSerial;
377         if (triedWindows)
378             event->window_got_focus.tried_windows = [triedWindows retain];
379         else
380             event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
381         [window.queue postEvent:event];
382         macdrv_release_event(event);
383     }
385     - (void) windowRejectedFocusEvent:(const macdrv_event*)event
386     {
387         if (event->window_got_focus.serial == windowFocusSerial)
388         {
389             NSMutableArray* windows = [keyWindows mutableCopy];
390             NSNumber* windowNumber;
391             WineWindow* window;
393             for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
394             {
395                 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
396                 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
397                     ![windows containsObject:window])
398                     [windows addObject:window];
399             }
401             triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
402             [triedWindows addObject:(WineWindow*)event->window];
403             for (window in windows)
404             {
405                 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
406                 {
407                     [window makeKeyWindow];
408                     break;
409                 }
410             }
411             triedWindows = nil;
412             [windows release];
413         }
414     }
416     static BOOL EqualInputSource(TISInputSourceRef source1, TISInputSourceRef source2)
417     {
418         if (!source1 && !source2)
419             return TRUE;
420         if (!source1 || !source2)
421             return FALSE;
422         return CFEqual(source1, source2);
423     }
425     - (void) keyboardSelectionDidChange:(BOOL)force
426     {
427         TISInputSourceRef inputSource, inputSourceLayout;
429         if (!force)
430         {
431             NSTextInputContext* context = [NSTextInputContext currentInputContext];
432             if (!context || ![context client])
433                 return;
434         }
436         inputSource = TISCopyCurrentKeyboardInputSource();
437         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
438         if (!force && EqualInputSource(inputSource, lastKeyboardInputSource) &&
439             EqualInputSource(inputSourceLayout, lastKeyboardLayoutInputSource))
440         {
441             if (inputSource) CFRelease(inputSource);
442             if (inputSourceLayout) CFRelease(inputSourceLayout);
443             return;
444         }
446         if (lastKeyboardInputSource)
447             CFRelease(lastKeyboardInputSource);
448         lastKeyboardInputSource = inputSource;
449         if (lastKeyboardLayoutInputSource)
450             CFRelease(lastKeyboardLayoutInputSource);
451         lastKeyboardLayoutInputSource = inputSourceLayout;
453         inputSourceIsInputMethodValid = FALSE;
455         if (inputSourceLayout)
456         {
457             CFDataRef uchr;
458             uchr = TISGetInputSourceProperty(inputSourceLayout,
459                     kTISPropertyUnicodeKeyLayoutData);
460             if (uchr)
461             {
462                 macdrv_event* event;
463                 WineEventQueue* queue;
465                 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
466                 event->keyboard_changed.keyboard_type = self.keyboardType;
467                 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
468                 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
469                 event->keyboard_changed.input_source = (TISInputSourceRef)CFRetain(inputSource);
471                 if (event->keyboard_changed.uchr)
472                 {
473                     [eventQueuesLock lock];
475                     for (queue in eventQueues)
476                         [queue postEvent:event];
478                     [eventQueuesLock unlock];
479                 }
481                 macdrv_release_event(event);
482             }
483         }
484     }
486     - (void) keyboardSelectionDidChange
487     {
488         [self keyboardSelectionDidChange:NO];
489     }
491     - (void) setKeyboardType:(CGEventSourceKeyboardType)newType
492     {
493         if (newType != keyboardType)
494         {
495             keyboardType = newType;
496             [self keyboardSelectionDidChange:YES];
497         }
498     }
500     - (void) enabledKeyboardInputSourcesChanged
501     {
502         macdrv_layout_list_needs_update = TRUE;
503     }
505     - (CGFloat) primaryScreenHeight
506     {
507         if (!primaryScreenHeightValid)
508         {
509             NSArray* screens = [NSScreen screens];
510             NSUInteger count = [screens count];
511             if (count)
512             {
513                 NSUInteger size;
514                 CGRect* rect;
515                 NSScreen* screen;
517                 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
518                 primaryScreenHeightValid = TRUE;
520                 size = count * sizeof(CGRect);
521                 if (!screenFrameCGRects)
522                     screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
523                 else
524                     [screenFrameCGRects setLength:size];
526                 rect = [screenFrameCGRects mutableBytes];
527                 for (screen in screens)
528                 {
529                     CGRect temp = NSRectToCGRect([screen frame]);
530                     temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
531                     *rect++ = temp;
532                 }
533             }
534             else
535                 return 1280; /* arbitrary value */
536         }
538         return primaryScreenHeight;
539     }
541     - (NSPoint) flippedMouseLocation:(NSPoint)point
542     {
543         /* This relies on the fact that Cocoa's mouse location points are
544            actually off by one (precisely because they were flipped from
545            Quartz screen coordinates using this same technique). */
546         point.y = [self primaryScreenHeight] - point.y;
547         return point;
548     }
550     - (void) flipRect:(NSRect*)rect
551     {
552         // We don't use -primaryScreenHeight here so there's no chance of having
553         // out-of-date cached info.  This method is called infrequently enough
554         // that getting the screen height each time is not prohibitively expensive.
555         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
556     }
558     - (WineWindow*) frontWineWindow
559     {
560         NSNumber* windowNumber;
561         for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
562         {
563             NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
564             if ([window isKindOfClass:[WineWindow class]] && [window screen])
565                 return (WineWindow*)window;
566         }
568         return nil;
569     }
571     - (void) adjustWindowLevels:(BOOL)active
572     {
573         NSArray* windowNumbers;
574         NSMutableArray* wineWindows;
575         NSNumber* windowNumber;
576         NSUInteger nextFloatingIndex = 0;
577         __block NSInteger maxLevel = NSIntegerMin;
578         __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
579         __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
580         __block WineWindow* prev = nil;
581         WineWindow* window;
583         if ([NSApp isHidden]) return;
585         windowNumbers = [NSWindow windowNumbersWithOptions:0];
586         wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
588         // For the most part, we rely on the window server's ordering of the windows
589         // to be authoritative.  The one exception is if the "floating" property of
590         // one of the windows has been changed, it may be in the wrong level and thus
591         // in the order.  This method is what's supposed to fix that up.  So build
592         // a list of Wine windows sorted first by floating-ness and then by order
593         // as indicated by the window server.
594         for (windowNumber in windowNumbers)
595         {
596             window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
597             if ([window isKindOfClass:[WineWindow class]])
598             {
599                 if (window.floating)
600                     [wineWindows insertObject:window atIndex:nextFloatingIndex++];
601                 else
602                     [wineWindows addObject:window];
603             }
604         }
606         NSDisableScreenUpdates();
608         // Go from back to front so that all windows in front of one which is
609         // elevated for full-screen are also elevated.
610         [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
611                                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
612             WineWindow* window = (WineWindow*)obj;
613             NSInteger origLevel = [window level];
614             NSInteger newLevel = [window minimumLevelForActive:active];
616             if (window.floating)
617             {
618                 if (minFloatingLevel <= maxNonfloatingLevel)
619                     minFloatingLevel = maxNonfloatingLevel + 1;
620                 if (newLevel < minFloatingLevel)
621                     newLevel = minFloatingLevel;
622             }
624             if (newLevel < maxLevel)
625                 newLevel = maxLevel;
626             else
627                 maxLevel = newLevel;
629             if (!window.floating && maxNonfloatingLevel < newLevel)
630                 maxNonfloatingLevel = newLevel;
632             if (newLevel != origLevel)
633             {
634                 [window setLevel:newLevel];
636                 // -setLevel: puts the window at the front of its new level.  If
637                 // we decreased the level, that's good (it was in front of that
638                 // level before, so it should still be now).  But if we increased
639                 // the level, the window should be toward the back (but still
640                 // ahead of the previous windows we did this to).
641                 if (origLevel < newLevel)
642                 {
643                     if (prev)
644                         [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
645                     else
646                         [window orderBack:nil];
647                 }
648             }
650             prev = window;
651         }];
653         NSEnableScreenUpdates();
655         [wineWindows release];
657         // The above took care of the visible windows on the current space.  That
658         // leaves windows on other spaces, minimized windows, and windows which
659         // are not ordered in.  We want to leave windows on other spaces alone
660         // so the space remains just as they left it (when viewed in Exposé or
661         // Mission Control, for example).  We'll adjust the window levels again
662         // after we switch to another space, anyway.  Windows which aren't
663         // ordered in will be handled when we order them in.  Minimized windows
664         // on the current space should be set to the level they would have gotten
665         // if they were at the front of the windows with the same floating-ness,
666         // because that's where they'll go if/when they are unminimized.  Again,
667         // for good measure we'll adjust window levels again when a window is
668         // unminimized, too.
669         for (window in [NSApp windows])
670         {
671             if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
672                 [window isOnActiveSpace])
673             {
674                 NSInteger origLevel = [window level];
675                 NSInteger newLevel = [window minimumLevelForActive:YES];
676                 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
678                 if (newLevel < maxLevelForType)
679                     newLevel = maxLevelForType;
681                 if (newLevel != origLevel)
682                     [window setLevel:newLevel];
683             }
684         }
685     }
687     - (void) adjustWindowLevels
688     {
689         [self adjustWindowLevels:[NSApp isActive]];
690     }
692     - (void) updateFullscreenWindows
693     {
694         if (capture_displays_for_fullscreen && [NSApp isActive])
695         {
696             BOOL anyFullscreen = FALSE;
697             NSNumber* windowNumber;
698             for (windowNumber in [NSWindow windowNumbersWithOptions:0])
699             {
700                 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
701                 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
702                 {
703                     anyFullscreen = TRUE;
704                     break;
705                 }
706             }
708             if (anyFullscreen)
709             {
710                 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
711                     displaysCapturedForFullscreen = TRUE;
712             }
713             else if (displaysCapturedForFullscreen)
714             {
715                 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
716                     displaysCapturedForFullscreen = FALSE;
717             }
718         }
719     }
721     - (void) activeSpaceDidChange
722     {
723         [self updateFullscreenWindows];
724         [self adjustWindowLevels];
725     }
727     - (void) sendDisplaysChanged:(BOOL)activating
728     {
729         macdrv_event* event;
730         WineEventQueue* queue;
732         event = macdrv_create_event(DISPLAYS_CHANGED, nil);
733         event->displays_changed.activating = activating;
735         [eventQueuesLock lock];
737         // If we're activating, then we just need one of our threads to get the
738         // event, so it can send it directly to the desktop window.  Otherwise,
739         // we need all of the threads to get it because we don't know which owns
740         // the desktop window and only that one will do anything with it.
741         if (activating) event->deliver = 1;
743         for (queue in eventQueues)
744             [queue postEvent:event];
745         [eventQueuesLock unlock];
747         macdrv_release_event(event);
748     }
750     // We can compare two modes directly using CFEqual, but that may require that
751     // they are identical to a level that we don't need.  In particular, when the
752     // OS switches between the integrated and discrete GPUs, the set of display
753     // modes can change in subtle ways.  We're interested in whether two modes
754     // match in their most salient features, even if they aren't identical.
755     - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
756     {
757         NSString *encoding1, *encoding2;
758         uint32_t ioflags1, ioflags2, different;
759         double refresh1, refresh2;
761         if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
762         if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
764 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
765         if (&CGDisplayModeGetPixelWidth != NULL &&
766             CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
767         if (&CGDisplayModeGetPixelHeight != NULL &&
768             CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
769 #endif
771         encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
772         encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
773         if (![encoding1 isEqualToString:encoding2]) return FALSE;
775         ioflags1 = CGDisplayModeGetIOFlags(mode1);
776         ioflags2 = CGDisplayModeGetIOFlags(mode2);
777         different = ioflags1 ^ ioflags2;
778         if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
779                          kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
780             return FALSE;
782         refresh1 = CGDisplayModeGetRefreshRate(mode1);
783         if (refresh1 == 0) refresh1 = 60;
784         refresh2 = CGDisplayModeGetRefreshRate(mode2);
785         if (refresh2 == 0) refresh2 = 60;
786         if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
788         return TRUE;
789     }
791     - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
792     {
793         NSMutableArray* ret = [NSMutableArray array];
794         NSDictionary* options = nil;
796 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
797         options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:TRUE]
798                                               forKey:(NSString*)kCGDisplayShowDuplicateLowResolutionModes];
799 #endif
801         NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
802         for (id candidateModeObject in modes)
803         {
804             CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
805             if ([self mode:candidateMode matchesMode:mode])
806                 [ret addObject:candidateModeObject];
807         }
808         return ret;
809     }
811     - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
812     {
813         BOOL ret = FALSE;
814         NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
815         CGDisplayModeRef originalMode;
817         originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
819         if (originalMode && [self mode:mode matchesMode:originalMode])
820         {
821             if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
822             {
823                 CGRestorePermanentDisplayConfiguration();
824                 if (!displaysCapturedForFullscreen)
825                     CGReleaseAllDisplays();
826                 [originalDisplayModes removeAllObjects];
827                 ret = TRUE;
828             }
829             else // ... otherwise, try to restore just the one display
830             {
831                 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
832                 {
833                     mode = (CGDisplayModeRef)modeObject;
834                     if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
835                     {
836                         [originalDisplayModes removeObjectForKey:displayIDKey];
837                         ret = TRUE;
838                         break;
839                     }
840                 }
841             }
842         }
843         else
844         {
845             CGDisplayModeRef currentMode;
846             NSArray* modes;
848             currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
849             if (!currentMode)
850                 currentMode = CGDisplayCopyDisplayMode(displayID);
851             if (!currentMode) // Invalid display ID
852                 return FALSE;
854             if ([self mode:mode matchesMode:currentMode]) // Already there!
855             {
856                 CGDisplayModeRelease(currentMode);
857                 return TRUE;
858             }
860             CGDisplayModeRelease(currentMode);
861             currentMode = NULL;
863             modes = [self modesMatchingMode:mode forDisplay:displayID];
864             if (!modes.count)
865                 return FALSE;
867             [self transformProcessToForeground:YES];
869             BOOL active = [NSApp isActive];
871             if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
872                 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
873             {
874                 if (active)
875                 {
876                     // If we get here, we have the displays captured.  If we don't
877                     // know the original mode of the display, the current mode must
878                     // be the original.  We should re-query the current mode since
879                     // another process could have changed it between when we last
880                     // checked and when we captured the displays.
881                     if (!originalMode)
882                         originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
884                     if (originalMode)
885                     {
886                         for (id modeObject in modes)
887                         {
888                             mode = (CGDisplayModeRef)modeObject;
889                             if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
890                             {
891                                 ret = TRUE;
892                                 break;
893                             }
894                         }
895                     }
896                     if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
897                         [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
898                     else if (![originalDisplayModes count])
899                     {
900                         CGRestorePermanentDisplayConfiguration();
901                         if (!displaysCapturedForFullscreen)
902                             CGReleaseAllDisplays();
903                     }
905                     if (currentMode)
906                         CGDisplayModeRelease(currentMode);
907                 }
908                 else
909                 {
910                     [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
911                     ret = TRUE;
912                 }
913             }
914         }
916         if (ret)
917             [self adjustWindowLevels];
919         return ret;
920     }
922     - (BOOL) areDisplaysCaptured
923     {
924         return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
925     }
927     - (void) updateCursor:(BOOL)force
928     {
929         if (force || lastTargetWindow)
930         {
931             if (clientWantsCursorHidden && !cursorHidden)
932             {
933                 [NSCursor hide];
934                 cursorHidden = TRUE;
935             }
937             if (!cursorIsCurrent)
938             {
939                 [cursor set];
940                 cursorIsCurrent = TRUE;
941             }
943             if (!clientWantsCursorHidden && cursorHidden)
944             {
945                 [NSCursor unhide];
946                 cursorHidden = FALSE;
947             }
948         }
949         else
950         {
951             if (cursorIsCurrent)
952             {
953                 [[NSCursor arrowCursor] set];
954                 cursorIsCurrent = FALSE;
955             }
956             if (cursorHidden)
957             {
958                 [NSCursor unhide];
959                 cursorHidden = FALSE;
960             }
961         }
962     }
964     - (void) hideCursor
965     {
966         if (!clientWantsCursorHidden)
967         {
968             clientWantsCursorHidden = TRUE;
969             [self updateCursor:TRUE];
970         }
971     }
973     - (void) unhideCursor
974     {
975         if (clientWantsCursorHidden)
976         {
977             clientWantsCursorHidden = FALSE;
978             [self updateCursor:FALSE];
979         }
980     }
982     - (void) setCursor:(NSCursor*)newCursor
983     {
984         if (newCursor != cursor)
985         {
986             [cursor release];
987             cursor = [newCursor retain];
988             cursorIsCurrent = FALSE;
989             [self updateCursor:FALSE];
990         }
991     }
993     - (void) setCursor
994     {
995         NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
996         CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
997         CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
998         NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
999         CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
1000         CGPoint hotSpot;
1002         if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
1003             hotSpot = CGPointZero;
1004         hotSpot = cgpoint_mac_from_win(hotSpot);
1005         self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1006         [image release];
1007         [self unhideCursor];
1008     }
1010     - (void) nextCursorFrame:(NSTimer*)theTimer
1011     {
1012         NSDictionary* frame;
1013         NSTimeInterval duration;
1014         NSDate* date;
1016         cursorFrame++;
1017         if (cursorFrame >= [cursorFrames count])
1018             cursorFrame = 0;
1019         [self setCursor];
1021         frame = [cursorFrames objectAtIndex:cursorFrame];
1022         duration = [[frame objectForKey:@"duration"] doubleValue];
1023         date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1024         [cursorTimer setFireDate:date];
1025     }
1027     - (void) setCursorWithFrames:(NSArray*)frames
1028     {
1029         if (self.cursorFrames == frames)
1030             return;
1032         self.cursorFrames = frames;
1033         cursorFrame = 0;
1034         [cursorTimer invalidate];
1035         self.cursorTimer = nil;
1037         if ([frames count])
1038         {
1039             if ([frames count] > 1)
1040             {
1041                 NSDictionary* frame = [frames objectAtIndex:0];
1042                 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
1043                 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1044                 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1045                                                              interval:1000000
1046                                                                target:self
1047                                                              selector:@selector(nextCursorFrame:)
1048                                                              userInfo:nil
1049                                                               repeats:YES] autorelease];
1050                 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1051             }
1053             [self setCursor];
1054         }
1055     }
1057     - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1058     {
1059         NSImage* nsimage = nil;
1061         if ([images count])
1062         {
1063             NSSize bestSize = NSZeroSize;
1064             id image;
1066             nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1068             for (image in images)
1069             {
1070                 CGImageRef cgimage = (CGImageRef)image;
1071                 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1072                 if (imageRep)
1073                 {
1074                     NSSize size = [imageRep size];
1076                     [nsimage addRepresentation:imageRep];
1077                     [imageRep release];
1079                     if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1080                         bestSize = size;
1081                 }
1082             }
1084             if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1085                 [nsimage setSize:bestSize];
1086             else
1087                 nsimage = nil;
1088         }
1090         self.applicationIcon = nsimage;
1091     }
1093     - (void) handleCommandTab
1094     {
1095         if ([NSApp isActive])
1096         {
1097             NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1098             NSRunningApplication* app;
1099             NSRunningApplication* otherValidApp = nil;
1101             if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1102             {
1103                 NSNumber* displayID;
1104                 for (displayID in originalDisplayModes)
1105                 {
1106                     CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1107                     [latentDisplayModes setObject:(id)mode forKey:displayID];
1108                     CGDisplayModeRelease(mode);
1109                 }
1111                 CGRestorePermanentDisplayConfiguration();
1112                 CGReleaseAllDisplays();
1113                 [originalDisplayModes removeAllObjects];
1114                 displaysCapturedForFullscreen = FALSE;
1115             }
1117             for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1118             {
1119                 if (![app isEqual:thisApp] && !app.terminated &&
1120                     app.activationPolicy == NSApplicationActivationPolicyRegular)
1121                 {
1122                     if (!app.hidden)
1123                     {
1124                         // There's another visible app.  Just hide ourselves and let
1125                         // the system activate the other app.
1126                         [NSApp hide:self];
1127                         return;
1128                     }
1130                     if (!otherValidApp)
1131                         otherValidApp = app;
1132                 }
1133             }
1135             // Didn't find a visible GUI app.  Try the Finder or, if that's not
1136             // running, the first hidden GUI app.  If even that doesn't work, we
1137             // just fail to switch and remain the active app.
1138             app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1139             if (!app) app = otherValidApp;
1140             [app unhide];
1141             [app activateWithOptions:0];
1142         }
1143     }
1145     - (BOOL) setCursorPosition:(CGPoint)pos
1146     {
1147         BOOL ret;
1149         if ([windowsBeingDragged count])
1150             ret = FALSE;
1151         else if (self.clippingCursor && [clipCursorHandler respondsToSelector:@selector(setCursorPosition:)])
1152             ret = [clipCursorHandler setCursorPosition:pos];
1153         else
1154         {
1155             if (self.clippingCursor)
1156                 [clipCursorHandler clipCursorLocation:&pos];
1158             // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1159             // the mouse from the cursor position for 0.25 seconds.  This means
1160             // that mouse movement during that interval doesn't move the cursor
1161             // and events carry a constant location (the warped-to position)
1162             // even though they have delta values.  For apps which warp the
1163             // cursor frequently (like after every mouse move), this makes
1164             // cursor movement horribly laggy and jerky, as only a fraction of
1165             // mouse move events have any effect.
1166             //
1167             // On some versions of OS X, it's sufficient to forcibly reassociate
1168             // the mouse and cursor position.  On others, it's necessary to set
1169             // the local events suppression interval to 0 for the warp.  That's
1170             // deprecated, but I'm not aware of any other way.  For good
1171             // measure, we do both.
1172             CGSetLocalEventsSuppressionInterval(0);
1173             ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1174             CGSetLocalEventsSuppressionInterval(0.25);
1175             if (ret)
1176             {
1177                 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1179                 CGAssociateMouseAndMouseCursorPosition(true);
1180             }
1181         }
1183         if (ret)
1184         {
1185             WineEventQueue* queue;
1187             // Discard all pending mouse move events.
1188             [eventQueuesLock lock];
1189             for (queue in eventQueues)
1190             {
1191                 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1192                                                  event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1193                                        forWindow:nil];
1194                 [queue resetMouseEventPositions:pos];
1195             }
1196             [eventQueuesLock unlock];
1197         }
1199         return ret;
1200     }
1202     - (void) updateWindowsForCursorClipping
1203     {
1204         WineWindow* window;
1205         for (window in [NSApp windows])
1206         {
1207             if ([window isKindOfClass:[WineWindow class]])
1208                 [window updateForCursorClipping];
1209         }
1210     }
1212     - (BOOL) startClippingCursor:(CGRect)rect
1213     {
1214         if (!clipCursorHandler) {
1215             if (use_confinement_cursor_clipping && [WineConfinementClipCursorHandler isAvailable])
1216                 clipCursorHandler = [[WineConfinementClipCursorHandler alloc] init];
1217             else
1218                 clipCursorHandler = [[WineEventTapClipCursorHandler alloc] init];
1219         }
1221         if (self.clippingCursor && CGRectEqualToRect(rect, clipCursorHandler.cursorClipRect))
1222             return TRUE;
1224         if (![clipCursorHandler startClippingCursor:rect])
1225             return FALSE;
1227         [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1229         [self updateWindowsForCursorClipping];
1231         return TRUE;
1232     }
1234     - (BOOL) stopClippingCursor
1235     {
1236         if (!self.clippingCursor)
1237             return TRUE;
1239         if (![clipCursorHandler stopClippingCursor])
1240             return FALSE;
1242         lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1244         [self updateWindowsForCursorClipping];
1246         return TRUE;
1247     }
1249     - (BOOL) clippingCursor
1250     {
1251         return clipCursorHandler.clippingCursor;
1252     }
1254     - (BOOL) isKeyPressed:(uint16_t)keyCode
1255     {
1256         int bits = sizeof(pressedKeyCodes[0]) * 8;
1257         int index = keyCode / bits;
1258         uint32_t mask = 1 << (keyCode % bits);
1259         return (pressedKeyCodes[index] & mask) != 0;
1260     }
1262     - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1263     {
1264         int bits = sizeof(pressedKeyCodes[0]) * 8;
1265         int index = keyCode / bits;
1266         uint32_t mask = 1 << (keyCode % bits);
1267         if (pressed)
1268             pressedKeyCodes[index] |= mask;
1269         else
1270             pressedKeyCodes[index] &= ~mask;
1271     }
1273     - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1274     {
1275         if (dragged)
1276             [windowsBeingDragged addObject:window];
1277         else
1278             [windowsBeingDragged removeObject:window];
1279     }
1281     - (void) windowWillOrderOut:(WineWindow*)window
1282     {
1283         if ([windowsBeingDragged containsObject:window])
1284         {
1285             [self window:window isBeingDragged:NO];
1287             macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1288             [window.queue postEvent:event];
1289             macdrv_release_event(event);
1290         }
1291     }
1293     - (BOOL) isAnyWineWindowVisible
1294     {
1295         for (WineWindow* w in [NSApp windows])
1296         {
1297             if ([w isKindOfClass:[WineWindow class]] && ![w isMiniaturized] && [w isVisible])
1298                 return YES;
1299         }
1301         return NO;
1302     }
1304     - (void) handleWindowDrag:(WineWindow*)window begin:(BOOL)begin
1305     {
1306         macdrv_event* event;
1307         int eventType;
1309         if (begin)
1310         {
1311             [windowsBeingDragged addObject:window];
1312             eventType = WINDOW_DRAG_BEGIN;
1313         }
1314         else
1315         {
1316             [windowsBeingDragged removeObject:window];
1317             eventType = WINDOW_DRAG_END;
1318         }
1320         event = macdrv_create_event(eventType, window);
1321         if (eventType == WINDOW_DRAG_BEGIN)
1322             event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
1323         [window.queue postEvent:event];
1324         macdrv_release_event(event);
1325     }
1327     - (void) handleMouseMove:(NSEvent*)anEvent
1328     {
1329         WineWindow* targetWindow;
1330         BOOL drag = [anEvent type] != NSEventTypeMouseMoved;
1332         if ([windowsBeingDragged count])
1333             targetWindow = nil;
1334         else if (mouseCaptureWindow)
1335             targetWindow = mouseCaptureWindow;
1336         else if (drag)
1337             targetWindow = (WineWindow*)[anEvent window];
1338         else
1339         {
1340             /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1341                event indicates its window is the main window, even if the cursor is
1342                over a different window.  Find the actual WineWindow that is under the
1343                cursor and post the event as being for that window. */
1344             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1345             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1346             NSInteger windowUnderNumber;
1348             windowUnderNumber = [NSWindow windowNumberAtPoint:point
1349                                   belowWindowWithWindowNumber:0];
1350             targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1351             if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1352                 targetWindow = nil;
1353         }
1355         if ([targetWindow isKindOfClass:[WineWindow class]])
1356         {
1357             CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1358             macdrv_event* event;
1359             BOOL absolute;
1361             // If we recently warped the cursor (other than in our cursor-clipping
1362             // event tap), discard mouse move events until we see an event which is
1363             // later than that time.
1364             if (lastSetCursorPositionTime)
1365             {
1366                 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1367                     return;
1369                 lastSetCursorPositionTime = 0;
1370                 forceNextMouseMoveAbsolute = TRUE;
1371             }
1373             if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1374             {
1375                 absolute = TRUE;
1376                 forceNextMouseMoveAbsolute = FALSE;
1377             }
1378             else
1379             {
1380                 // Send absolute move events if the cursor is in the interior of
1381                 // its range.  Only send relative moves if the cursor is pinned to
1382                 // the boundaries of where it can go.  We compute the position
1383                 // that's one additional point in the direction of movement.  If
1384                 // that is outside of the clipping rect or desktop region (the
1385                 // union of the screen frames), then we figure the cursor would
1386                 // have moved outside if it could but it was pinned.
1387                 CGPoint computedPoint = point;
1388                 CGFloat deltaX = [anEvent deltaX];
1389                 CGFloat deltaY = [anEvent deltaY];
1391                 if (deltaX > 0.001)
1392                     computedPoint.x++;
1393                 else if (deltaX < -0.001)
1394                     computedPoint.x--;
1396                 if (deltaY > 0.001)
1397                     computedPoint.y++;
1398                 else if (deltaY < -0.001)
1399                     computedPoint.y--;
1401                 // Assume cursor is pinned for now
1402                 absolute = FALSE;
1403                 if (!self.clippingCursor || CGRectContainsPoint(clipCursorHandler.cursorClipRect, computedPoint))
1404                 {
1405                     const CGRect* rects;
1406                     NSUInteger count, i;
1408                     // Caches screenFrameCGRects if necessary
1409                     [self primaryScreenHeight];
1411                     rects = [screenFrameCGRects bytes];
1412                     count = [screenFrameCGRects length] / sizeof(rects[0]);
1414                     for (i = 0; i < count; i++)
1415                     {
1416                         if (CGRectContainsPoint(rects[i], computedPoint))
1417                         {
1418                             absolute = TRUE;
1419                             break;
1420                         }
1421                     }
1422                 }
1423             }
1425             if (absolute)
1426             {
1427                 if (self.clippingCursor)
1428                     [clipCursorHandler clipCursorLocation:&point];
1429                 point = cgpoint_win_from_mac(point);
1431                 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1432                 event->mouse_moved.x = floor(point.x);
1433                 event->mouse_moved.y = floor(point.y);
1435                 mouseMoveDeltaX = 0;
1436                 mouseMoveDeltaY = 0;
1437             }
1438             else
1439             {
1440                 double scale = retina_on ? 2 : 1;
1442                 /* Add event delta to accumulated delta error */
1443                 /* deltaY is already flipped */
1444                 mouseMoveDeltaX += [anEvent deltaX];
1445                 mouseMoveDeltaY += [anEvent deltaY];
1447                 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1448                 event->mouse_moved.x = mouseMoveDeltaX * scale;
1449                 event->mouse_moved.y = mouseMoveDeltaY * scale;
1451                 /* Keep the remainder after integer truncation. */
1452                 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1453                 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1454             }
1456             if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1457             {
1458                 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1459                 event->mouse_moved.drag = drag;
1461                 [targetWindow.queue postEvent:event];
1462             }
1464             macdrv_release_event(event);
1466             lastTargetWindow = targetWindow;
1467         }
1468         else
1469             lastTargetWindow = nil;
1471         [self updateCursor:FALSE];
1472     }
1474     - (void) handleMouseButton:(NSEvent*)theEvent
1475     {
1476         WineWindow* window = (WineWindow*)[theEvent window];
1477         NSEventType type = [theEvent type];
1478         WineWindow* windowBroughtForward = nil;
1479         BOOL process = FALSE;
1481         if ([window isKindOfClass:[WineWindow class]] &&
1482             type == NSEventTypeLeftMouseDown &&
1483             ![theEvent wine_commandKeyDown])
1484         {
1485             NSWindowButton windowButton;
1487             windowBroughtForward = window;
1489             /* Any left-click on our window anyplace other than the close or
1490                minimize buttons will bring it forward. */
1491             for (windowButton = NSWindowCloseButton;
1492                  windowButton <= NSWindowMiniaturizeButton;
1493                  windowButton++)
1494             {
1495                 NSButton* button = [window standardWindowButton:windowButton];
1496                 if (button)
1497                 {
1498                     NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1499                     if ([button mouse:point inRect:[button bounds]])
1500                     {
1501                         windowBroughtForward = nil;
1502                         break;
1503                     }
1504                 }
1505             }
1506         }
1508         if ([windowsBeingDragged count])
1509             window = nil;
1510         else if (mouseCaptureWindow)
1511             window = mouseCaptureWindow;
1513         if ([window isKindOfClass:[WineWindow class]])
1514         {
1515             BOOL pressed = (type == NSEventTypeLeftMouseDown ||
1516                             type == NSEventTypeRightMouseDown ||
1517                             type == NSEventTypeOtherMouseDown);
1518             CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1520             if (self.clippingCursor)
1521                 [clipCursorHandler clipCursorLocation:&pt];
1523             if (pressed)
1524             {
1525                 if (mouseCaptureWindow)
1526                     process = TRUE;
1527                 else
1528                 {
1529                     // Test if the click was in the window's content area.
1530                     NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1531                     NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1532                     process = NSMouseInRect(nspoint, contentRect, NO);
1533                     if (process && [window styleMask] & NSWindowStyleMaskResizable)
1534                     {
1535                         // Ignore clicks in the grow box (resize widget).
1536                         HIPoint origin = { 0, 0 };
1537                         HIThemeGrowBoxDrawInfo info = { 0 };
1538                         HIRect bounds;
1539                         OSStatus status;
1541                         info.kind = kHIThemeGrowBoxKindNormal;
1542                         info.direction = kThemeGrowRight | kThemeGrowDown;
1543                         if ([window styleMask] & NSWindowStyleMaskUtilityWindow)
1544                             info.size = kHIThemeGrowBoxSizeSmall;
1545                         else
1546                             info.size = kHIThemeGrowBoxSizeNormal;
1548                         status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1549                         if (status == noErr)
1550                         {
1551                             NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1552                                                         NSMinY(contentRect),
1553                                                         bounds.size.width,
1554                                                         bounds.size.height);
1555                             process = !NSMouseInRect(nspoint, growBox, NO);
1556                         }
1557                     }
1558                 }
1559                 if (process)
1560                     unmatchedMouseDowns |= NSEventMaskFromType(type);
1561             }
1562             else
1563             {
1564                 NSEventType downType = type - 1;
1565                 NSUInteger downMask = NSEventMaskFromType(downType);
1566                 process = (unmatchedMouseDowns & downMask) != 0;
1567                 unmatchedMouseDowns &= ~downMask;
1568             }
1570             if (process)
1571             {
1572                 macdrv_event* event;
1574                 pt = cgpoint_win_from_mac(pt);
1576                 event = macdrv_create_event(MOUSE_BUTTON, window);
1577                 event->mouse_button.button = [theEvent buttonNumber];
1578                 event->mouse_button.pressed = pressed;
1579                 event->mouse_button.x = floor(pt.x);
1580                 event->mouse_button.y = floor(pt.y);
1581                 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1583                 [window.queue postEvent:event];
1585                 macdrv_release_event(event);
1586             }
1587         }
1589         if (windowBroughtForward)
1590         {
1591             WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1592             NSInteger ancestorNumber = [ancestor windowNumber];
1593             NSInteger ancestorLevel = [ancestor level];
1595             for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1596             {
1597                 NSInteger windowNumber = [windowNumberObject integerValue];
1598                 if (windowNumber == ancestorNumber)
1599                     break;
1600                 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1601                 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1602                     [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1603                 {
1604                     [ancestor postBroughtForwardEvent];
1605                     break;
1606                 }
1607             }
1608             if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noForeground)
1609                 [self windowGotFocus:windowBroughtForward];
1610         }
1612         // Since mouse button events deliver absolute cursor position, the
1613         // accumulating delta from move events is invalidated.  Make sure
1614         // next mouse move event starts over from an absolute baseline.
1615         // Also, it's at least possible that the title bar widgets (e.g. close
1616         // button, etc.) could enter an internal event loop on a mouse down that
1617         // wouldn't exit until a mouse up.  In that case, we'd miss any mouse
1618         // dragged events and, after that, any notion of the cursor position
1619         // computed from accumulating deltas would be wrong.
1620         forceNextMouseMoveAbsolute = TRUE;
1621     }
1623     - (void) handleScrollWheel:(NSEvent*)theEvent
1624     {
1625         WineWindow* window;
1627         if (mouseCaptureWindow)
1628             window = mouseCaptureWindow;
1629         else
1630             window = (WineWindow*)[theEvent window];
1632         if ([window isKindOfClass:[WineWindow class]])
1633         {
1634             CGEventRef cgevent = [theEvent CGEvent];
1635             CGPoint pt = CGEventGetLocation(cgevent);
1636             BOOL process;
1638             if (self.clippingCursor)
1639                 [clipCursorHandler clipCursorLocation:&pt];
1641             if (mouseCaptureWindow)
1642                 process = TRUE;
1643             else
1644             {
1645                 // Only process the event if it was in the window's content area.
1646                 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1647                 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1648                 process = NSMouseInRect(nspoint, contentRect, NO);
1649             }
1651             if (process)
1652             {
1653                 macdrv_event* event;
1654                 double x, y;
1655                 BOOL continuous = FALSE;
1657                 pt = cgpoint_win_from_mac(pt);
1659                 event = macdrv_create_event(MOUSE_SCROLL, window);
1660                 event->mouse_scroll.x = floor(pt.x);
1661                 event->mouse_scroll.y = floor(pt.y);
1662                 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1664                 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1665                 {
1666                     continuous = TRUE;
1668                     /* Continuous scroll wheel events come from high-precision scrolling
1669                        hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1670                        For these, we can get more precise data from the CGEvent API. */
1671                     /* Axis 1 is vertical, axis 2 is horizontal. */
1672                     x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1673                     y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1674                 }
1675                 else
1676                 {
1677                     double pixelsPerLine = 10;
1678                     CGEventSourceRef source;
1680                     /* The non-continuous values are in units of "lines", not pixels. */
1681                     if ((source = CGEventCreateSourceFromEvent(cgevent)))
1682                     {
1683                         pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1684                         CFRelease(source);
1685                     }
1687                     x = pixelsPerLine * [theEvent deltaX];
1688                     y = pixelsPerLine * [theEvent deltaY];
1689                 }
1691                 /* Mac: negative is right or down, positive is left or up.
1692                    Win32: negative is left or down, positive is right or up.
1693                    So, negate the X scroll value to translate. */
1694                 x = -x;
1696                 /* The x,y values so far are in pixels.  Win32 expects to receive some
1697                    fraction of WHEEL_DELTA == 120.  By my estimation, that's roughly
1698                    6 times the pixel value. */
1699                 x *= 6;
1700                 y *= 6;
1702                 if (use_precise_scrolling)
1703                 {
1704                     event->mouse_scroll.x_scroll = x;
1705                     event->mouse_scroll.y_scroll = y;
1707                     if (!continuous)
1708                     {
1709                         /* For non-continuous "clicky" wheels, if there was any motion, make
1710                            sure there was at least WHEEL_DELTA motion.  This is so, at slow
1711                            speeds where the system's acceleration curve is actually reducing the
1712                            scroll distance, the user is sure to get some action out of each click.
1713                            For example, this is important for rotating though weapons in a
1714                            first-person shooter. */
1715                         if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1716                             event->mouse_scroll.x_scroll = 120;
1717                         else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1718                             event->mouse_scroll.x_scroll = -120;
1720                         if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1721                             event->mouse_scroll.y_scroll = 120;
1722                         else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1723                             event->mouse_scroll.y_scroll = -120;
1724                     }
1725                 }
1726                 else
1727                 {
1728                     /* If it's been a while since the last scroll event or if the scrolling has
1729                        reversed direction, reset the accumulated scroll value. */
1730                     if ([theEvent timestamp] - lastScrollTime > 1)
1731                         accumScrollX = accumScrollY = 0;
1732                     else
1733                     {
1734                         /* The accumulated scroll value is in the opposite direction/sign of the last
1735                            scroll.  That's because it's the "debt" resulting from over-scrolling in
1736                            that direction.  We accumulate by adding in the scroll amount and then, if
1737                            it has the same sign as the scroll value, we subtract any whole or partial
1738                            WHEEL_DELTAs, leaving it 0 or the opposite sign.  So, the user switched
1739                            scroll direction if the accumulated debt and the new scroll value have the
1740                            same sign. */
1741                         if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1742                             accumScrollX = 0;
1743                         if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1744                             accumScrollY = 0;
1745                     }
1746                     lastScrollTime = [theEvent timestamp];
1748                     accumScrollX += x;
1749                     accumScrollY += y;
1751                     if (accumScrollX > 0 && x > 0)
1752                         event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1753                     if (accumScrollX < 0 && x < 0)
1754                         event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1755                     if (accumScrollY > 0 && y > 0)
1756                         event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1757                     if (accumScrollY < 0 && y < 0)
1758                         event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1760                     accumScrollX -= event->mouse_scroll.x_scroll;
1761                     accumScrollY -= event->mouse_scroll.y_scroll;
1762                 }
1764                 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1765                     [window.queue postEvent:event];
1767                 macdrv_release_event(event);
1769                 // Since scroll wheel events deliver absolute cursor position, the
1770                 // accumulating delta from move events is invalidated.  Make sure next
1771                 // mouse move event starts over from an absolute baseline.
1772                 forceNextMouseMoveAbsolute = TRUE;
1773             }
1774         }
1775     }
1777     // Returns TRUE if the event was handled and caller should do nothing more
1778     // with it.  Returns FALSE if the caller should process it as normal and
1779     // then call -didSendEvent:.
1780     - (BOOL) handleEvent:(NSEvent*)anEvent
1781     {
1782         BOOL ret = FALSE;
1783         NSEventType type = [anEvent type];
1785         if (type == NSEventTypeFlagsChanged)
1786             self.lastFlagsChanged = anEvent;
1787         else if (type == NSEventTypeMouseMoved || type == NSEventTypeLeftMouseDragged ||
1788                  type == NSEventTypeRightMouseDragged || type == NSEventTypeOtherMouseDragged)
1789         {
1790             [self handleMouseMove:anEvent];
1791             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1792         }
1793         else if (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp ||
1794                  type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp ||
1795                  type == NSEventTypeOtherMouseDown || type == NSEventTypeOtherMouseUp)
1796         {
1797             [self handleMouseButton:anEvent];
1798             ret = mouseCaptureWindow && ![windowsBeingDragged count];
1799         }
1800         else if (type == NSEventTypeScrollWheel)
1801         {
1802             [self handleScrollWheel:anEvent];
1803             ret = mouseCaptureWindow != nil;
1804         }
1805         else if (type == NSEventTypeKeyDown)
1806         {
1807             // -[NSApplication sendEvent:] seems to consume presses of the Help
1808             // key (Insert key on PC keyboards), so we have to bypass it and
1809             // send the event directly to the window.
1810             if (anEvent.keyCode == kVK_Help)
1811             {
1812                 [anEvent.window sendEvent:anEvent];
1813                 ret = TRUE;
1814             }
1815         }
1816         else if (type == NSEventTypeKeyUp)
1817         {
1818             uint16_t keyCode = [anEvent keyCode];
1819             if ([self isKeyPressed:keyCode])
1820             {
1821                 WineWindow* window = (WineWindow*)[anEvent window];
1822                 [self noteKey:keyCode pressed:FALSE];
1823                 if ([window isKindOfClass:[WineWindow class]])
1824                     [window postKeyEvent:anEvent];
1825             }
1826         }
1827         else if (!useDragNotifications && type == NSEventTypeAppKitDefined)
1828         {
1829             WineWindow *window = (WineWindow *)[anEvent window];
1830             short subtype = [anEvent subtype];
1832             // These subtypes are not documented but they appear to mean
1833             // "a window is being dragged" and "a window is no longer being
1834             // dragged", respectively.
1835             if ((subtype == 20 || subtype == 21) && [window isKindOfClass:[WineWindow class]])
1836                 [self handleWindowDrag:window begin:(subtype == 20)];
1837         }
1839         return ret;
1840     }
1842     - (void) didSendEvent:(NSEvent*)anEvent
1843     {
1844         NSEventType type = [anEvent type];
1846         if (type == NSEventTypeKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1847         {
1848             NSUInteger modifiers = [anEvent modifierFlags];
1849             if ((modifiers & NSEventModifierFlagCommand) &&
1850                 !(modifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)))
1851             {
1852                 // Command-Tab and Command-Shift-Tab would normally be intercepted
1853                 // by the system to switch applications.  If we're seeing it, it's
1854                 // presumably because we've captured the displays, preventing
1855                 // normal application switching.  Do it manually.
1856                 [self handleCommandTab];
1857             }
1858         }
1859     }
1861     - (void) setupObservations
1862     {
1863         NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1864         NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1865         NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
1867         [nc addObserverForName:NSWindowDidBecomeKeyNotification
1868                         object:nil
1869                          queue:nil
1870                     usingBlock:^(NSNotification *note){
1871             NSWindow* window = [note object];
1872             [keyWindows removeObjectIdenticalTo:window];
1873             [keyWindows insertObject:window atIndex:0];
1874         }];
1876         [nc addObserverForName:NSWindowWillCloseNotification
1877                         object:nil
1878                          queue:[NSOperationQueue mainQueue]
1879                     usingBlock:^(NSNotification *note){
1880             NSWindow* window = [note object];
1881             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
1882                 return;
1883             [keyWindows removeObjectIdenticalTo:window];
1884             if (window == lastTargetWindow)
1885                 lastTargetWindow = nil;
1886             if (window == self.mouseCaptureWindow)
1887                 self.mouseCaptureWindow = nil;
1888             if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
1889             {
1890                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
1891                     [self updateFullscreenWindows];
1892                 });
1893             }
1894             [windowsBeingDragged removeObject:window];
1895         }];
1897         if (useDragNotifications) {
1898             [nc addObserverForName:NSWindowWillStartDraggingNotification
1899                             object:nil
1900                              queue:[NSOperationQueue mainQueue]
1901                         usingBlock:^(NSNotification *note){
1902                 NSWindow* window = [note object];
1903                 if ([window isKindOfClass:[WineWindow class]])
1904                     [self handleWindowDrag:(WineWindow *)window begin:YES];
1905             }];
1907             [nc addObserverForName:NSWindowDidEndDraggingNotification
1908                             object:nil
1909                              queue:[NSOperationQueue mainQueue]
1910                         usingBlock:^(NSNotification *note){
1911                 NSWindow* window = [note object];
1912                 if ([window isKindOfClass:[WineWindow class]])
1913                     [self handleWindowDrag:(WineWindow *)window begin:NO];
1914             }];
1915         }
1917         [nc addObserver:self
1918                selector:@selector(keyboardSelectionDidChange)
1919                    name:NSTextInputContextKeyboardSelectionDidChangeNotification
1920                  object:nil];
1922         /* The above notification isn't sent unless the NSTextInputContext
1923            class has initialized itself.  Poke it. */
1924         [NSTextInputContext self];
1926         [wsnc addObserver:self
1927                  selector:@selector(activeSpaceDidChange)
1928                      name:NSWorkspaceActiveSpaceDidChangeNotification
1929                    object:nil];
1931         [nc addObserver:self
1932                selector:@selector(releaseMouseCapture)
1933                    name:NSMenuDidBeginTrackingNotification
1934                  object:nil];
1936         [dnc        addObserver:self
1937                        selector:@selector(releaseMouseCapture)
1938                            name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
1939                          object:nil
1940              suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
1942         [dnc addObserver:self
1943                 selector:@selector(enabledKeyboardInputSourcesChanged)
1944                     name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
1945                   object:nil];
1946     }
1948     - (BOOL) inputSourceIsInputMethod
1949     {
1950         if (!inputSourceIsInputMethodValid)
1951         {
1952             TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
1953             if (inputSource)
1954             {
1955                 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
1956                 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
1957                 CFRelease(inputSource);
1958             }
1959             else
1960                 inputSourceIsInputMethod = FALSE;
1961             inputSourceIsInputMethodValid = TRUE;
1962         }
1964         return inputSourceIsInputMethod;
1965     }
1967     - (void) releaseMouseCapture
1968     {
1969         // This might be invoked on a background thread by the distributed
1970         // notification center.  Shunt it to the main thread.
1971         if (![NSThread isMainThread])
1972         {
1973             dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
1974             return;
1975         }
1977         if (mouseCaptureWindow)
1978         {
1979             macdrv_event* event;
1981             event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
1982             [mouseCaptureWindow.queue postEvent:event];
1983             macdrv_release_event(event);
1984         }
1985     }
1987     - (void) unminimizeWindowIfNoneVisible
1988     {
1989         if (![self frontWineWindow])
1990         {
1991             for (WineWindow* window in [NSApp windows])
1992             {
1993                 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
1994                 {
1995                     [window deminiaturize:self];
1996                     break;
1997                 }
1998             }
1999         }
2000     }
2002     - (void) setRetinaMode:(int)mode
2003     {
2004         retina_on = mode;
2006         [clipCursorHandler setRetinaMode:mode];
2008         for (WineWindow* window in [NSApp windows])
2009         {
2010             if ([window isKindOfClass:[WineWindow class]])
2011                 [window setRetinaMode:mode];
2012         }
2013     }
2016     /*
2017      * ---------- NSApplicationDelegate methods ----------
2018      */
2019     - (void)applicationDidBecomeActive:(NSNotification *)notification
2020     {
2021         NSNumber* displayID;
2022         NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2024         latentDisplayModes = [[NSMutableDictionary alloc] init];
2025         for (displayID in modesToRealize)
2026         {
2027             CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2028             [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2029         }
2031         [self updateFullscreenWindows];
2032         [self adjustWindowLevels:YES];
2034         if (beenActive)
2035             [self unminimizeWindowIfNoneVisible];
2036         beenActive = TRUE;
2038         // If a Wine process terminates abruptly while it has the display captured
2039         // and switched to a different resolution, Mac OS X will uncapture the
2040         // displays and switch their resolutions back.  However, the other Wine
2041         // processes won't have their notion of the desktop rect changed back.
2042         // This can lead them to refuse to draw or acknowledge clicks in certain
2043         // portions of their windows.
2044         //
2045         // To solve this, we synthesize a displays-changed event whenever we're
2046         // activated.  This will provoke a re-synchronization of Wine's notion of
2047         // the desktop rect with the actual state.
2048         [self sendDisplaysChanged:TRUE];
2050         // The cursor probably moved while we were inactive.  Accumulated mouse
2051         // movement deltas are invalidated.  Make sure the next mouse move event
2052         // starts over from an absolute baseline.
2053         forceNextMouseMoveAbsolute = TRUE;
2054     }
2056     - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2057     {
2058         primaryScreenHeightValid = FALSE;
2059         [self sendDisplaysChanged:FALSE];
2060         [self adjustWindowLevels];
2062         // When the display configuration changes, the cursor position may jump.
2063         // Accumulated mouse movement deltas are invalidated.  Make sure the next
2064         // mouse move event starts over from an absolute baseline.
2065         forceNextMouseMoveAbsolute = TRUE;
2066     }
2068     - (void)applicationDidResignActive:(NSNotification *)notification
2069     {
2070         macdrv_event* event;
2071         WineEventQueue* queue;
2073         [self invalidateGotFocusEvents];
2075         event = macdrv_create_event(APP_DEACTIVATED, nil);
2077         [eventQueuesLock lock];
2078         for (queue in eventQueues)
2079             [queue postEvent:event];
2080         [eventQueuesLock unlock];
2082         macdrv_release_event(event);
2084         [self releaseMouseCapture];
2085     }
2087     - (void) applicationDidUnhide:(NSNotification*)aNotification
2088     {
2089         [self adjustWindowLevels];
2090     }
2092     - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2093     {
2094         // Note that "flag" is often wrong.  WineWindows are NSPanels and NSPanels
2095         // don't count as "visible windows" for this purpose.
2096         [self unminimizeWindowIfNoneVisible];
2097         return YES;
2098     }
2100     - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2101     {
2102         NSApplicationTerminateReply ret = NSTerminateNow;
2103         NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2104         NSAppleEventDescriptor* desc = [m currentAppleEvent];
2105         macdrv_event* event;
2106         WineEventQueue* queue;
2108         event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2109         event->deliver = 1;
2110         switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2111         {
2112             case kAELogOut:
2113             case kAEReallyLogOut:
2114                 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2115                 break;
2116             case kAEShowRestartDialog:
2117                 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2118                 break;
2119             case kAEShowShutdownDialog:
2120                 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2121                 break;
2122             default:
2123                 event->app_quit_requested.reason = QUIT_REASON_NONE;
2124                 break;
2125         }
2127         [eventQueuesLock lock];
2129         if ([eventQueues count])
2130         {
2131             for (queue in eventQueues)
2132                 [queue postEvent:event];
2133             ret = NSTerminateLater;
2134         }
2136         [eventQueuesLock unlock];
2138         macdrv_release_event(event);
2140         return ret;
2141     }
2143     - (void)applicationWillBecomeActive:(NSNotification *)notification
2144     {
2145         macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2146         event->deliver = 1;
2148         [eventQueuesLock lock];
2149         for (WineEventQueue* queue in eventQueues)
2150             [queue postEvent:event];
2151         [eventQueuesLock unlock];
2153         macdrv_release_event(event);
2154     }
2156     - (void)applicationWillResignActive:(NSNotification *)notification
2157     {
2158         [self adjustWindowLevels:NO];
2159     }
2161 /***********************************************************************
2162  *              PerformRequest
2164  * Run-loop-source perform callback.  Pull request blocks from the
2165  * array of queued requests and invoke them.
2166  */
2167 static void PerformRequest(void *info)
2169     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2170     WineApplicationController* controller = [WineApplicationController sharedController];
2172     for (;;)
2173     {
2174         __block dispatch_block_t block;
2176         dispatch_sync(controller->requestsManipQueue, ^{
2177             if ([controller->requests count])
2178             {
2179                 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2180                 [controller->requests removeObjectAtIndex:0];
2181             }
2182             else
2183                 block = nil;
2184         });
2186         if (!block)
2187             break;
2189         block();
2190         [block release];
2192         [pool release];
2193         pool = [[NSAutoreleasePool alloc] init];
2194     }
2196     [pool release];
2199 /***********************************************************************
2200  *              OnMainThreadAsync
2202  * Run a block on the main thread asynchronously.
2203  */
2204 void OnMainThreadAsync(dispatch_block_t block)
2206     WineApplicationController* controller = [WineApplicationController sharedController];
2208     block = [block copy];
2209     dispatch_sync(controller->requestsManipQueue, ^{
2210         [controller->requests addObject:block];
2211     });
2212     [block release];
2213     CFRunLoopSourceSignal(controller->requestSource);
2214     CFRunLoopWakeUp(CFRunLoopGetMain());
2217 @end
2219 /***********************************************************************
2220  *              LogError
2221  */
2222 void LogError(const char* func, NSString* format, ...)
2224     va_list args;
2225     va_start(args, format);
2226     LogErrorv(func, format, args);
2227     va_end(args);
2230 /***********************************************************************
2231  *              LogErrorv
2232  */
2233 void LogErrorv(const char* func, NSString* format, va_list args)
2235     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2237     NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2238     fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2239     [message release];
2241     [pool release];
2244 /***********************************************************************
2245  *              macdrv_window_rejected_focus
2247  * Pass focus to the next window that hasn't already rejected this same
2248  * WINDOW_GOT_FOCUS event.
2249  */
2250 void macdrv_window_rejected_focus(const macdrv_event *event)
2252     OnMainThread(^{
2253         [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2254     });
2257 /***********************************************************************
2258  *              macdrv_get_input_source_info
2260  * Returns the keyboard layout uchr data, keyboard type and input source.
2261  */
2262 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2264     OnMainThread(^{
2265         TISInputSourceRef inputSourceLayout;
2267         inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2268         if (inputSourceLayout)
2269         {
2270             CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2271                                 kTISPropertyUnicodeKeyLayoutData);
2272             *uchr = CFDataCreateCopy(NULL, data);
2273             CFRelease(inputSourceLayout);
2275             *keyboard_type = [WineApplicationController sharedController].keyboardType;
2276             *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2277             if (input_source)
2278                 *input_source = TISCopyCurrentKeyboardInputSource();
2279         }
2280     });
2283 /***********************************************************************
2284  *              macdrv_beep
2286  * Play the beep sound configured by the user in System Preferences.
2287  */
2288 void macdrv_beep(void)
2290     OnMainThreadAsync(^{
2291         NSBeep();
2292     });
2295 /***********************************************************************
2296  *              macdrv_set_display_mode
2297  */
2298 int macdrv_set_display_mode(const struct macdrv_display* display,
2299                             CGDisplayModeRef display_mode)
2301     __block int ret;
2303     OnMainThread(^{
2304         ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2305     });
2307     return ret;
2310 /***********************************************************************
2311  *              macdrv_set_cursor
2313  * Set the cursor.
2315  * If name is non-NULL, it is a selector for a class method on NSCursor
2316  * identifying the cursor to set.  In that case, frames is ignored.  If
2317  * name is NULL, then frames is used.
2319  * frames is an array of dictionaries.  Each dictionary is a frame of
2320  * an animated cursor.  Under the key "image" is a CGImage for the
2321  * frame.  Under the key "duration" is a CFNumber time interval, in
2322  * seconds, for how long that frame is presented before proceeding to
2323  * the next frame.  Under the key "hotSpot" is a CFDictionary encoding a
2324  * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2325  * This is the hot spot, measured in pixels down and to the right of the
2326  * top-left corner of the image.
2328  * If the array has exactly 1 element, the cursor is static, not
2329  * animated.  If frames is NULL or has 0 elements, the cursor is hidden.
2330  */
2331 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2333     SEL sel;
2335     sel = NSSelectorFromString((NSString*)name);
2336     if (sel)
2337     {
2338         OnMainThreadAsync(^{
2339             WineApplicationController* controller = [WineApplicationController sharedController];
2340             [controller setCursorWithFrames:nil];
2341             controller.cursor = [NSCursor performSelector:sel];
2342             [controller unhideCursor];
2343         });
2344     }
2345     else
2346     {
2347         NSArray* nsframes = (NSArray*)frames;
2348         if ([nsframes count])
2349         {
2350             OnMainThreadAsync(^{
2351                 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2352             });
2353         }
2354         else
2355         {
2356             OnMainThreadAsync(^{
2357                 WineApplicationController* controller = [WineApplicationController sharedController];
2358                 [controller setCursorWithFrames:nil];
2359                 [controller hideCursor];
2360             });
2361         }
2362     }
2365 /***********************************************************************
2366  *              macdrv_get_cursor_position
2368  * Obtains the current cursor position.  Returns zero on failure,
2369  * non-zero on success.
2370  */
2371 int macdrv_get_cursor_position(CGPoint *pos)
2373     OnMainThread(^{
2374         NSPoint location = [NSEvent mouseLocation];
2375         location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2376         *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2377     });
2379     return TRUE;
2382 /***********************************************************************
2383  *              macdrv_set_cursor_position
2385  * Sets the cursor position without generating events.  Returns zero on
2386  * failure, non-zero on success.
2387  */
2388 int macdrv_set_cursor_position(CGPoint pos)
2390     __block int ret;
2392     OnMainThread(^{
2393         ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2394     });
2396     return ret;
2399 /***********************************************************************
2400  *              macdrv_clip_cursor
2402  * Sets the cursor cursor clipping rectangle.  If the rectangle is equal
2403  * to or larger than the whole desktop region, the cursor is unclipped.
2404  * Returns zero on failure, non-zero on success.
2405  */
2406 int macdrv_clip_cursor(CGRect r)
2408     __block int ret;
2410     OnMainThread(^{
2411         WineApplicationController* controller = [WineApplicationController sharedController];
2412         BOOL clipping = FALSE;
2413         CGRect rect = r;
2415         if (!CGRectIsInfinite(rect))
2416             rect = cgrect_mac_from_win(rect);
2418         if (!CGRectIsInfinite(rect))
2419         {
2420             NSRect nsrect = NSRectFromCGRect(rect);
2421             NSScreen* screen;
2423             /* Convert the rectangle from top-down coords to bottom-up. */
2424             [controller flipRect:&nsrect];
2426             clipping = FALSE;
2427             for (screen in [NSScreen screens])
2428             {
2429                 if (!NSContainsRect(nsrect, [screen frame]))
2430                 {
2431                     clipping = TRUE;
2432                     break;
2433                 }
2434             }
2435         }
2437         if (clipping)
2438             ret = [controller startClippingCursor:rect];
2439         else
2440             ret = [controller stopClippingCursor];
2441     });
2443     return ret;
2446 /***********************************************************************
2447  *              macdrv_set_application_icon
2449  * Set the application icon.  The images array contains CGImages.  If
2450  * there are more than one, then they represent different sizes or
2451  * color depths from the icon resource.  If images is NULL or empty,
2452  * restores the default application image.
2453  */
2454 void macdrv_set_application_icon(CFArrayRef images)
2456     NSArray* imageArray = (NSArray*)images;
2458     OnMainThreadAsync(^{
2459         [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2460     });
2463 /***********************************************************************
2464  *              macdrv_quit_reply
2465  */
2466 void macdrv_quit_reply(int reply)
2468     OnMainThread(^{
2469         [NSApp replyToApplicationShouldTerminate:reply];
2470     });
2473 /***********************************************************************
2474  *              macdrv_using_input_method
2475  */
2476 int macdrv_using_input_method(void)
2478     __block BOOL ret;
2480     OnMainThread(^{
2481         ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2482     });
2484     return ret;
2487 /***********************************************************************
2488  *              macdrv_set_mouse_capture_window
2489  */
2490 void macdrv_set_mouse_capture_window(macdrv_window window)
2492     WineWindow* w = (WineWindow*)window;
2494     [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2496     OnMainThread(^{
2497         [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2498     });
2501 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2502 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2503 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2505 /***********************************************************************
2506  *              macdrv_create_input_source_list
2507  */
2508 CFArrayRef macdrv_create_input_source_list(void)
2510     CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2512     OnMainThread(^{
2513         CFArrayRef input_list;
2514         CFDictionaryRef filter_dict;
2515         const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2516         const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2517         int i;
2519         filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2520                                          &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2521         input_list = TISCreateInputSourceList(filter_dict, false);
2523         for (i = 0; i < CFArrayGetCount(input_list); i++)
2524         {
2525             TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2526             CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2527             CFDictionaryRef entry;
2528             const void *input_keys[3] = { macdrv_input_source_input_key,
2529                                           macdrv_input_source_type_key,
2530                                           macdrv_input_source_lang_key };
2531             const void *input_values[3];
2533             input_values[0] = input;
2534             input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2535             input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2537             entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2538                                        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2540             CFArrayAppendValue(ret, entry);
2541             CFRelease(entry);
2542         }
2543         CFRelease(input_list);
2544         CFRelease(filter_dict);
2545     });
2547     return ret;
2550 int macdrv_select_input_source(TISInputSourceRef input_source)
2552     __block int ret = FALSE;
2554     OnMainThread(^{
2555         ret = (TISSelectInputSource(input_source) == noErr);
2556     });
2558     return ret;
2561 void macdrv_set_cocoa_retina_mode(int new_mode)
2563     OnMainThread(^{
2564         [[WineApplicationController sharedController] setRetinaMode:new_mode];
2565     });
2568 int macdrv_is_any_wine_window_visible(void)
2570     __block int ret = FALSE;
2572     OnMainThread(^{
2573         ret = [[WineApplicationController sharedController] isAnyWineWindowVisible];
2574     });
2576     return ret;