2 * MACDRV Cocoa application class
4 * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
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.
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.
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
21 #import <Carbon/Carbon.h>
24 #import "cocoa_event.h"
25 #import "cocoa_window.h"
28 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
34 #if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
35 @interface NSWindow (WineAutoTabbingExtensions)
37 + (void) setAllowsAutomaticWindowTabbing:(BOOL)allows;
43 /***********************************************************************
46 * Look up a localized string by its ID in the dictionary.
48 static NSString* WineLocalizedString(unsigned int stringID)
50 NSNumber* key = [NSNumber numberWithUnsignedInt:stringID];
51 return [(NSDictionary*)localized_strings objectForKey:key];
55 @implementation WineApplication
57 @synthesize wineController;
59 - (void) sendEvent:(NSEvent*)anEvent
61 if (![wineController handleEvent:anEvent])
63 [super sendEvent:anEvent];
64 [wineController didSendEvent:anEvent];
68 - (void) setWineController:(WineApplicationController*)newController
70 wineController = newController;
71 [self setDelegate:wineController];
77 @interface WarpRecord : NSObject
79 CGEventTimestamp timeBefore, timeAfter;
83 @property (nonatomic) CGEventTimestamp timeBefore;
84 @property (nonatomic) CGEventTimestamp timeAfter;
85 @property (nonatomic) CGPoint from;
86 @property (nonatomic) CGPoint to;
91 @implementation WarpRecord
93 @synthesize timeBefore, timeAfter, from, to;
98 @interface WineApplicationController ()
100 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
101 @property (copy, nonatomic) NSArray* cursorFrames;
102 @property (retain, nonatomic) NSTimer* cursorTimer;
103 @property (retain, nonatomic) NSCursor* cursor;
104 @property (retain, nonatomic) NSImage* applicationIcon;
105 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
106 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
108 - (void) setupObservations;
109 - (void) applicationDidBecomeActive:(NSNotification *)notification;
111 static void PerformRequest(void *info);
116 @implementation WineApplicationController
118 @synthesize keyboardType, lastFlagsChanged;
119 @synthesize applicationIcon;
120 @synthesize cursorFrames, cursorTimer, cursor;
121 @synthesize mouseCaptureWindow;
123 @synthesize clippingCursor;
127 if (self == [WineApplicationController class])
129 NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
130 @"", @"NSQuotedKeystrokeBinding",
131 @"", @"NSRepeatCountBinding",
132 [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
134 [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
136 if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
137 [NSWindow setAllowsAutomaticWindowTabbing:NO];
141 + (WineApplicationController*) sharedController
143 static WineApplicationController* sharedController;
144 static dispatch_once_t once;
146 dispatch_once(&once, ^{
147 sharedController = [[self alloc] init];
150 return sharedController;
158 CFRunLoopSourceContext context = { 0 };
159 context.perform = PerformRequest;
160 requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
166 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
167 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
169 requests = [[NSMutableArray alloc] init];
170 requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
172 eventQueues = [[NSMutableArray alloc] init];
173 eventQueuesLock = [[NSLock alloc] init];
175 keyWindows = [[NSMutableArray alloc] init];
177 originalDisplayModes = [[NSMutableDictionary alloc] init];
178 latentDisplayModes = [[NSMutableDictionary alloc] init];
180 warpRecords = [[NSMutableArray alloc] init];
182 windowsBeingDragged = [[NSMutableSet alloc] init];
184 if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
185 !keyWindows || !originalDisplayModes || !latentDisplayModes || !warpRecords)
191 [self setupObservations];
193 keyboardType = LMGetKbdType();
195 if ([NSApp isActive])
196 [self applicationDidBecomeActive:nil];
203 [windowsBeingDragged release];
205 [screenFrameCGRects release];
206 [applicationIcon release];
207 [warpRecords release];
208 [cursorTimer release];
209 [cursorFrames release];
210 [latentDisplayModes release];
211 [originalDisplayModes release];
212 [keyWindows release];
213 [eventQueues release];
214 [eventQueuesLock release];
215 if (requestsManipQueue) dispatch_release(requestsManipQueue);
219 CFRunLoopSourceInvalidate(requestSource);
220 CFRelease(requestSource);
225 - (void) transformProcessToForeground
227 if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
231 NSString* bundleName;
235 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
236 [NSApp activateIgnoringOtherApps:YES];
237 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
238 if (!enable_app_nap && [NSProcessInfo instancesRespondToSelector:@selector(beginActivityWithOptions:reason:)])
240 [[[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep
241 reason:@"Running Windows program"] retain]; // intentional leak
245 mainMenu = [[[NSMenu alloc] init] autorelease];
248 submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
249 bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
251 if ([bundleName length])
252 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
254 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
255 item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
257 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
258 action:@selector(hideOtherApplications:)
260 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
262 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
263 action:@selector(unhideAllApplications:)
266 [submenu addItem:[NSMenuItem separatorItem]];
268 if ([bundleName length])
269 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
271 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
272 item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
273 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
274 item = [[[NSMenuItem alloc] init] autorelease];
275 [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
276 [item setSubmenu:submenu];
277 [mainMenu addItem:item];
280 submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
281 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
282 action:@selector(performMiniaturize:)
284 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
285 action:@selector(performZoom:)
287 if ([NSWindow instancesRespondToSelector:@selector(toggleFullScreen:)])
289 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
290 action:@selector(toggleFullScreen:)
292 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand |
293 NSEventModifierFlagOption |
294 NSEventModifierFlagControl];
296 [submenu addItem:[NSMenuItem separatorItem]];
297 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
298 action:@selector(arrangeInFront:)
300 item = [[[NSMenuItem alloc] init] autorelease];
301 [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
302 [item setSubmenu:submenu];
303 [mainMenu addItem:item];
305 [NSApp setMainMenu:mainMenu];
306 [NSApp setWindowsMenu:submenu];
308 [NSApp setApplicationIconImage:self.applicationIcon];
312 - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
314 PerformRequest(NULL);
320 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
321 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
323 inMode:NSDefaultRunLoopMode
326 [NSApp sendEvent:event];
330 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
331 } while (!*done && [timeout timeIntervalSinceNow] >= 0);
336 - (BOOL) registerEventQueue:(WineEventQueue*)queue
338 [eventQueuesLock lock];
339 [eventQueues addObject:queue];
340 [eventQueuesLock unlock];
344 - (void) unregisterEventQueue:(WineEventQueue*)queue
346 [eventQueuesLock lock];
347 [eventQueues removeObjectIdenticalTo:queue];
348 [eventQueuesLock unlock];
351 - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
353 eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
356 - (double) ticksForEventTime:(NSTimeInterval)eventTime
358 return (eventTime + eventTimeAdjustment) * 1000;
361 /* Invalidate old focus offers across all queues. */
362 - (void) invalidateGotFocusEvents
364 WineEventQueue* queue;
368 [eventQueuesLock lock];
369 for (queue in eventQueues)
371 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
374 [eventQueuesLock unlock];
377 - (void) windowGotFocus:(WineWindow*)window
381 [self invalidateGotFocusEvents];
383 event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
384 event->window_got_focus.serial = windowFocusSerial;
386 event->window_got_focus.tried_windows = [triedWindows retain];
388 event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
389 [window.queue postEvent:event];
390 macdrv_release_event(event);
393 - (void) windowRejectedFocusEvent:(const macdrv_event*)event
395 if (event->window_got_focus.serial == windowFocusSerial)
397 NSMutableArray* windows = [keyWindows mutableCopy];
398 NSNumber* windowNumber;
401 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
403 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
404 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
405 ![windows containsObject:window])
406 [windows addObject:window];
409 triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
410 [triedWindows addObject:(WineWindow*)event->window];
411 for (window in windows)
413 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
415 [window makeKeyWindow];
424 static BOOL EqualInputSource(TISInputSourceRef source1, TISInputSourceRef source2)
426 if (!source1 && !source2)
428 if (!source1 || !source2)
430 return CFEqual(source1, source2);
433 - (void) keyboardSelectionDidChange:(BOOL)force
435 TISInputSourceRef inputSource, inputSourceLayout;
439 NSTextInputContext* context = [NSTextInputContext currentInputContext];
440 if (!context || ![context client])
444 inputSource = TISCopyCurrentKeyboardInputSource();
445 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
446 if (!force && EqualInputSource(inputSource, lastKeyboardInputSource) &&
447 EqualInputSource(inputSourceLayout, lastKeyboardLayoutInputSource))
449 if (inputSource) CFRelease(inputSource);
450 if (inputSourceLayout) CFRelease(inputSourceLayout);
454 if (lastKeyboardInputSource)
455 CFRelease(lastKeyboardInputSource);
456 lastKeyboardInputSource = inputSource;
457 if (lastKeyboardLayoutInputSource)
458 CFRelease(lastKeyboardLayoutInputSource);
459 lastKeyboardLayoutInputSource = inputSourceLayout;
461 inputSourceIsInputMethodValid = FALSE;
463 if (inputSourceLayout)
466 uchr = TISGetInputSourceProperty(inputSourceLayout,
467 kTISPropertyUnicodeKeyLayoutData);
471 WineEventQueue* queue;
473 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
474 event->keyboard_changed.keyboard_type = self.keyboardType;
475 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
476 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
477 event->keyboard_changed.input_source = (TISInputSourceRef)CFRetain(inputSource);
479 if (event->keyboard_changed.uchr)
481 [eventQueuesLock lock];
483 for (queue in eventQueues)
484 [queue postEvent:event];
486 [eventQueuesLock unlock];
489 macdrv_release_event(event);
494 - (void) keyboardSelectionDidChange
496 [self keyboardSelectionDidChange:NO];
499 - (void) setKeyboardType:(CGEventSourceKeyboardType)newType
501 if (newType != keyboardType)
503 keyboardType = newType;
504 [self keyboardSelectionDidChange:YES];
508 - (void) enabledKeyboardInputSourcesChanged
510 macdrv_layout_list_needs_update = TRUE;
513 - (CGFloat) primaryScreenHeight
515 if (!primaryScreenHeightValid)
517 NSArray* screens = [NSScreen screens];
518 NSUInteger count = [screens count];
525 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
526 primaryScreenHeightValid = TRUE;
528 size = count * sizeof(CGRect);
529 if (!screenFrameCGRects)
530 screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
532 [screenFrameCGRects setLength:size];
534 rect = [screenFrameCGRects mutableBytes];
535 for (screen in screens)
537 CGRect temp = NSRectToCGRect([screen frame]);
538 temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
543 return 1280; /* arbitrary value */
546 return primaryScreenHeight;
549 - (NSPoint) flippedMouseLocation:(NSPoint)point
551 /* This relies on the fact that Cocoa's mouse location points are
552 actually off by one (precisely because they were flipped from
553 Quartz screen coordinates using this same technique). */
554 point.y = [self primaryScreenHeight] - point.y;
558 - (void) flipRect:(NSRect*)rect
560 // We don't use -primaryScreenHeight here so there's no chance of having
561 // out-of-date cached info. This method is called infrequently enough
562 // that getting the screen height each time is not prohibitively expensive.
563 rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
566 - (WineWindow*) frontWineWindow
568 NSNumber* windowNumber;
569 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
571 NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
572 if ([window isKindOfClass:[WineWindow class]] && [window screen])
573 return (WineWindow*)window;
579 - (void) adjustWindowLevels:(BOOL)active
581 NSArray* windowNumbers;
582 NSMutableArray* wineWindows;
583 NSNumber* windowNumber;
584 NSUInteger nextFloatingIndex = 0;
585 __block NSInteger maxLevel = NSIntegerMin;
586 __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
587 __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
588 __block WineWindow* prev = nil;
591 if ([NSApp isHidden]) return;
593 windowNumbers = [NSWindow windowNumbersWithOptions:0];
594 wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
596 // For the most part, we rely on the window server's ordering of the windows
597 // to be authoritative. The one exception is if the "floating" property of
598 // one of the windows has been changed, it may be in the wrong level and thus
599 // in the order. This method is what's supposed to fix that up. So build
600 // a list of Wine windows sorted first by floating-ness and then by order
601 // as indicated by the window server.
602 for (windowNumber in windowNumbers)
604 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
605 if ([window isKindOfClass:[WineWindow class]])
608 [wineWindows insertObject:window atIndex:nextFloatingIndex++];
610 [wineWindows addObject:window];
614 NSDisableScreenUpdates();
616 // Go from back to front so that all windows in front of one which is
617 // elevated for full-screen are also elevated.
618 [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
619 usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
620 WineWindow* window = (WineWindow*)obj;
621 NSInteger origLevel = [window level];
622 NSInteger newLevel = [window minimumLevelForActive:active];
626 if (minFloatingLevel <= maxNonfloatingLevel)
627 minFloatingLevel = maxNonfloatingLevel + 1;
628 if (newLevel < minFloatingLevel)
629 newLevel = minFloatingLevel;
632 if (newLevel < maxLevel)
637 if (!window.floating && maxNonfloatingLevel < newLevel)
638 maxNonfloatingLevel = newLevel;
640 if (newLevel != origLevel)
642 [window setLevel:newLevel];
644 // -setLevel: puts the window at the front of its new level. If
645 // we decreased the level, that's good (it was in front of that
646 // level before, so it should still be now). But if we increased
647 // the level, the window should be toward the back (but still
648 // ahead of the previous windows we did this to).
649 if (origLevel < newLevel)
652 [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
654 [window orderBack:nil];
661 NSEnableScreenUpdates();
663 [wineWindows release];
665 // The above took care of the visible windows on the current space. That
666 // leaves windows on other spaces, minimized windows, and windows which
667 // are not ordered in. We want to leave windows on other spaces alone
668 // so the space remains just as they left it (when viewed in Exposé or
669 // Mission Control, for example). We'll adjust the window levels again
670 // after we switch to another space, anyway. Windows which aren't
671 // ordered in will be handled when we order them in. Minimized windows
672 // on the current space should be set to the level they would have gotten
673 // if they were at the front of the windows with the same floating-ness,
674 // because that's where they'll go if/when they are unminimized. Again,
675 // for good measure we'll adjust window levels again when a window is
677 for (window in [NSApp windows])
679 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
680 [window isOnActiveSpace])
682 NSInteger origLevel = [window level];
683 NSInteger newLevel = [window minimumLevelForActive:YES];
684 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
686 if (newLevel < maxLevelForType)
687 newLevel = maxLevelForType;
689 if (newLevel != origLevel)
690 [window setLevel:newLevel];
695 - (void) adjustWindowLevels
697 [self adjustWindowLevels:[NSApp isActive]];
700 - (void) updateFullscreenWindows
702 if (capture_displays_for_fullscreen && [NSApp isActive])
704 BOOL anyFullscreen = FALSE;
705 NSNumber* windowNumber;
706 for (windowNumber in [NSWindow windowNumbersWithOptions:0])
708 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
709 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
711 anyFullscreen = TRUE;
718 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
719 displaysCapturedForFullscreen = TRUE;
721 else if (displaysCapturedForFullscreen)
723 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
724 displaysCapturedForFullscreen = FALSE;
729 - (void) activeSpaceDidChange
731 [self updateFullscreenWindows];
732 [self adjustWindowLevels];
735 - (void) sendDisplaysChanged:(BOOL)activating
738 WineEventQueue* queue;
740 event = macdrv_create_event(DISPLAYS_CHANGED, nil);
741 event->displays_changed.activating = activating;
743 [eventQueuesLock lock];
745 // If we're activating, then we just need one of our threads to get the
746 // event, so it can send it directly to the desktop window. Otherwise,
747 // we need all of the threads to get it because we don't know which owns
748 // the desktop window and only that one will do anything with it.
749 if (activating) event->deliver = 1;
751 for (queue in eventQueues)
752 [queue postEvent:event];
753 [eventQueuesLock unlock];
755 macdrv_release_event(event);
758 // We can compare two modes directly using CFEqual, but that may require that
759 // they are identical to a level that we don't need. In particular, when the
760 // OS switches between the integrated and discrete GPUs, the set of display
761 // modes can change in subtle ways. We're interested in whether two modes
762 // match in their most salient features, even if they aren't identical.
763 - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
765 NSString *encoding1, *encoding2;
766 uint32_t ioflags1, ioflags2, different;
767 double refresh1, refresh2;
769 if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
770 if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
772 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
773 if (&CGDisplayModeGetPixelWidth != NULL &&
774 CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
775 if (&CGDisplayModeGetPixelHeight != NULL &&
776 CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
779 encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
780 encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
781 if (![encoding1 isEqualToString:encoding2]) return FALSE;
783 ioflags1 = CGDisplayModeGetIOFlags(mode1);
784 ioflags2 = CGDisplayModeGetIOFlags(mode2);
785 different = ioflags1 ^ ioflags2;
786 if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
787 kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
790 refresh1 = CGDisplayModeGetRefreshRate(mode1);
791 if (refresh1 == 0) refresh1 = 60;
792 refresh2 = CGDisplayModeGetRefreshRate(mode2);
793 if (refresh2 == 0) refresh2 = 60;
794 if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
799 - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
801 NSMutableArray* ret = [NSMutableArray array];
802 NSDictionary* options = nil;
804 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
805 options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:TRUE]
806 forKey:(NSString*)kCGDisplayShowDuplicateLowResolutionModes];
809 NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
810 for (id candidateModeObject in modes)
812 CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
813 if ([self mode:candidateMode matchesMode:mode])
814 [ret addObject:candidateModeObject];
819 - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
822 NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
823 CGDisplayModeRef originalMode;
825 originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
827 if (originalMode && [self mode:mode matchesMode:originalMode])
829 if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
831 CGRestorePermanentDisplayConfiguration();
832 if (!displaysCapturedForFullscreen)
833 CGReleaseAllDisplays();
834 [originalDisplayModes removeAllObjects];
837 else // ... otherwise, try to restore just the one display
839 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
841 mode = (CGDisplayModeRef)modeObject;
842 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
844 [originalDisplayModes removeObjectForKey:displayIDKey];
853 CGDisplayModeRef currentMode;
856 currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
858 currentMode = CGDisplayCopyDisplayMode(displayID);
859 if (!currentMode) // Invalid display ID
862 if ([self mode:mode matchesMode:currentMode]) // Already there!
864 CGDisplayModeRelease(currentMode);
868 CGDisplayModeRelease(currentMode);
871 modes = [self modesMatchingMode:mode forDisplay:displayID];
875 [self transformProcessToForeground];
877 BOOL active = [NSApp isActive];
879 if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
880 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
884 // If we get here, we have the displays captured. If we don't
885 // know the original mode of the display, the current mode must
886 // be the original. We should re-query the current mode since
887 // another process could have changed it between when we last
888 // checked and when we captured the displays.
890 originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
894 for (id modeObject in modes)
896 mode = (CGDisplayModeRef)modeObject;
897 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
904 if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
905 [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
906 else if (![originalDisplayModes count])
908 CGRestorePermanentDisplayConfiguration();
909 if (!displaysCapturedForFullscreen)
910 CGReleaseAllDisplays();
914 CGDisplayModeRelease(currentMode);
918 [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
925 [self adjustWindowLevels];
930 - (BOOL) areDisplaysCaptured
932 return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
935 - (void) updateCursor:(BOOL)force
937 if (force || lastTargetWindow)
939 if (clientWantsCursorHidden && !cursorHidden)
945 if (!cursorIsCurrent)
948 cursorIsCurrent = TRUE;
951 if (!clientWantsCursorHidden && cursorHidden)
954 cursorHidden = FALSE;
961 [[NSCursor arrowCursor] set];
962 cursorIsCurrent = FALSE;
967 cursorHidden = FALSE;
974 if (!clientWantsCursorHidden)
976 clientWantsCursorHidden = TRUE;
977 [self updateCursor:TRUE];
981 - (void) unhideCursor
983 if (clientWantsCursorHidden)
985 clientWantsCursorHidden = FALSE;
986 [self updateCursor:FALSE];
990 - (void) setCursor:(NSCursor*)newCursor
992 if (newCursor != cursor)
995 cursor = [newCursor retain];
996 cursorIsCurrent = FALSE;
997 [self updateCursor:FALSE];
1003 NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
1004 CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
1005 CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
1006 NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
1007 CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
1010 if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
1011 hotSpot = CGPointZero;
1012 hotSpot = cgpoint_mac_from_win(hotSpot);
1013 self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1015 [self unhideCursor];
1018 - (void) nextCursorFrame:(NSTimer*)theTimer
1020 NSDictionary* frame;
1021 NSTimeInterval duration;
1025 if (cursorFrame >= [cursorFrames count])
1029 frame = [cursorFrames objectAtIndex:cursorFrame];
1030 duration = [[frame objectForKey:@"duration"] doubleValue];
1031 date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1032 [cursorTimer setFireDate:date];
1035 - (void) setCursorWithFrames:(NSArray*)frames
1037 if (self.cursorFrames == frames)
1040 self.cursorFrames = frames;
1042 [cursorTimer invalidate];
1043 self.cursorTimer = nil;
1047 if ([frames count] > 1)
1049 NSDictionary* frame = [frames objectAtIndex:0];
1050 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
1051 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1052 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1055 selector:@selector(nextCursorFrame:)
1057 repeats:YES] autorelease];
1058 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1065 - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1067 NSImage* nsimage = nil;
1071 NSSize bestSize = NSZeroSize;
1074 nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1076 for (image in images)
1078 CGImageRef cgimage = (CGImageRef)image;
1079 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1082 NSSize size = [imageRep size];
1084 [nsimage addRepresentation:imageRep];
1087 if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1092 if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1093 [nsimage setSize:bestSize];
1098 self.applicationIcon = nsimage;
1101 - (void) handleCommandTab
1103 if ([NSApp isActive])
1105 NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1106 NSRunningApplication* app;
1107 NSRunningApplication* otherValidApp = nil;
1109 if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1111 NSNumber* displayID;
1112 for (displayID in originalDisplayModes)
1114 CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1115 [latentDisplayModes setObject:(id)mode forKey:displayID];
1116 CGDisplayModeRelease(mode);
1119 CGRestorePermanentDisplayConfiguration();
1120 CGReleaseAllDisplays();
1121 [originalDisplayModes removeAllObjects];
1122 displaysCapturedForFullscreen = FALSE;
1125 for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1127 if (![app isEqual:thisApp] && !app.terminated &&
1128 app.activationPolicy == NSApplicationActivationPolicyRegular)
1132 // There's another visible app. Just hide ourselves and let
1133 // the system activate the other app.
1139 otherValidApp = app;
1143 // Didn't find a visible GUI app. Try the Finder or, if that's not
1144 // running, the first hidden GUI app. If even that doesn't work, we
1145 // just fail to switch and remain the active app.
1146 app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1147 if (!app) app = otherValidApp;
1149 [app activateWithOptions:0];
1154 * ---------- Cursor clipping methods ----------
1156 * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1157 * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1158 * equivalent: CGAssociateMouseAndMouseCursorPosition(false). For the
1159 * general case, we leverage that. We disassociate mouse movements from
1160 * the cursor position and then move the cursor manually, keeping it within
1161 * the clipping rectangle.
1163 * Moving the cursor manually isn't enough. We need to modify the event
1164 * stream so that the events have the new location, too. We need to do
1165 * this at a point before the events enter Cocoa, so that Cocoa will assign
1166 * the correct window to the event. So, we install a Quartz event tap to
1169 * Also, there's a complication when we move the cursor. We use
1170 * CGWarpMouseCursorPosition(). That doesn't generate mouse movement
1171 * events, but the change of cursor position is incorporated into the
1172 * deltas of the next mouse move event. When the mouse is disassociated
1173 * from the cursor position, we need the deltas to only reflect actual
1174 * device movement, not programmatic changes. So, the event tap cancels
1175 * out the change caused by our calls to CGWarpMouseCursorPosition().
1177 - (void) clipCursorLocation:(CGPoint*)location
1179 if (location->x < CGRectGetMinX(cursorClipRect))
1180 location->x = CGRectGetMinX(cursorClipRect);
1181 if (location->y < CGRectGetMinY(cursorClipRect))
1182 location->y = CGRectGetMinY(cursorClipRect);
1183 if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1184 location->x = CGRectGetMaxX(cursorClipRect) - 1;
1185 if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1186 location->y = CGRectGetMaxY(cursorClipRect) - 1;
1189 - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1191 CGPoint oldLocation;
1193 if (currentLocation)
1194 oldLocation = *currentLocation;
1196 oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1198 if (!CGPointEqualToPoint(oldLocation, *newLocation))
1200 WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1203 warpRecord.from = oldLocation;
1204 warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1206 /* Actually move the cursor. */
1207 err = CGWarpMouseCursorPosition(*newLocation);
1208 if (err != kCGErrorSuccess)
1211 warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1212 *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1214 if (!CGPointEqualToPoint(oldLocation, *newLocation))
1216 warpRecord.to = *newLocation;
1217 [warpRecords addObject:warpRecord];
1224 - (BOOL) isMouseMoveEventType:(CGEventType)type
1228 case kCGEventMouseMoved:
1229 case kCGEventLeftMouseDragged:
1230 case kCGEventRightMouseDragged:
1231 case kCGEventOtherMouseDragged:
1238 - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1240 int warpsFinished = 0;
1241 for (WarpRecord* warpRecord in warpRecords)
1243 if (warpRecord.timeAfter < eventTime ||
1244 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1250 return warpsFinished;
1253 - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1254 type:(CGEventType)type
1255 event:(CGEventRef)event
1257 CGEventTimestamp eventTime;
1258 CGPoint eventLocation, cursorLocation;
1260 if (type == kCGEventTapDisabledByUserInput)
1262 if (type == kCGEventTapDisabledByTimeout)
1264 CGEventTapEnable(cursorClippingEventTap, TRUE);
1268 if (!clippingCursor)
1271 eventTime = CGEventGetTimestamp(event);
1272 lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1274 eventLocation = CGEventGetLocation(event);
1276 cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1278 if ([self isMouseMoveEventType:type])
1280 double deltaX, deltaY;
1281 int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1284 deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1285 deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1287 for (i = 0; i < warpsFinished; i++)
1289 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1290 deltaX -= warpRecord.to.x - warpRecord.from.x;
1291 deltaY -= warpRecord.to.y - warpRecord.from.y;
1292 [warpRecords removeObjectAtIndex:0];
1297 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1298 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1301 synthesizedLocation.x += deltaX;
1302 synthesizedLocation.y += deltaY;
1305 // If the event is destined for another process, don't clip it. This may
1306 // happen if the user activates Exposé or Mission Control. In that case,
1307 // our app does not resign active status, so clipping is still in effect,
1308 // but the cursor should not actually be clipped.
1310 // In addition, the fact that mouse moves may have been delivered to a
1311 // different process means we have to treat the next one we receive as
1312 // absolute rather than relative.
1313 if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1314 [self clipCursorLocation:&synthesizedLocation];
1316 lastSetCursorPositionTime = lastEventTapEventTime;
1318 [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1319 if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1320 CGEventSetLocation(event, synthesizedLocation);
1325 CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1326 CGEventRef event, void *refcon)
1328 WineApplicationController* controller = refcon;
1329 return [controller eventTapWithProxy:proxy type:type event:event];
1332 - (BOOL) installEventTap
1334 CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown) |
1335 CGEventMaskBit(kCGEventLeftMouseUp) |
1336 CGEventMaskBit(kCGEventRightMouseDown) |
1337 CGEventMaskBit(kCGEventRightMouseUp) |
1338 CGEventMaskBit(kCGEventMouseMoved) |
1339 CGEventMaskBit(kCGEventLeftMouseDragged) |
1340 CGEventMaskBit(kCGEventRightMouseDragged) |
1341 CGEventMaskBit(kCGEventOtherMouseDown) |
1342 CGEventMaskBit(kCGEventOtherMouseUp) |
1343 CGEventMaskBit(kCGEventOtherMouseDragged) |
1344 CGEventMaskBit(kCGEventScrollWheel);
1345 CFRunLoopSourceRef source;
1347 if (cursorClippingEventTap)
1350 // We create an annotated session event tap rather than a process-specific
1351 // event tap because we need to programmatically move the cursor even when
1352 // mouse moves are directed to other processes. We disable our tap when
1353 // other processes are active, but things like Exposé are handled by other
1354 // processes even when we remain active.
1355 cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1356 kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1357 if (!cursorClippingEventTap)
1360 CGEventTapEnable(cursorClippingEventTap, FALSE);
1362 source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1365 CFRelease(cursorClippingEventTap);
1366 cursorClippingEventTap = NULL;
1370 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1375 - (BOOL) setCursorPosition:(CGPoint)pos
1379 if ([windowsBeingDragged count])
1381 else if (clippingCursor)
1383 [self clipCursorLocation:&pos];
1385 ret = [self warpCursorTo:&pos from:NULL];
1386 synthesizedLocation = pos;
1389 // We want to discard mouse-move events that have already been
1390 // through the event tap, because it's too late to account for
1391 // the setting of the cursor position with them. However, the
1392 // events that may be queued with times after that but before
1393 // the above warp can still be used. So, use the last event
1394 // tap event time so that -sendEvent: doesn't discard them.
1395 lastSetCursorPositionTime = lastEventTapEventTime;
1400 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1401 // the mouse from the cursor position for 0.25 seconds. This means
1402 // that mouse movement during that interval doesn't move the cursor
1403 // and events carry a constant location (the warped-to position)
1404 // even though they have delta values. For apps which warp the
1405 // cursor frequently (like after every mouse move), this makes
1406 // cursor movement horribly laggy and jerky, as only a fraction of
1407 // mouse move events have any effect.
1409 // On some versions of OS X, it's sufficient to forcibly reassociate
1410 // the mouse and cursor position. On others, it's necessary to set
1411 // the local events suppression interval to 0 for the warp. That's
1412 // deprecated, but I'm not aware of any other way. For good
1413 // measure, we do both.
1414 CGSetLocalEventsSuppressionInterval(0);
1415 ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1416 CGSetLocalEventsSuppressionInterval(0.25);
1419 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1421 CGAssociateMouseAndMouseCursorPosition(true);
1427 WineEventQueue* queue;
1429 // Discard all pending mouse move events.
1430 [eventQueuesLock lock];
1431 for (queue in eventQueues)
1433 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1434 event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1436 [queue resetMouseEventPositions:pos];
1438 [eventQueuesLock unlock];
1444 - (void) activateCursorClipping
1446 if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1448 CGEventTapEnable(cursorClippingEventTap, TRUE);
1449 [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1453 - (void) deactivateCursorClipping
1455 if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1457 CGEventTapEnable(cursorClippingEventTap, FALSE);
1458 [warpRecords removeAllObjects];
1459 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1463 - (void) updateCursorClippingState
1465 if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1466 [self activateCursorClipping];
1468 [self deactivateCursorClipping];
1471 - (void) updateWindowsForCursorClipping
1474 for (window in [NSApp windows])
1476 if ([window isKindOfClass:[WineWindow class]])
1477 [window updateForCursorClipping];
1481 - (BOOL) startClippingCursor:(CGRect)rect
1485 if (!cursorClippingEventTap && ![self installEventTap])
1488 if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1489 CGEventTapIsEnabled(cursorClippingEventTap))
1492 err = CGAssociateMouseAndMouseCursorPosition(false);
1493 if (err != kCGErrorSuccess)
1496 clippingCursor = TRUE;
1497 cursorClipRect = rect;
1498 [self updateCursorClippingState];
1499 [self updateWindowsForCursorClipping];
1504 - (BOOL) stopClippingCursor
1506 CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1507 if (err != kCGErrorSuccess)
1510 clippingCursor = FALSE;
1511 [self updateCursorClippingState];
1512 [self updateWindowsForCursorClipping];
1517 - (BOOL) isKeyPressed:(uint16_t)keyCode
1519 int bits = sizeof(pressedKeyCodes[0]) * 8;
1520 int index = keyCode / bits;
1521 uint32_t mask = 1 << (keyCode % bits);
1522 return (pressedKeyCodes[index] & mask) != 0;
1525 - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1527 int bits = sizeof(pressedKeyCodes[0]) * 8;
1528 int index = keyCode / bits;
1529 uint32_t mask = 1 << (keyCode % bits);
1531 pressedKeyCodes[index] |= mask;
1533 pressedKeyCodes[index] &= ~mask;
1536 - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1539 [windowsBeingDragged addObject:window];
1541 [windowsBeingDragged removeObject:window];
1542 [self updateCursorClippingState];
1545 - (void) windowWillOrderOut:(WineWindow*)window
1547 if ([windowsBeingDragged containsObject:window])
1549 [self window:window isBeingDragged:NO];
1551 macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1552 [window.queue postEvent:event];
1553 macdrv_release_event(event);
1557 - (void) handleWindowDrag:(NSEvent*)anEvent begin:(BOOL)begin
1559 WineWindow* window = (WineWindow*)[anEvent window];
1560 if ([window isKindOfClass:[WineWindow class]])
1562 macdrv_event* event;
1567 [windowsBeingDragged addObject:window];
1568 eventType = WINDOW_DRAG_BEGIN;
1572 [windowsBeingDragged removeObject:window];
1573 eventType = WINDOW_DRAG_END;
1575 [self updateCursorClippingState];
1577 event = macdrv_create_event(eventType, window);
1578 if (eventType == WINDOW_DRAG_BEGIN)
1579 event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
1580 [window.queue postEvent:event];
1581 macdrv_release_event(event);
1585 - (void) handleMouseMove:(NSEvent*)anEvent
1587 WineWindow* targetWindow;
1588 BOOL drag = [anEvent type] != NSEventTypeMouseMoved;
1590 if ([windowsBeingDragged count])
1592 else if (mouseCaptureWindow)
1593 targetWindow = mouseCaptureWindow;
1595 targetWindow = (WineWindow*)[anEvent window];
1598 /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1599 event indicates its window is the main window, even if the cursor is
1600 over a different window. Find the actual WineWindow that is under the
1601 cursor and post the event as being for that window. */
1602 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1603 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1604 NSInteger windowUnderNumber;
1606 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1607 belowWindowWithWindowNumber:0];
1608 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1609 if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1613 if ([targetWindow isKindOfClass:[WineWindow class]])
1615 CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1616 macdrv_event* event;
1619 // If we recently warped the cursor (other than in our cursor-clipping
1620 // event tap), discard mouse move events until we see an event which is
1621 // later than that time.
1622 if (lastSetCursorPositionTime)
1624 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1627 lastSetCursorPositionTime = 0;
1628 forceNextMouseMoveAbsolute = TRUE;
1631 if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1634 forceNextMouseMoveAbsolute = FALSE;
1638 // Send absolute move events if the cursor is in the interior of
1639 // its range. Only send relative moves if the cursor is pinned to
1640 // the boundaries of where it can go. We compute the position
1641 // that's one additional point in the direction of movement. If
1642 // that is outside of the clipping rect or desktop region (the
1643 // union of the screen frames), then we figure the cursor would
1644 // have moved outside if it could but it was pinned.
1645 CGPoint computedPoint = point;
1646 CGFloat deltaX = [anEvent deltaX];
1647 CGFloat deltaY = [anEvent deltaY];
1651 else if (deltaX < -0.001)
1656 else if (deltaY < -0.001)
1659 // Assume cursor is pinned for now
1661 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1663 const CGRect* rects;
1664 NSUInteger count, i;
1666 // Caches screenFrameCGRects if necessary
1667 [self primaryScreenHeight];
1669 rects = [screenFrameCGRects bytes];
1670 count = [screenFrameCGRects length] / sizeof(rects[0]);
1672 for (i = 0; i < count; i++)
1674 if (CGRectContainsPoint(rects[i], computedPoint))
1686 [self clipCursorLocation:&point];
1687 point = cgpoint_win_from_mac(point);
1689 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1690 event->mouse_moved.x = floor(point.x);
1691 event->mouse_moved.y = floor(point.y);
1693 mouseMoveDeltaX = 0;
1694 mouseMoveDeltaY = 0;
1698 double scale = retina_on ? 2 : 1;
1700 /* Add event delta to accumulated delta error */
1701 /* deltaY is already flipped */
1702 mouseMoveDeltaX += [anEvent deltaX];
1703 mouseMoveDeltaY += [anEvent deltaY];
1705 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1706 event->mouse_moved.x = mouseMoveDeltaX * scale;
1707 event->mouse_moved.y = mouseMoveDeltaY * scale;
1709 /* Keep the remainder after integer truncation. */
1710 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1711 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1714 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1716 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1717 event->mouse_moved.drag = drag;
1719 [targetWindow.queue postEvent:event];
1722 macdrv_release_event(event);
1724 lastTargetWindow = targetWindow;
1727 lastTargetWindow = nil;
1729 [self updateCursor:FALSE];
1732 - (void) handleMouseButton:(NSEvent*)theEvent
1734 WineWindow* window = (WineWindow*)[theEvent window];
1735 NSEventType type = [theEvent type];
1736 WineWindow* windowBroughtForward = nil;
1737 BOOL process = FALSE;
1739 if (type == NSEventTypeLeftMouseUp && [windowsBeingDragged count])
1740 [self handleWindowDrag:theEvent begin:NO];
1742 if ([window isKindOfClass:[WineWindow class]] &&
1743 type == NSEventTypeLeftMouseDown &&
1744 ![theEvent wine_commandKeyDown])
1746 NSWindowButton windowButton;
1748 windowBroughtForward = window;
1750 /* Any left-click on our window anyplace other than the close or
1751 minimize buttons will bring it forward. */
1752 for (windowButton = NSWindowCloseButton;
1753 windowButton <= NSWindowMiniaturizeButton;
1756 NSButton* button = [window standardWindowButton:windowButton];
1759 NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1760 if ([button mouse:point inRect:[button bounds]])
1762 windowBroughtForward = nil;
1769 if ([windowsBeingDragged count])
1771 else if (mouseCaptureWindow)
1772 window = mouseCaptureWindow;
1774 if ([window isKindOfClass:[WineWindow class]])
1776 BOOL pressed = (type == NSEventTypeLeftMouseDown ||
1777 type == NSEventTypeRightMouseDown ||
1778 type == NSEventTypeOtherMouseDown);
1779 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1782 [self clipCursorLocation:&pt];
1786 if (mouseCaptureWindow)
1790 // Test if the click was in the window's content area.
1791 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1792 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1793 process = NSMouseInRect(nspoint, contentRect, NO);
1794 if (process && [window styleMask] & NSWindowStyleMaskResizable)
1796 // Ignore clicks in the grow box (resize widget).
1797 HIPoint origin = { 0, 0 };
1798 HIThemeGrowBoxDrawInfo info = { 0 };
1802 info.kind = kHIThemeGrowBoxKindNormal;
1803 info.direction = kThemeGrowRight | kThemeGrowDown;
1804 if ([window styleMask] & NSWindowStyleMaskUtilityWindow)
1805 info.size = kHIThemeGrowBoxSizeSmall;
1807 info.size = kHIThemeGrowBoxSizeNormal;
1809 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1810 if (status == noErr)
1812 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1813 NSMinY(contentRect),
1815 bounds.size.height);
1816 process = !NSMouseInRect(nspoint, growBox, NO);
1821 unmatchedMouseDowns |= NSEventMaskFromType(type);
1825 NSEventType downType = type - 1;
1826 NSUInteger downMask = NSEventMaskFromType(downType);
1827 process = (unmatchedMouseDowns & downMask) != 0;
1828 unmatchedMouseDowns &= ~downMask;
1833 macdrv_event* event;
1835 pt = cgpoint_win_from_mac(pt);
1837 event = macdrv_create_event(MOUSE_BUTTON, window);
1838 event->mouse_button.button = [theEvent buttonNumber];
1839 event->mouse_button.pressed = pressed;
1840 event->mouse_button.x = floor(pt.x);
1841 event->mouse_button.y = floor(pt.y);
1842 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1844 [window.queue postEvent:event];
1846 macdrv_release_event(event);
1850 if (windowBroughtForward)
1852 WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1853 NSInteger ancestorNumber = [ancestor windowNumber];
1854 NSInteger ancestorLevel = [ancestor level];
1856 for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1858 NSInteger windowNumber = [windowNumberObject integerValue];
1859 if (windowNumber == ancestorNumber)
1861 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1862 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1863 [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1865 [ancestor postBroughtForwardEvent];
1869 if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate)
1870 [self windowGotFocus:windowBroughtForward];
1873 // Since mouse button events deliver absolute cursor position, the
1874 // accumulating delta from move events is invalidated. Make sure
1875 // next mouse move event starts over from an absolute baseline.
1876 // Also, it's at least possible that the title bar widgets (e.g. close
1877 // button, etc.) could enter an internal event loop on a mouse down that
1878 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1879 // dragged events and, after that, any notion of the cursor position
1880 // computed from accumulating deltas would be wrong.
1881 forceNextMouseMoveAbsolute = TRUE;
1884 - (void) handleScrollWheel:(NSEvent*)theEvent
1888 if (mouseCaptureWindow)
1889 window = mouseCaptureWindow;
1891 window = (WineWindow*)[theEvent window];
1893 if ([window isKindOfClass:[WineWindow class]])
1895 CGEventRef cgevent = [theEvent CGEvent];
1896 CGPoint pt = CGEventGetLocation(cgevent);
1900 [self clipCursorLocation:&pt];
1902 if (mouseCaptureWindow)
1906 // Only process the event if it was in the window's content area.
1907 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1908 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1909 process = NSMouseInRect(nspoint, contentRect, NO);
1914 macdrv_event* event;
1916 BOOL continuous = FALSE;
1918 pt = cgpoint_win_from_mac(pt);
1920 event = macdrv_create_event(MOUSE_SCROLL, window);
1921 event->mouse_scroll.x = floor(pt.x);
1922 event->mouse_scroll.y = floor(pt.y);
1923 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1925 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1929 /* Continuous scroll wheel events come from high-precision scrolling
1930 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1931 For these, we can get more precise data from the CGEvent API. */
1932 /* Axis 1 is vertical, axis 2 is horizontal. */
1933 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1934 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1938 double pixelsPerLine = 10;
1939 CGEventSourceRef source;
1941 /* The non-continuous values are in units of "lines", not pixels. */
1942 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1944 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1948 x = pixelsPerLine * [theEvent deltaX];
1949 y = pixelsPerLine * [theEvent deltaY];
1952 /* Mac: negative is right or down, positive is left or up.
1953 Win32: negative is left or down, positive is right or up.
1954 So, negate the X scroll value to translate. */
1957 /* The x,y values so far are in pixels. Win32 expects to receive some
1958 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1959 6 times the pixel value. */
1963 if (use_precise_scrolling)
1965 event->mouse_scroll.x_scroll = x;
1966 event->mouse_scroll.y_scroll = y;
1970 /* For non-continuous "clicky" wheels, if there was any motion, make
1971 sure there was at least WHEEL_DELTA motion. This is so, at slow
1972 speeds where the system's acceleration curve is actually reducing the
1973 scroll distance, the user is sure to get some action out of each click.
1974 For example, this is important for rotating though weapons in a
1975 first-person shooter. */
1976 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1977 event->mouse_scroll.x_scroll = 120;
1978 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1979 event->mouse_scroll.x_scroll = -120;
1981 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1982 event->mouse_scroll.y_scroll = 120;
1983 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1984 event->mouse_scroll.y_scroll = -120;
1989 /* If it's been a while since the last scroll event or if the scrolling has
1990 reversed direction, reset the accumulated scroll value. */
1991 if ([theEvent timestamp] - lastScrollTime > 1)
1992 accumScrollX = accumScrollY = 0;
1995 /* The accumulated scroll value is in the opposite direction/sign of the last
1996 scroll. That's because it's the "debt" resulting from over-scrolling in
1997 that direction. We accumulate by adding in the scroll amount and then, if
1998 it has the same sign as the scroll value, we subtract any whole or partial
1999 WHEEL_DELTAs, leaving it 0 or the opposite sign. So, the user switched
2000 scroll direction if the accumulated debt and the new scroll value have the
2002 if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
2004 if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
2007 lastScrollTime = [theEvent timestamp];
2012 if (accumScrollX > 0 && x > 0)
2013 event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
2014 if (accumScrollX < 0 && x < 0)
2015 event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
2016 if (accumScrollY > 0 && y > 0)
2017 event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
2018 if (accumScrollY < 0 && y < 0)
2019 event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
2021 accumScrollX -= event->mouse_scroll.x_scroll;
2022 accumScrollY -= event->mouse_scroll.y_scroll;
2025 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
2026 [window.queue postEvent:event];
2028 macdrv_release_event(event);
2030 // Since scroll wheel events deliver absolute cursor position, the
2031 // accumulating delta from move events is invalidated. Make sure next
2032 // mouse move event starts over from an absolute baseline.
2033 forceNextMouseMoveAbsolute = TRUE;
2038 // Returns TRUE if the event was handled and caller should do nothing more
2039 // with it. Returns FALSE if the caller should process it as normal and
2040 // then call -didSendEvent:.
2041 - (BOOL) handleEvent:(NSEvent*)anEvent
2044 NSEventType type = [anEvent type];
2046 if (type == NSEventTypeFlagsChanged)
2047 self.lastFlagsChanged = anEvent;
2048 else if (type == NSEventTypeMouseMoved || type == NSEventTypeLeftMouseDragged ||
2049 type == NSEventTypeRightMouseDragged || type == NSEventTypeOtherMouseDragged)
2051 [self handleMouseMove:anEvent];
2052 ret = mouseCaptureWindow && ![windowsBeingDragged count];
2054 else if (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp ||
2055 type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp ||
2056 type == NSEventTypeOtherMouseDown || type == NSEventTypeOtherMouseUp)
2058 [self handleMouseButton:anEvent];
2059 ret = mouseCaptureWindow && ![windowsBeingDragged count];
2061 else if (type == NSEventTypeScrollWheel)
2063 [self handleScrollWheel:anEvent];
2064 ret = mouseCaptureWindow != nil;
2066 else if (type == NSEventTypeKeyDown)
2068 // -[NSApplication sendEvent:] seems to consume presses of the Help
2069 // key (Insert key on PC keyboards), so we have to bypass it and
2070 // send the event directly to the window.
2071 if (anEvent.keyCode == kVK_Help)
2073 [anEvent.window sendEvent:anEvent];
2077 else if (type == NSEventTypeKeyUp)
2079 uint16_t keyCode = [anEvent keyCode];
2080 if ([self isKeyPressed:keyCode])
2082 WineWindow* window = (WineWindow*)[anEvent window];
2083 [self noteKey:keyCode pressed:FALSE];
2084 if ([window isKindOfClass:[WineWindow class]])
2085 [window postKeyEvent:anEvent];
2088 else if (type == NSEventTypeAppKitDefined)
2090 short subtype = [anEvent subtype];
2092 // These subtypes are not documented but they appear to mean
2093 // "a window is being dragged" and "a window is no longer being
2094 // dragged", respectively.
2095 if (subtype == 20 || subtype == 21)
2096 [self handleWindowDrag:anEvent begin:(subtype == 20)];
2102 - (void) didSendEvent:(NSEvent*)anEvent
2104 NSEventType type = [anEvent type];
2106 if (type == NSEventTypeKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
2108 NSUInteger modifiers = [anEvent modifierFlags];
2109 if ((modifiers & NSEventModifierFlagCommand) &&
2110 !(modifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)))
2112 // Command-Tab and Command-Shift-Tab would normally be intercepted
2113 // by the system to switch applications. If we're seeing it, it's
2114 // presumably because we've captured the displays, preventing
2115 // normal application switching. Do it manually.
2116 [self handleCommandTab];
2121 - (void) setupObservations
2123 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2124 NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
2125 NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
2127 [nc addObserverForName:NSWindowDidBecomeKeyNotification
2130 usingBlock:^(NSNotification *note){
2131 NSWindow* window = [note object];
2132 [keyWindows removeObjectIdenticalTo:window];
2133 [keyWindows insertObject:window atIndex:0];
2136 [nc addObserverForName:NSWindowWillCloseNotification
2138 queue:[NSOperationQueue mainQueue]
2139 usingBlock:^(NSNotification *note){
2140 NSWindow* window = [note object];
2141 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
2143 [keyWindows removeObjectIdenticalTo:window];
2144 if (window == lastTargetWindow)
2145 lastTargetWindow = nil;
2146 if (window == self.mouseCaptureWindow)
2147 self.mouseCaptureWindow = nil;
2148 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
2150 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
2151 [self updateFullscreenWindows];
2154 [windowsBeingDragged removeObject:window];
2155 [self updateCursorClippingState];
2158 [nc addObserver:self
2159 selector:@selector(keyboardSelectionDidChange)
2160 name:NSTextInputContextKeyboardSelectionDidChangeNotification
2163 /* The above notification isn't sent unless the NSTextInputContext
2164 class has initialized itself. Poke it. */
2165 [NSTextInputContext self];
2167 [wsnc addObserver:self
2168 selector:@selector(activeSpaceDidChange)
2169 name:NSWorkspaceActiveSpaceDidChangeNotification
2172 [nc addObserver:self
2173 selector:@selector(releaseMouseCapture)
2174 name:NSMenuDidBeginTrackingNotification
2177 [dnc addObserver:self
2178 selector:@selector(releaseMouseCapture)
2179 name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2181 suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2183 [dnc addObserver:self
2184 selector:@selector(enabledKeyboardInputSourcesChanged)
2185 name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2189 - (BOOL) inputSourceIsInputMethod
2191 if (!inputSourceIsInputMethodValid)
2193 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2196 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2197 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2198 CFRelease(inputSource);
2201 inputSourceIsInputMethod = FALSE;
2202 inputSourceIsInputMethodValid = TRUE;
2205 return inputSourceIsInputMethod;
2208 - (void) releaseMouseCapture
2210 // This might be invoked on a background thread by the distributed
2211 // notification center. Shunt it to the main thread.
2212 if (![NSThread isMainThread])
2214 dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2218 if (mouseCaptureWindow)
2220 macdrv_event* event;
2222 event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2223 [mouseCaptureWindow.queue postEvent:event];
2224 macdrv_release_event(event);
2228 - (void) unminimizeWindowIfNoneVisible
2230 if (![self frontWineWindow])
2232 for (WineWindow* window in [NSApp windows])
2234 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2236 [window deminiaturize:self];
2243 - (void) setRetinaMode:(int)mode
2249 double scale = mode ? 0.5 : 2.0;
2250 cursorClipRect.origin.x *= scale;
2251 cursorClipRect.origin.y *= scale;
2252 cursorClipRect.size.width *= scale;
2253 cursorClipRect.size.height *= scale;
2256 for (WineWindow* window in [NSApp windows])
2258 if ([window isKindOfClass:[WineWindow class]])
2259 [window setRetinaMode:mode];
2265 * ---------- NSApplicationDelegate methods ----------
2267 - (void)applicationDidBecomeActive:(NSNotification *)notification
2269 NSNumber* displayID;
2270 NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2272 latentDisplayModes = [[NSMutableDictionary alloc] init];
2273 for (displayID in modesToRealize)
2275 CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2276 [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2279 [self updateCursorClippingState];
2281 [self updateFullscreenWindows];
2282 [self adjustWindowLevels:YES];
2285 [self unminimizeWindowIfNoneVisible];
2288 // If a Wine process terminates abruptly while it has the display captured
2289 // and switched to a different resolution, Mac OS X will uncapture the
2290 // displays and switch their resolutions back. However, the other Wine
2291 // processes won't have their notion of the desktop rect changed back.
2292 // This can lead them to refuse to draw or acknowledge clicks in certain
2293 // portions of their windows.
2295 // To solve this, we synthesize a displays-changed event whenever we're
2296 // activated. This will provoke a re-synchronization of Wine's notion of
2297 // the desktop rect with the actual state.
2298 [self sendDisplaysChanged:TRUE];
2300 // The cursor probably moved while we were inactive. Accumulated mouse
2301 // movement deltas are invalidated. Make sure the next mouse move event
2302 // starts over from an absolute baseline.
2303 forceNextMouseMoveAbsolute = TRUE;
2306 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2308 primaryScreenHeightValid = FALSE;
2309 [self sendDisplaysChanged:FALSE];
2310 [self adjustWindowLevels];
2312 // When the display configuration changes, the cursor position may jump.
2313 // Accumulated mouse movement deltas are invalidated. Make sure the next
2314 // mouse move event starts over from an absolute baseline.
2315 forceNextMouseMoveAbsolute = TRUE;
2318 - (void)applicationDidResignActive:(NSNotification *)notification
2320 macdrv_event* event;
2321 WineEventQueue* queue;
2323 [self updateCursorClippingState];
2325 [self invalidateGotFocusEvents];
2327 event = macdrv_create_event(APP_DEACTIVATED, nil);
2329 [eventQueuesLock lock];
2330 for (queue in eventQueues)
2331 [queue postEvent:event];
2332 [eventQueuesLock unlock];
2334 macdrv_release_event(event);
2336 [self releaseMouseCapture];
2339 - (void) applicationDidUnhide:(NSNotification*)aNotification
2341 [self adjustWindowLevels];
2344 - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2346 // Note that "flag" is often wrong. WineWindows are NSPanels and NSPanels
2347 // don't count as "visible windows" for this purpose.
2348 [self unminimizeWindowIfNoneVisible];
2352 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2354 NSApplicationTerminateReply ret = NSTerminateNow;
2355 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2356 NSAppleEventDescriptor* desc = [m currentAppleEvent];
2357 macdrv_event* event;
2358 WineEventQueue* queue;
2360 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2362 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2365 case kAEReallyLogOut:
2366 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2368 case kAEShowRestartDialog:
2369 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2371 case kAEShowShutdownDialog:
2372 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2375 event->app_quit_requested.reason = QUIT_REASON_NONE;
2379 [eventQueuesLock lock];
2381 if ([eventQueues count])
2383 for (queue in eventQueues)
2384 [queue postEvent:event];
2385 ret = NSTerminateLater;
2388 [eventQueuesLock unlock];
2390 macdrv_release_event(event);
2395 - (void)applicationWillBecomeActive:(NSNotification *)notification
2397 macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2400 [eventQueuesLock lock];
2401 for (WineEventQueue* queue in eventQueues)
2402 [queue postEvent:event];
2403 [eventQueuesLock unlock];
2405 macdrv_release_event(event);
2408 - (void)applicationWillResignActive:(NSNotification *)notification
2410 [self adjustWindowLevels:NO];
2413 /***********************************************************************
2416 * Run-loop-source perform callback. Pull request blocks from the
2417 * array of queued requests and invoke them.
2419 static void PerformRequest(void *info)
2421 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2422 WineApplicationController* controller = [WineApplicationController sharedController];
2426 __block dispatch_block_t block;
2428 dispatch_sync(controller->requestsManipQueue, ^{
2429 if ([controller->requests count])
2431 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2432 [controller->requests removeObjectAtIndex:0];
2445 pool = [[NSAutoreleasePool alloc] init];
2451 /***********************************************************************
2454 * Run a block on the main thread asynchronously.
2456 void OnMainThreadAsync(dispatch_block_t block)
2458 WineApplicationController* controller = [WineApplicationController sharedController];
2460 block = [block copy];
2461 dispatch_sync(controller->requestsManipQueue, ^{
2462 [controller->requests addObject:block];
2465 CFRunLoopSourceSignal(controller->requestSource);
2466 CFRunLoopWakeUp(CFRunLoopGetMain());
2471 /***********************************************************************
2474 void LogError(const char* func, NSString* format, ...)
2477 va_start(args, format);
2478 LogErrorv(func, format, args);
2482 /***********************************************************************
2485 void LogErrorv(const char* func, NSString* format, va_list args)
2487 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2489 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2490 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2496 /***********************************************************************
2497 * macdrv_window_rejected_focus
2499 * Pass focus to the next window that hasn't already rejected this same
2500 * WINDOW_GOT_FOCUS event.
2502 void macdrv_window_rejected_focus(const macdrv_event *event)
2505 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2509 /***********************************************************************
2510 * macdrv_get_input_source_info
2512 * Returns the keyboard layout uchr data, keyboard type and input source.
2514 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2517 TISInputSourceRef inputSourceLayout;
2519 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2520 if (inputSourceLayout)
2522 CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2523 kTISPropertyUnicodeKeyLayoutData);
2524 *uchr = CFDataCreateCopy(NULL, data);
2525 CFRelease(inputSourceLayout);
2527 *keyboard_type = [WineApplicationController sharedController].keyboardType;
2528 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2530 *input_source = TISCopyCurrentKeyboardInputSource();
2535 /***********************************************************************
2538 * Play the beep sound configured by the user in System Preferences.
2540 void macdrv_beep(void)
2542 OnMainThreadAsync(^{
2547 /***********************************************************************
2548 * macdrv_set_display_mode
2550 int macdrv_set_display_mode(const struct macdrv_display* display,
2551 CGDisplayModeRef display_mode)
2556 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2562 /***********************************************************************
2567 * If name is non-NULL, it is a selector for a class method on NSCursor
2568 * identifying the cursor to set. In that case, frames is ignored. If
2569 * name is NULL, then frames is used.
2571 * frames is an array of dictionaries. Each dictionary is a frame of
2572 * an animated cursor. Under the key "image" is a CGImage for the
2573 * frame. Under the key "duration" is a CFNumber time interval, in
2574 * seconds, for how long that frame is presented before proceeding to
2575 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
2576 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2577 * This is the hot spot, measured in pixels down and to the right of the
2578 * top-left corner of the image.
2580 * If the array has exactly 1 element, the cursor is static, not
2581 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
2583 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2587 sel = NSSelectorFromString((NSString*)name);
2590 OnMainThreadAsync(^{
2591 WineApplicationController* controller = [WineApplicationController sharedController];
2592 [controller setCursorWithFrames:nil];
2593 controller.cursor = [NSCursor performSelector:sel];
2594 [controller unhideCursor];
2599 NSArray* nsframes = (NSArray*)frames;
2600 if ([nsframes count])
2602 OnMainThreadAsync(^{
2603 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2608 OnMainThreadAsync(^{
2609 WineApplicationController* controller = [WineApplicationController sharedController];
2610 [controller setCursorWithFrames:nil];
2611 [controller hideCursor];
2617 /***********************************************************************
2618 * macdrv_get_cursor_position
2620 * Obtains the current cursor position. Returns zero on failure,
2621 * non-zero on success.
2623 int macdrv_get_cursor_position(CGPoint *pos)
2626 NSPoint location = [NSEvent mouseLocation];
2627 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2628 *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2634 /***********************************************************************
2635 * macdrv_set_cursor_position
2637 * Sets the cursor position without generating events. Returns zero on
2638 * failure, non-zero on success.
2640 int macdrv_set_cursor_position(CGPoint pos)
2645 ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2651 /***********************************************************************
2652 * macdrv_clip_cursor
2654 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
2655 * to or larger than the whole desktop region, the cursor is unclipped.
2656 * Returns zero on failure, non-zero on success.
2658 int macdrv_clip_cursor(CGRect r)
2663 WineApplicationController* controller = [WineApplicationController sharedController];
2664 BOOL clipping = FALSE;
2667 if (!CGRectIsInfinite(rect))
2668 rect = cgrect_mac_from_win(rect);
2670 if (!CGRectIsInfinite(rect))
2672 NSRect nsrect = NSRectFromCGRect(rect);
2675 /* Convert the rectangle from top-down coords to bottom-up. */
2676 [controller flipRect:&nsrect];
2679 for (screen in [NSScreen screens])
2681 if (!NSContainsRect(nsrect, [screen frame]))
2690 ret = [controller startClippingCursor:rect];
2692 ret = [controller stopClippingCursor];
2698 /***********************************************************************
2699 * macdrv_set_application_icon
2701 * Set the application icon. The images array contains CGImages. If
2702 * there are more than one, then they represent different sizes or
2703 * color depths from the icon resource. If images is NULL or empty,
2704 * restores the default application image.
2706 void macdrv_set_application_icon(CFArrayRef images)
2708 NSArray* imageArray = (NSArray*)images;
2710 OnMainThreadAsync(^{
2711 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2715 /***********************************************************************
2718 void macdrv_quit_reply(int reply)
2721 [NSApp replyToApplicationShouldTerminate:reply];
2725 /***********************************************************************
2726 * macdrv_using_input_method
2728 int macdrv_using_input_method(void)
2733 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2739 /***********************************************************************
2740 * macdrv_set_mouse_capture_window
2742 void macdrv_set_mouse_capture_window(macdrv_window window)
2744 WineWindow* w = (WineWindow*)window;
2746 [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2749 [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2753 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2754 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2755 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2757 /***********************************************************************
2758 * macdrv_create_input_source_list
2760 CFArrayRef macdrv_create_input_source_list(void)
2762 CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2765 CFArrayRef input_list;
2766 CFDictionaryRef filter_dict;
2767 const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2768 const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2771 filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2772 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2773 input_list = TISCreateInputSourceList(filter_dict, false);
2775 for (i = 0; i < CFArrayGetCount(input_list); i++)
2777 TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2778 CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2779 CFDictionaryRef entry;
2780 const void *input_keys[3] = { macdrv_input_source_input_key,
2781 macdrv_input_source_type_key,
2782 macdrv_input_source_lang_key };
2783 const void *input_values[3];
2785 input_values[0] = input;
2786 input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2787 input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2789 entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2790 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2792 CFArrayAppendValue(ret, entry);
2795 CFRelease(input_list);
2796 CFRelease(filter_dict);
2802 int macdrv_select_input_source(TISInputSourceRef input_source)
2804 __block int ret = FALSE;
2807 ret = (TISSelectInputSource(input_source) == noErr);
2813 void macdrv_set_cocoa_retina_mode(int new_mode)
2816 [[WineApplicationController sharedController] setRetinaMode:new_mode];