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>
25 #import "cocoa_event.h"
26 #import "cocoa_window.h"
29 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
35 #if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
36 @interface NSWindow (WineAutoTabbingExtensions)
38 + (void) setAllowsAutomaticWindowTabbing:(BOOL)allows;
44 /***********************************************************************
47 * Look up a localized string by its ID in the dictionary.
49 static NSString* WineLocalizedString(unsigned int stringID)
51 NSNumber* key = [NSNumber numberWithUnsignedInt:stringID];
52 return [(NSDictionary*)localized_strings objectForKey:key];
56 @implementation WineApplication
58 @synthesize wineController;
60 - (void) sendEvent:(NSEvent*)anEvent
62 if (![wineController handleEvent:anEvent])
64 [super sendEvent:anEvent];
65 [wineController didSendEvent:anEvent];
69 - (void) setWineController:(WineApplicationController*)newController
71 wineController = newController;
72 [self setDelegate:wineController];
78 @interface WarpRecord : NSObject
80 CGEventTimestamp timeBefore, timeAfter;
84 @property (nonatomic) CGEventTimestamp timeBefore;
85 @property (nonatomic) CGEventTimestamp timeAfter;
86 @property (nonatomic) CGPoint from;
87 @property (nonatomic) CGPoint to;
92 @implementation WarpRecord
94 @synthesize timeBefore, timeAfter, from, to;
99 @interface WineApplicationController ()
101 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
102 @property (copy, nonatomic) NSArray* cursorFrames;
103 @property (retain, nonatomic) NSTimer* cursorTimer;
104 @property (retain, nonatomic) NSCursor* cursor;
105 @property (retain, nonatomic) NSImage* applicationIcon;
106 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
107 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
109 - (void) setupObservations;
110 - (void) applicationDidBecomeActive:(NSNotification *)notification;
112 static void PerformRequest(void *info);
117 @implementation WineApplicationController
119 @synthesize keyboardType, lastFlagsChanged;
120 @synthesize applicationIcon;
121 @synthesize cursorFrames, cursorTimer, cursor;
122 @synthesize mouseCaptureWindow;
124 @synthesize clippingCursor;
128 if (self == [WineApplicationController class])
130 NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
131 @"", @"NSQuotedKeystrokeBinding",
132 @"", @"NSRepeatCountBinding",
133 [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
135 [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
137 if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
138 [NSWindow setAllowsAutomaticWindowTabbing:NO];
142 + (WineApplicationController*) sharedController
144 static WineApplicationController* sharedController;
145 static dispatch_once_t once;
147 dispatch_once(&once, ^{
148 sharedController = [[self alloc] init];
151 return sharedController;
159 CFRunLoopSourceContext context = { 0 };
160 context.perform = PerformRequest;
161 requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
167 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
168 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
170 requests = [[NSMutableArray alloc] init];
171 requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
173 eventQueues = [[NSMutableArray alloc] init];
174 eventQueuesLock = [[NSLock alloc] init];
176 keyWindows = [[NSMutableArray alloc] init];
178 originalDisplayModes = [[NSMutableDictionary alloc] init];
179 latentDisplayModes = [[NSMutableDictionary alloc] init];
181 warpRecords = [[NSMutableArray alloc] init];
183 windowsBeingDragged = [[NSMutableSet alloc] init];
185 if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
186 !keyWindows || !originalDisplayModes || !latentDisplayModes || !warpRecords)
192 [self setupObservations];
194 keyboardType = LMGetKbdType();
196 if ([NSApp isActive])
197 [self applicationDidBecomeActive:nil];
204 [windowsBeingDragged release];
206 [screenFrameCGRects release];
207 [applicationIcon release];
208 [warpRecords release];
209 [cursorTimer release];
210 [cursorFrames release];
211 [latentDisplayModes release];
212 [originalDisplayModes release];
213 [keyWindows release];
214 [eventQueues release];
215 [eventQueuesLock release];
216 if (requestsManipQueue) dispatch_release(requestsManipQueue);
220 CFRunLoopSourceInvalidate(requestSource);
221 CFRelease(requestSource);
226 - (void) transformProcessToForeground
228 if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
232 NSString* bundleName;
236 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
237 [NSApp activateIgnoringOtherApps:YES];
238 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
239 if (!enable_app_nap && [NSProcessInfo instancesRespondToSelector:@selector(beginActivityWithOptions:reason:)])
241 [[[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep
242 reason:@"Running Windows program"] retain]; // intentional leak
246 mainMenu = [[[NSMenu alloc] init] autorelease];
249 submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
250 bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
252 if ([bundleName length])
253 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
255 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
256 item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
258 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
259 action:@selector(hideOtherApplications:)
261 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
263 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
264 action:@selector(unhideAllApplications:)
267 [submenu addItem:[NSMenuItem separatorItem]];
269 if ([bundleName length])
270 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
272 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
273 item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
274 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
275 item = [[[NSMenuItem alloc] init] autorelease];
276 [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
277 [item setSubmenu:submenu];
278 [mainMenu addItem:item];
281 submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
282 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
283 action:@selector(performMiniaturize:)
285 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
286 action:@selector(performZoom:)
288 if ([NSWindow instancesRespondToSelector:@selector(toggleFullScreen:)])
290 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
291 action:@selector(toggleFullScreen:)
293 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand |
294 NSEventModifierFlagOption |
295 NSEventModifierFlagControl];
297 [submenu addItem:[NSMenuItem separatorItem]];
298 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
299 action:@selector(arrangeInFront:)
301 item = [[[NSMenuItem alloc] init] autorelease];
302 [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
303 [item setSubmenu:submenu];
304 [mainMenu addItem:item];
306 [NSApp setMainMenu:mainMenu];
307 [NSApp setWindowsMenu:submenu];
309 [NSApp setApplicationIconImage:self.applicationIcon];
313 - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
315 PerformRequest(NULL);
321 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
322 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
324 inMode:NSDefaultRunLoopMode
327 [NSApp sendEvent:event];
331 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
332 } while (!*done && [timeout timeIntervalSinceNow] >= 0);
337 - (BOOL) registerEventQueue:(WineEventQueue*)queue
339 [eventQueuesLock lock];
340 [eventQueues addObject:queue];
341 [eventQueuesLock unlock];
345 - (void) unregisterEventQueue:(WineEventQueue*)queue
347 [eventQueuesLock lock];
348 [eventQueues removeObjectIdenticalTo:queue];
349 [eventQueuesLock unlock];
352 - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
354 eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
357 - (double) ticksForEventTime:(NSTimeInterval)eventTime
359 return (eventTime + eventTimeAdjustment) * 1000;
362 /* Invalidate old focus offers across all queues. */
363 - (void) invalidateGotFocusEvents
365 WineEventQueue* queue;
369 [eventQueuesLock lock];
370 for (queue in eventQueues)
372 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
375 [eventQueuesLock unlock];
378 - (void) windowGotFocus:(WineWindow*)window
382 [self invalidateGotFocusEvents];
384 event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
385 event->window_got_focus.serial = windowFocusSerial;
387 event->window_got_focus.tried_windows = [triedWindows retain];
389 event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
390 [window.queue postEvent:event];
391 macdrv_release_event(event);
394 - (void) windowRejectedFocusEvent:(const macdrv_event*)event
396 if (event->window_got_focus.serial == windowFocusSerial)
398 NSMutableArray* windows = [keyWindows mutableCopy];
399 NSNumber* windowNumber;
402 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
404 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
405 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
406 ![windows containsObject:window])
407 [windows addObject:window];
410 triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
411 [triedWindows addObject:(WineWindow*)event->window];
412 for (window in windows)
414 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
416 [window makeKeyWindow];
425 static BOOL EqualInputSource(TISInputSourceRef source1, TISInputSourceRef source2)
427 if (!source1 && !source2)
429 if (!source1 || !source2)
431 return CFEqual(source1, source2);
434 - (void) keyboardSelectionDidChange:(BOOL)force
436 TISInputSourceRef inputSource, inputSourceLayout;
440 NSTextInputContext* context = [NSTextInputContext currentInputContext];
441 if (!context || ![context client])
445 inputSource = TISCopyCurrentKeyboardInputSource();
446 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
447 if (!force && EqualInputSource(inputSource, lastKeyboardInputSource) &&
448 EqualInputSource(inputSourceLayout, lastKeyboardLayoutInputSource))
450 if (inputSource) CFRelease(inputSource);
451 if (inputSourceLayout) CFRelease(inputSourceLayout);
455 if (lastKeyboardInputSource)
456 CFRelease(lastKeyboardInputSource);
457 lastKeyboardInputSource = inputSource;
458 if (lastKeyboardLayoutInputSource)
459 CFRelease(lastKeyboardLayoutInputSource);
460 lastKeyboardLayoutInputSource = inputSourceLayout;
462 inputSourceIsInputMethodValid = FALSE;
464 if (inputSourceLayout)
467 uchr = TISGetInputSourceProperty(inputSourceLayout,
468 kTISPropertyUnicodeKeyLayoutData);
472 WineEventQueue* queue;
474 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
475 event->keyboard_changed.keyboard_type = self.keyboardType;
476 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
477 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
478 event->keyboard_changed.input_source = (TISInputSourceRef)CFRetain(inputSource);
480 if (event->keyboard_changed.uchr)
482 [eventQueuesLock lock];
484 for (queue in eventQueues)
485 [queue postEvent:event];
487 [eventQueuesLock unlock];
490 macdrv_release_event(event);
495 - (void) keyboardSelectionDidChange
497 [self keyboardSelectionDidChange:NO];
500 - (void) setKeyboardType:(CGEventSourceKeyboardType)newType
502 if (newType != keyboardType)
504 keyboardType = newType;
505 [self keyboardSelectionDidChange:YES];
509 - (void) enabledKeyboardInputSourcesChanged
511 macdrv_layout_list_needs_update = TRUE;
514 - (CGFloat) primaryScreenHeight
516 if (!primaryScreenHeightValid)
518 NSArray* screens = [NSScreen screens];
519 NSUInteger count = [screens count];
526 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
527 primaryScreenHeightValid = TRUE;
529 size = count * sizeof(CGRect);
530 if (!screenFrameCGRects)
531 screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
533 [screenFrameCGRects setLength:size];
535 rect = [screenFrameCGRects mutableBytes];
536 for (screen in screens)
538 CGRect temp = NSRectToCGRect([screen frame]);
539 temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
544 return 1280; /* arbitrary value */
547 return primaryScreenHeight;
550 - (NSPoint) flippedMouseLocation:(NSPoint)point
552 /* This relies on the fact that Cocoa's mouse location points are
553 actually off by one (precisely because they were flipped from
554 Quartz screen coordinates using this same technique). */
555 point.y = [self primaryScreenHeight] - point.y;
559 - (void) flipRect:(NSRect*)rect
561 // We don't use -primaryScreenHeight here so there's no chance of having
562 // out-of-date cached info. This method is called infrequently enough
563 // that getting the screen height each time is not prohibitively expensive.
564 rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
567 - (WineWindow*) frontWineWindow
569 NSNumber* windowNumber;
570 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
572 NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
573 if ([window isKindOfClass:[WineWindow class]] && [window screen])
574 return (WineWindow*)window;
580 - (void) adjustWindowLevels:(BOOL)active
582 NSArray* windowNumbers;
583 NSMutableArray* wineWindows;
584 NSNumber* windowNumber;
585 NSUInteger nextFloatingIndex = 0;
586 __block NSInteger maxLevel = NSIntegerMin;
587 __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
588 __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
589 __block WineWindow* prev = nil;
592 if ([NSApp isHidden]) return;
594 windowNumbers = [NSWindow windowNumbersWithOptions:0];
595 wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
597 // For the most part, we rely on the window server's ordering of the windows
598 // to be authoritative. The one exception is if the "floating" property of
599 // one of the windows has been changed, it may be in the wrong level and thus
600 // in the order. This method is what's supposed to fix that up. So build
601 // a list of Wine windows sorted first by floating-ness and then by order
602 // as indicated by the window server.
603 for (windowNumber in windowNumbers)
605 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
606 if ([window isKindOfClass:[WineWindow class]])
609 [wineWindows insertObject:window atIndex:nextFloatingIndex++];
611 [wineWindows addObject:window];
615 NSDisableScreenUpdates();
617 // Go from back to front so that all windows in front of one which is
618 // elevated for full-screen are also elevated.
619 [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
620 usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
621 WineWindow* window = (WineWindow*)obj;
622 NSInteger origLevel = [window level];
623 NSInteger newLevel = [window minimumLevelForActive:active];
627 if (minFloatingLevel <= maxNonfloatingLevel)
628 minFloatingLevel = maxNonfloatingLevel + 1;
629 if (newLevel < minFloatingLevel)
630 newLevel = minFloatingLevel;
633 if (newLevel < maxLevel)
638 if (!window.floating && maxNonfloatingLevel < newLevel)
639 maxNonfloatingLevel = newLevel;
641 if (newLevel != origLevel)
643 [window setLevel:newLevel];
645 // -setLevel: puts the window at the front of its new level. If
646 // we decreased the level, that's good (it was in front of that
647 // level before, so it should still be now). But if we increased
648 // the level, the window should be toward the back (but still
649 // ahead of the previous windows we did this to).
650 if (origLevel < newLevel)
653 [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
655 [window orderBack:nil];
662 NSEnableScreenUpdates();
664 [wineWindows release];
666 // The above took care of the visible windows on the current space. That
667 // leaves windows on other spaces, minimized windows, and windows which
668 // are not ordered in. We want to leave windows on other spaces alone
669 // so the space remains just as they left it (when viewed in Exposé or
670 // Mission Control, for example). We'll adjust the window levels again
671 // after we switch to another space, anyway. Windows which aren't
672 // ordered in will be handled when we order them in. Minimized windows
673 // on the current space should be set to the level they would have gotten
674 // if they were at the front of the windows with the same floating-ness,
675 // because that's where they'll go if/when they are unminimized. Again,
676 // for good measure we'll adjust window levels again when a window is
678 for (window in [NSApp windows])
680 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
681 [window isOnActiveSpace])
683 NSInteger origLevel = [window level];
684 NSInteger newLevel = [window minimumLevelForActive:YES];
685 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
687 if (newLevel < maxLevelForType)
688 newLevel = maxLevelForType;
690 if (newLevel != origLevel)
691 [window setLevel:newLevel];
696 - (void) adjustWindowLevels
698 [self adjustWindowLevels:[NSApp isActive]];
701 - (void) updateFullscreenWindows
703 if (capture_displays_for_fullscreen && [NSApp isActive])
705 BOOL anyFullscreen = FALSE;
706 NSNumber* windowNumber;
707 for (windowNumber in [NSWindow windowNumbersWithOptions:0])
709 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
710 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
712 anyFullscreen = TRUE;
719 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
720 displaysCapturedForFullscreen = TRUE;
722 else if (displaysCapturedForFullscreen)
724 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
725 displaysCapturedForFullscreen = FALSE;
730 - (void) activeSpaceDidChange
732 [self updateFullscreenWindows];
733 [self adjustWindowLevels];
736 - (void) sendDisplaysChanged:(BOOL)activating
739 WineEventQueue* queue;
741 event = macdrv_create_event(DISPLAYS_CHANGED, nil);
742 event->displays_changed.activating = activating;
744 [eventQueuesLock lock];
746 // If we're activating, then we just need one of our threads to get the
747 // event, so it can send it directly to the desktop window. Otherwise,
748 // we need all of the threads to get it because we don't know which owns
749 // the desktop window and only that one will do anything with it.
750 if (activating) event->deliver = 1;
752 for (queue in eventQueues)
753 [queue postEvent:event];
754 [eventQueuesLock unlock];
756 macdrv_release_event(event);
759 // We can compare two modes directly using CFEqual, but that may require that
760 // they are identical to a level that we don't need. In particular, when the
761 // OS switches between the integrated and discrete GPUs, the set of display
762 // modes can change in subtle ways. We're interested in whether two modes
763 // match in their most salient features, even if they aren't identical.
764 - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
766 NSString *encoding1, *encoding2;
767 uint32_t ioflags1, ioflags2, different;
768 double refresh1, refresh2;
770 if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
771 if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
773 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
774 if (&CGDisplayModeGetPixelWidth != NULL &&
775 CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
776 if (&CGDisplayModeGetPixelHeight != NULL &&
777 CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
780 encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
781 encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
782 if (![encoding1 isEqualToString:encoding2]) return FALSE;
784 ioflags1 = CGDisplayModeGetIOFlags(mode1);
785 ioflags2 = CGDisplayModeGetIOFlags(mode2);
786 different = ioflags1 ^ ioflags2;
787 if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
788 kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
791 refresh1 = CGDisplayModeGetRefreshRate(mode1);
792 if (refresh1 == 0) refresh1 = 60;
793 refresh2 = CGDisplayModeGetRefreshRate(mode2);
794 if (refresh2 == 0) refresh2 = 60;
795 if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
800 - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
802 NSMutableArray* ret = [NSMutableArray array];
803 NSDictionary* options = nil;
805 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
806 options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:TRUE]
807 forKey:(NSString*)kCGDisplayShowDuplicateLowResolutionModes];
810 NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
811 for (id candidateModeObject in modes)
813 CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
814 if ([self mode:candidateMode matchesMode:mode])
815 [ret addObject:candidateModeObject];
820 - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
823 NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
824 CGDisplayModeRef originalMode;
826 originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
828 if (originalMode && [self mode:mode matchesMode:originalMode])
830 if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
832 CGRestorePermanentDisplayConfiguration();
833 if (!displaysCapturedForFullscreen)
834 CGReleaseAllDisplays();
835 [originalDisplayModes removeAllObjects];
838 else // ... otherwise, try to restore just the one display
840 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
842 mode = (CGDisplayModeRef)modeObject;
843 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
845 [originalDisplayModes removeObjectForKey:displayIDKey];
854 CGDisplayModeRef currentMode;
857 currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
859 currentMode = CGDisplayCopyDisplayMode(displayID);
860 if (!currentMode) // Invalid display ID
863 if ([self mode:mode matchesMode:currentMode]) // Already there!
865 CGDisplayModeRelease(currentMode);
869 CGDisplayModeRelease(currentMode);
872 modes = [self modesMatchingMode:mode forDisplay:displayID];
876 [self transformProcessToForeground];
878 BOOL active = [NSApp isActive];
880 if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
881 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
885 // If we get here, we have the displays captured. If we don't
886 // know the original mode of the display, the current mode must
887 // be the original. We should re-query the current mode since
888 // another process could have changed it between when we last
889 // checked and when we captured the displays.
891 originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
895 for (id modeObject in modes)
897 mode = (CGDisplayModeRef)modeObject;
898 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
905 if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
906 [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
907 else if (![originalDisplayModes count])
909 CGRestorePermanentDisplayConfiguration();
910 if (!displaysCapturedForFullscreen)
911 CGReleaseAllDisplays();
915 CGDisplayModeRelease(currentMode);
919 [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
926 [self adjustWindowLevels];
931 - (BOOL) areDisplaysCaptured
933 return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
936 - (void) updateCursor:(BOOL)force
938 if (force || lastTargetWindow)
940 if (clientWantsCursorHidden && !cursorHidden)
946 if (!cursorIsCurrent)
949 cursorIsCurrent = TRUE;
952 if (!clientWantsCursorHidden && cursorHidden)
955 cursorHidden = FALSE;
962 [[NSCursor arrowCursor] set];
963 cursorIsCurrent = FALSE;
968 cursorHidden = FALSE;
975 if (!clientWantsCursorHidden)
977 clientWantsCursorHidden = TRUE;
978 [self updateCursor:TRUE];
982 - (void) unhideCursor
984 if (clientWantsCursorHidden)
986 clientWantsCursorHidden = FALSE;
987 [self updateCursor:FALSE];
991 - (void) setCursor:(NSCursor*)newCursor
993 if (newCursor != cursor)
996 cursor = [newCursor retain];
997 cursorIsCurrent = FALSE;
998 [self updateCursor:FALSE];
1004 NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
1005 CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
1006 CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
1007 NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
1008 CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
1011 if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
1012 hotSpot = CGPointZero;
1013 hotSpot = cgpoint_mac_from_win(hotSpot);
1014 self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1016 [self unhideCursor];
1019 - (void) nextCursorFrame:(NSTimer*)theTimer
1021 NSDictionary* frame;
1022 NSTimeInterval duration;
1026 if (cursorFrame >= [cursorFrames count])
1030 frame = [cursorFrames objectAtIndex:cursorFrame];
1031 duration = [[frame objectForKey:@"duration"] doubleValue];
1032 date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1033 [cursorTimer setFireDate:date];
1036 - (void) setCursorWithFrames:(NSArray*)frames
1038 if (self.cursorFrames == frames)
1041 self.cursorFrames = frames;
1043 [cursorTimer invalidate];
1044 self.cursorTimer = nil;
1048 if ([frames count] > 1)
1050 NSDictionary* frame = [frames objectAtIndex:0];
1051 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
1052 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1053 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1056 selector:@selector(nextCursorFrame:)
1058 repeats:YES] autorelease];
1059 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1066 - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1068 NSImage* nsimage = nil;
1072 NSSize bestSize = NSZeroSize;
1075 nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1077 for (image in images)
1079 CGImageRef cgimage = (CGImageRef)image;
1080 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1083 NSSize size = [imageRep size];
1085 [nsimage addRepresentation:imageRep];
1088 if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1093 if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1094 [nsimage setSize:bestSize];
1099 self.applicationIcon = nsimage;
1102 - (void) handleCommandTab
1104 if ([NSApp isActive])
1106 NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1107 NSRunningApplication* app;
1108 NSRunningApplication* otherValidApp = nil;
1110 if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1112 NSNumber* displayID;
1113 for (displayID in originalDisplayModes)
1115 CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1116 [latentDisplayModes setObject:(id)mode forKey:displayID];
1117 CGDisplayModeRelease(mode);
1120 CGRestorePermanentDisplayConfiguration();
1121 CGReleaseAllDisplays();
1122 [originalDisplayModes removeAllObjects];
1123 displaysCapturedForFullscreen = FALSE;
1126 for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1128 if (![app isEqual:thisApp] && !app.terminated &&
1129 app.activationPolicy == NSApplicationActivationPolicyRegular)
1133 // There's another visible app. Just hide ourselves and let
1134 // the system activate the other app.
1140 otherValidApp = app;
1144 // Didn't find a visible GUI app. Try the Finder or, if that's not
1145 // running, the first hidden GUI app. If even that doesn't work, we
1146 // just fail to switch and remain the active app.
1147 app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1148 if (!app) app = otherValidApp;
1150 [app activateWithOptions:0];
1155 * ---------- Cursor clipping methods ----------
1157 * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1158 * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1159 * equivalent: CGAssociateMouseAndMouseCursorPosition(false). For the
1160 * general case, we leverage that. We disassociate mouse movements from
1161 * the cursor position and then move the cursor manually, keeping it within
1162 * the clipping rectangle.
1164 * Moving the cursor manually isn't enough. We need to modify the event
1165 * stream so that the events have the new location, too. We need to do
1166 * this at a point before the events enter Cocoa, so that Cocoa will assign
1167 * the correct window to the event. So, we install a Quartz event tap to
1170 * Also, there's a complication when we move the cursor. We use
1171 * CGWarpMouseCursorPosition(). That doesn't generate mouse movement
1172 * events, but the change of cursor position is incorporated into the
1173 * deltas of the next mouse move event. When the mouse is disassociated
1174 * from the cursor position, we need the deltas to only reflect actual
1175 * device movement, not programmatic changes. So, the event tap cancels
1176 * out the change caused by our calls to CGWarpMouseCursorPosition().
1178 - (void) clipCursorLocation:(CGPoint*)location
1180 if (location->x < CGRectGetMinX(cursorClipRect))
1181 location->x = CGRectGetMinX(cursorClipRect);
1182 if (location->y < CGRectGetMinY(cursorClipRect))
1183 location->y = CGRectGetMinY(cursorClipRect);
1184 if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1185 location->x = CGRectGetMaxX(cursorClipRect) - 1;
1186 if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1187 location->y = CGRectGetMaxY(cursorClipRect) - 1;
1190 - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1192 CGPoint oldLocation;
1194 if (currentLocation)
1195 oldLocation = *currentLocation;
1197 oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1199 if (!CGPointEqualToPoint(oldLocation, *newLocation))
1201 WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1204 warpRecord.from = oldLocation;
1205 warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1207 /* Actually move the cursor. */
1208 err = CGWarpMouseCursorPosition(*newLocation);
1209 if (err != kCGErrorSuccess)
1212 warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1213 *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1215 if (!CGPointEqualToPoint(oldLocation, *newLocation))
1217 warpRecord.to = *newLocation;
1218 [warpRecords addObject:warpRecord];
1225 - (BOOL) isMouseMoveEventType:(CGEventType)type
1229 case kCGEventMouseMoved:
1230 case kCGEventLeftMouseDragged:
1231 case kCGEventRightMouseDragged:
1232 case kCGEventOtherMouseDragged:
1239 - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1241 int warpsFinished = 0;
1242 for (WarpRecord* warpRecord in warpRecords)
1244 if (warpRecord.timeAfter < eventTime ||
1245 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1251 return warpsFinished;
1254 - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1255 type:(CGEventType)type
1256 event:(CGEventRef)event
1258 CGEventTimestamp eventTime;
1259 CGPoint eventLocation, cursorLocation;
1261 if (type == kCGEventTapDisabledByUserInput)
1263 if (type == kCGEventTapDisabledByTimeout)
1265 CGEventTapEnable(cursorClippingEventTap, TRUE);
1269 if (!clippingCursor)
1272 eventTime = CGEventGetTimestamp(event);
1273 lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1275 eventLocation = CGEventGetLocation(event);
1277 cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1279 if ([self isMouseMoveEventType:type])
1281 double deltaX, deltaY;
1282 int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1285 deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1286 deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1288 for (i = 0; i < warpsFinished; i++)
1290 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1291 deltaX -= warpRecord.to.x - warpRecord.from.x;
1292 deltaY -= warpRecord.to.y - warpRecord.from.y;
1293 [warpRecords removeObjectAtIndex:0];
1298 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1299 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1302 synthesizedLocation.x += deltaX;
1303 synthesizedLocation.y += deltaY;
1306 // If the event is destined for another process, don't clip it. This may
1307 // happen if the user activates Exposé or Mission Control. In that case,
1308 // our app does not resign active status, so clipping is still in effect,
1309 // but the cursor should not actually be clipped.
1311 // In addition, the fact that mouse moves may have been delivered to a
1312 // different process means we have to treat the next one we receive as
1313 // absolute rather than relative.
1314 if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1315 [self clipCursorLocation:&synthesizedLocation];
1317 lastSetCursorPositionTime = lastEventTapEventTime;
1319 [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1320 if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1321 CGEventSetLocation(event, synthesizedLocation);
1326 CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1327 CGEventRef event, void *refcon)
1329 WineApplicationController* controller = refcon;
1330 return [controller eventTapWithProxy:proxy type:type event:event];
1333 - (BOOL) installEventTap
1335 ProcessSerialNumber psn;
1337 CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown) |
1338 CGEventMaskBit(kCGEventLeftMouseUp) |
1339 CGEventMaskBit(kCGEventRightMouseDown) |
1340 CGEventMaskBit(kCGEventRightMouseUp) |
1341 CGEventMaskBit(kCGEventMouseMoved) |
1342 CGEventMaskBit(kCGEventLeftMouseDragged) |
1343 CGEventMaskBit(kCGEventRightMouseDragged) |
1344 CGEventMaskBit(kCGEventOtherMouseDown) |
1345 CGEventMaskBit(kCGEventOtherMouseUp) |
1346 CGEventMaskBit(kCGEventOtherMouseDragged) |
1347 CGEventMaskBit(kCGEventScrollWheel);
1348 CFRunLoopSourceRef source;
1350 OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1352 if (cursorClippingEventTap)
1355 // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1356 // framework with dlsym() because the Win32 function of the same name
1358 appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1362 pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1363 if (!pGetCurrentProcess)
1365 dlclose(appServices);
1369 err = pGetCurrentProcess(&psn);
1370 dlclose(appServices);
1374 // We create an annotated session event tap rather than a process-specific
1375 // event tap because we need to programmatically move the cursor even when
1376 // mouse moves are directed to other processes. We disable our tap when
1377 // other processes are active, but things like Exposé are handled by other
1378 // processes even when we remain active.
1379 cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1380 kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1381 if (!cursorClippingEventTap)
1384 CGEventTapEnable(cursorClippingEventTap, FALSE);
1386 source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1389 CFRelease(cursorClippingEventTap);
1390 cursorClippingEventTap = NULL;
1394 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1399 - (BOOL) setCursorPosition:(CGPoint)pos
1403 if ([windowsBeingDragged count])
1405 else if (clippingCursor)
1407 [self clipCursorLocation:&pos];
1409 ret = [self warpCursorTo:&pos from:NULL];
1410 synthesizedLocation = pos;
1413 // We want to discard mouse-move events that have already been
1414 // through the event tap, because it's too late to account for
1415 // the setting of the cursor position with them. However, the
1416 // events that may be queued with times after that but before
1417 // the above warp can still be used. So, use the last event
1418 // tap event time so that -sendEvent: doesn't discard them.
1419 lastSetCursorPositionTime = lastEventTapEventTime;
1424 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1425 // the mouse from the cursor position for 0.25 seconds. This means
1426 // that mouse movement during that interval doesn't move the cursor
1427 // and events carry a constant location (the warped-to position)
1428 // even though they have delta values. For apps which warp the
1429 // cursor frequently (like after every mouse move), this makes
1430 // cursor movement horribly laggy and jerky, as only a fraction of
1431 // mouse move events have any effect.
1433 // On some versions of OS X, it's sufficient to forcibly reassociate
1434 // the mouse and cursor position. On others, it's necessary to set
1435 // the local events suppression interval to 0 for the warp. That's
1436 // deprecated, but I'm not aware of any other way. For good
1437 // measure, we do both.
1438 CGSetLocalEventsSuppressionInterval(0);
1439 ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1440 CGSetLocalEventsSuppressionInterval(0.25);
1443 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1445 CGAssociateMouseAndMouseCursorPosition(true);
1451 WineEventQueue* queue;
1453 // Discard all pending mouse move events.
1454 [eventQueuesLock lock];
1455 for (queue in eventQueues)
1457 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1458 event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1460 [queue resetMouseEventPositions:pos];
1462 [eventQueuesLock unlock];
1468 - (void) activateCursorClipping
1470 if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1472 CGEventTapEnable(cursorClippingEventTap, TRUE);
1473 [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1477 - (void) deactivateCursorClipping
1479 if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1481 CGEventTapEnable(cursorClippingEventTap, FALSE);
1482 [warpRecords removeAllObjects];
1483 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1487 - (void) updateCursorClippingState
1489 if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1490 [self activateCursorClipping];
1492 [self deactivateCursorClipping];
1495 - (void) updateWindowsForCursorClipping
1498 for (window in [NSApp windows])
1500 if ([window isKindOfClass:[WineWindow class]])
1501 [window updateForCursorClipping];
1505 - (BOOL) startClippingCursor:(CGRect)rect
1509 if (!cursorClippingEventTap && ![self installEventTap])
1512 if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1513 CGEventTapIsEnabled(cursorClippingEventTap))
1516 err = CGAssociateMouseAndMouseCursorPosition(false);
1517 if (err != kCGErrorSuccess)
1520 clippingCursor = TRUE;
1521 cursorClipRect = rect;
1522 [self updateCursorClippingState];
1523 [self updateWindowsForCursorClipping];
1528 - (BOOL) stopClippingCursor
1530 CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1531 if (err != kCGErrorSuccess)
1534 clippingCursor = FALSE;
1535 [self updateCursorClippingState];
1536 [self updateWindowsForCursorClipping];
1541 - (BOOL) isKeyPressed:(uint16_t)keyCode
1543 int bits = sizeof(pressedKeyCodes[0]) * 8;
1544 int index = keyCode / bits;
1545 uint32_t mask = 1 << (keyCode % bits);
1546 return (pressedKeyCodes[index] & mask) != 0;
1549 - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1551 int bits = sizeof(pressedKeyCodes[0]) * 8;
1552 int index = keyCode / bits;
1553 uint32_t mask = 1 << (keyCode % bits);
1555 pressedKeyCodes[index] |= mask;
1557 pressedKeyCodes[index] &= ~mask;
1560 - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1563 [windowsBeingDragged addObject:window];
1565 [windowsBeingDragged removeObject:window];
1566 [self updateCursorClippingState];
1569 - (void) windowWillOrderOut:(WineWindow*)window
1571 if ([windowsBeingDragged containsObject:window])
1573 [self window:window isBeingDragged:NO];
1575 macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1576 [window.queue postEvent:event];
1577 macdrv_release_event(event);
1581 - (void) handleWindowDrag:(NSEvent*)anEvent begin:(BOOL)begin
1583 WineWindow* window = (WineWindow*)[anEvent window];
1584 if ([window isKindOfClass:[WineWindow class]])
1586 macdrv_event* event;
1591 [windowsBeingDragged addObject:window];
1592 eventType = WINDOW_DRAG_BEGIN;
1596 [windowsBeingDragged removeObject:window];
1597 eventType = WINDOW_DRAG_END;
1599 [self updateCursorClippingState];
1601 event = macdrv_create_event(eventType, window);
1602 if (eventType == WINDOW_DRAG_BEGIN)
1603 event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
1604 [window.queue postEvent:event];
1605 macdrv_release_event(event);
1609 - (void) handleMouseMove:(NSEvent*)anEvent
1611 WineWindow* targetWindow;
1612 BOOL drag = [anEvent type] != NSEventTypeMouseMoved;
1614 if ([windowsBeingDragged count])
1616 else if (mouseCaptureWindow)
1617 targetWindow = mouseCaptureWindow;
1619 targetWindow = (WineWindow*)[anEvent window];
1622 /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1623 event indicates its window is the main window, even if the cursor is
1624 over a different window. Find the actual WineWindow that is under the
1625 cursor and post the event as being for that window. */
1626 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1627 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1628 NSInteger windowUnderNumber;
1630 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1631 belowWindowWithWindowNumber:0];
1632 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1633 if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1637 if ([targetWindow isKindOfClass:[WineWindow class]])
1639 CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1640 macdrv_event* event;
1643 // If we recently warped the cursor (other than in our cursor-clipping
1644 // event tap), discard mouse move events until we see an event which is
1645 // later than that time.
1646 if (lastSetCursorPositionTime)
1648 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1651 lastSetCursorPositionTime = 0;
1652 forceNextMouseMoveAbsolute = TRUE;
1655 if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1658 forceNextMouseMoveAbsolute = FALSE;
1662 // Send absolute move events if the cursor is in the interior of
1663 // its range. Only send relative moves if the cursor is pinned to
1664 // the boundaries of where it can go. We compute the position
1665 // that's one additional point in the direction of movement. If
1666 // that is outside of the clipping rect or desktop region (the
1667 // union of the screen frames), then we figure the cursor would
1668 // have moved outside if it could but it was pinned.
1669 CGPoint computedPoint = point;
1670 CGFloat deltaX = [anEvent deltaX];
1671 CGFloat deltaY = [anEvent deltaY];
1675 else if (deltaX < -0.001)
1680 else if (deltaY < -0.001)
1683 // Assume cursor is pinned for now
1685 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1687 const CGRect* rects;
1688 NSUInteger count, i;
1690 // Caches screenFrameCGRects if necessary
1691 [self primaryScreenHeight];
1693 rects = [screenFrameCGRects bytes];
1694 count = [screenFrameCGRects length] / sizeof(rects[0]);
1696 for (i = 0; i < count; i++)
1698 if (CGRectContainsPoint(rects[i], computedPoint))
1710 [self clipCursorLocation:&point];
1711 point = cgpoint_win_from_mac(point);
1713 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1714 event->mouse_moved.x = floor(point.x);
1715 event->mouse_moved.y = floor(point.y);
1717 mouseMoveDeltaX = 0;
1718 mouseMoveDeltaY = 0;
1722 double scale = retina_on ? 2 : 1;
1724 /* Add event delta to accumulated delta error */
1725 /* deltaY is already flipped */
1726 mouseMoveDeltaX += [anEvent deltaX];
1727 mouseMoveDeltaY += [anEvent deltaY];
1729 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1730 event->mouse_moved.x = mouseMoveDeltaX * scale;
1731 event->mouse_moved.y = mouseMoveDeltaY * scale;
1733 /* Keep the remainder after integer truncation. */
1734 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1735 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1738 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1740 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1741 event->mouse_moved.drag = drag;
1743 [targetWindow.queue postEvent:event];
1746 macdrv_release_event(event);
1748 lastTargetWindow = targetWindow;
1751 lastTargetWindow = nil;
1753 [self updateCursor:FALSE];
1756 - (void) handleMouseButton:(NSEvent*)theEvent
1758 WineWindow* window = (WineWindow*)[theEvent window];
1759 NSEventType type = [theEvent type];
1760 WineWindow* windowBroughtForward = nil;
1761 BOOL process = FALSE;
1763 if (type == NSEventTypeLeftMouseUp && [windowsBeingDragged count])
1764 [self handleWindowDrag:theEvent begin:NO];
1766 if ([window isKindOfClass:[WineWindow class]] &&
1767 type == NSEventTypeLeftMouseDown &&
1768 ![theEvent wine_commandKeyDown])
1770 NSWindowButton windowButton;
1772 windowBroughtForward = window;
1774 /* Any left-click on our window anyplace other than the close or
1775 minimize buttons will bring it forward. */
1776 for (windowButton = NSWindowCloseButton;
1777 windowButton <= NSWindowMiniaturizeButton;
1780 NSButton* button = [window standardWindowButton:windowButton];
1783 NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1784 if ([button mouse:point inRect:[button bounds]])
1786 windowBroughtForward = nil;
1793 if ([windowsBeingDragged count])
1795 else if (mouseCaptureWindow)
1796 window = mouseCaptureWindow;
1798 if ([window isKindOfClass:[WineWindow class]])
1800 BOOL pressed = (type == NSEventTypeLeftMouseDown ||
1801 type == NSEventTypeRightMouseDown ||
1802 type == NSEventTypeOtherMouseDown);
1803 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1806 [self clipCursorLocation:&pt];
1810 if (mouseCaptureWindow)
1814 // Test if the click was in the window's content area.
1815 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1816 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1817 process = NSMouseInRect(nspoint, contentRect, NO);
1818 if (process && [window styleMask] & NSWindowStyleMaskResizable)
1820 // Ignore clicks in the grow box (resize widget).
1821 HIPoint origin = { 0, 0 };
1822 HIThemeGrowBoxDrawInfo info = { 0 };
1826 info.kind = kHIThemeGrowBoxKindNormal;
1827 info.direction = kThemeGrowRight | kThemeGrowDown;
1828 if ([window styleMask] & NSWindowStyleMaskUtilityWindow)
1829 info.size = kHIThemeGrowBoxSizeSmall;
1831 info.size = kHIThemeGrowBoxSizeNormal;
1833 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1834 if (status == noErr)
1836 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1837 NSMinY(contentRect),
1839 bounds.size.height);
1840 process = !NSMouseInRect(nspoint, growBox, NO);
1845 unmatchedMouseDowns |= NSEventMaskFromType(type);
1849 NSEventType downType = type - 1;
1850 NSUInteger downMask = NSEventMaskFromType(downType);
1851 process = (unmatchedMouseDowns & downMask) != 0;
1852 unmatchedMouseDowns &= ~downMask;
1857 macdrv_event* event;
1859 pt = cgpoint_win_from_mac(pt);
1861 event = macdrv_create_event(MOUSE_BUTTON, window);
1862 event->mouse_button.button = [theEvent buttonNumber];
1863 event->mouse_button.pressed = pressed;
1864 event->mouse_button.x = floor(pt.x);
1865 event->mouse_button.y = floor(pt.y);
1866 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1868 [window.queue postEvent:event];
1870 macdrv_release_event(event);
1874 if (windowBroughtForward)
1876 WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1877 NSInteger ancestorNumber = [ancestor windowNumber];
1878 NSInteger ancestorLevel = [ancestor level];
1880 for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1882 NSInteger windowNumber = [windowNumberObject integerValue];
1883 if (windowNumber == ancestorNumber)
1885 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1886 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1887 [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1889 [ancestor postBroughtForwardEvent];
1893 if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate)
1894 [self windowGotFocus:windowBroughtForward];
1897 // Since mouse button events deliver absolute cursor position, the
1898 // accumulating delta from move events is invalidated. Make sure
1899 // next mouse move event starts over from an absolute baseline.
1900 // Also, it's at least possible that the title bar widgets (e.g. close
1901 // button, etc.) could enter an internal event loop on a mouse down that
1902 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1903 // dragged events and, after that, any notion of the cursor position
1904 // computed from accumulating deltas would be wrong.
1905 forceNextMouseMoveAbsolute = TRUE;
1908 - (void) handleScrollWheel:(NSEvent*)theEvent
1912 if (mouseCaptureWindow)
1913 window = mouseCaptureWindow;
1915 window = (WineWindow*)[theEvent window];
1917 if ([window isKindOfClass:[WineWindow class]])
1919 CGEventRef cgevent = [theEvent CGEvent];
1920 CGPoint pt = CGEventGetLocation(cgevent);
1924 [self clipCursorLocation:&pt];
1926 if (mouseCaptureWindow)
1930 // Only process the event if it was in the window's content area.
1931 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1932 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1933 process = NSMouseInRect(nspoint, contentRect, NO);
1938 macdrv_event* event;
1940 BOOL continuous = FALSE;
1942 pt = cgpoint_win_from_mac(pt);
1944 event = macdrv_create_event(MOUSE_SCROLL, window);
1945 event->mouse_scroll.x = floor(pt.x);
1946 event->mouse_scroll.y = floor(pt.y);
1947 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1949 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1953 /* Continuous scroll wheel events come from high-precision scrolling
1954 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1955 For these, we can get more precise data from the CGEvent API. */
1956 /* Axis 1 is vertical, axis 2 is horizontal. */
1957 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1958 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1962 double pixelsPerLine = 10;
1963 CGEventSourceRef source;
1965 /* The non-continuous values are in units of "lines", not pixels. */
1966 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1968 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1972 x = pixelsPerLine * [theEvent deltaX];
1973 y = pixelsPerLine * [theEvent deltaY];
1976 /* Mac: negative is right or down, positive is left or up.
1977 Win32: negative is left or down, positive is right or up.
1978 So, negate the X scroll value to translate. */
1981 /* The x,y values so far are in pixels. Win32 expects to receive some
1982 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1983 6 times the pixel value. */
1987 if (use_precise_scrolling)
1989 event->mouse_scroll.x_scroll = x;
1990 event->mouse_scroll.y_scroll = y;
1994 /* For non-continuous "clicky" wheels, if there was any motion, make
1995 sure there was at least WHEEL_DELTA motion. This is so, at slow
1996 speeds where the system's acceleration curve is actually reducing the
1997 scroll distance, the user is sure to get some action out of each click.
1998 For example, this is important for rotating though weapons in a
1999 first-person shooter. */
2000 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
2001 event->mouse_scroll.x_scroll = 120;
2002 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
2003 event->mouse_scroll.x_scroll = -120;
2005 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
2006 event->mouse_scroll.y_scroll = 120;
2007 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
2008 event->mouse_scroll.y_scroll = -120;
2013 /* If it's been a while since the last scroll event or if the scrolling has
2014 reversed direction, reset the accumulated scroll value. */
2015 if ([theEvent timestamp] - lastScrollTime > 1)
2016 accumScrollX = accumScrollY = 0;
2019 /* The accumulated scroll value is in the opposite direction/sign of the last
2020 scroll. That's because it's the "debt" resulting from over-scrolling in
2021 that direction. We accumulate by adding in the scroll amount and then, if
2022 it has the same sign as the scroll value, we subtract any whole or partial
2023 WHEEL_DELTAs, leaving it 0 or the opposite sign. So, the user switched
2024 scroll direction if the accumulated debt and the new scroll value have the
2026 if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
2028 if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
2031 lastScrollTime = [theEvent timestamp];
2036 if (accumScrollX > 0 && x > 0)
2037 event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
2038 if (accumScrollX < 0 && x < 0)
2039 event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
2040 if (accumScrollY > 0 && y > 0)
2041 event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
2042 if (accumScrollY < 0 && y < 0)
2043 event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
2045 accumScrollX -= event->mouse_scroll.x_scroll;
2046 accumScrollY -= event->mouse_scroll.y_scroll;
2049 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
2050 [window.queue postEvent:event];
2052 macdrv_release_event(event);
2054 // Since scroll wheel events deliver absolute cursor position, the
2055 // accumulating delta from move events is invalidated. Make sure next
2056 // mouse move event starts over from an absolute baseline.
2057 forceNextMouseMoveAbsolute = TRUE;
2062 // Returns TRUE if the event was handled and caller should do nothing more
2063 // with it. Returns FALSE if the caller should process it as normal and
2064 // then call -didSendEvent:.
2065 - (BOOL) handleEvent:(NSEvent*)anEvent
2068 NSEventType type = [anEvent type];
2070 if (type == NSEventTypeFlagsChanged)
2071 self.lastFlagsChanged = anEvent;
2072 else if (type == NSEventTypeMouseMoved || type == NSEventTypeLeftMouseDragged ||
2073 type == NSEventTypeRightMouseDragged || type == NSEventTypeOtherMouseDragged)
2075 [self handleMouseMove:anEvent];
2076 ret = mouseCaptureWindow && ![windowsBeingDragged count];
2078 else if (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp ||
2079 type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp ||
2080 type == NSEventTypeOtherMouseDown || type == NSEventTypeOtherMouseUp)
2082 [self handleMouseButton:anEvent];
2083 ret = mouseCaptureWindow && ![windowsBeingDragged count];
2085 else if (type == NSEventTypeScrollWheel)
2087 [self handleScrollWheel:anEvent];
2088 ret = mouseCaptureWindow != nil;
2090 else if (type == NSEventTypeKeyDown)
2092 // -[NSApplication sendEvent:] seems to consume presses of the Help
2093 // key (Insert key on PC keyboards), so we have to bypass it and
2094 // send the event directly to the window.
2095 if (anEvent.keyCode == kVK_Help)
2097 [anEvent.window sendEvent:anEvent];
2101 else if (type == NSEventTypeKeyUp)
2103 uint16_t keyCode = [anEvent keyCode];
2104 if ([self isKeyPressed:keyCode])
2106 WineWindow* window = (WineWindow*)[anEvent window];
2107 [self noteKey:keyCode pressed:FALSE];
2108 if ([window isKindOfClass:[WineWindow class]])
2109 [window postKeyEvent:anEvent];
2112 else if (type == NSEventTypeAppKitDefined)
2114 short subtype = [anEvent subtype];
2116 // These subtypes are not documented but they appear to mean
2117 // "a window is being dragged" and "a window is no longer being
2118 // dragged", respectively.
2119 if (subtype == 20 || subtype == 21)
2120 [self handleWindowDrag:anEvent begin:(subtype == 20)];
2126 - (void) didSendEvent:(NSEvent*)anEvent
2128 NSEventType type = [anEvent type];
2130 if (type == NSEventTypeKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
2132 NSUInteger modifiers = [anEvent modifierFlags];
2133 if ((modifiers & NSEventModifierFlagCommand) &&
2134 !(modifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)))
2136 // Command-Tab and Command-Shift-Tab would normally be intercepted
2137 // by the system to switch applications. If we're seeing it, it's
2138 // presumably because we've captured the displays, preventing
2139 // normal application switching. Do it manually.
2140 [self handleCommandTab];
2145 - (void) setupObservations
2147 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2148 NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
2149 NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
2151 [nc addObserverForName:NSWindowDidBecomeKeyNotification
2154 usingBlock:^(NSNotification *note){
2155 NSWindow* window = [note object];
2156 [keyWindows removeObjectIdenticalTo:window];
2157 [keyWindows insertObject:window atIndex:0];
2160 [nc addObserverForName:NSWindowWillCloseNotification
2162 queue:[NSOperationQueue mainQueue]
2163 usingBlock:^(NSNotification *note){
2164 NSWindow* window = [note object];
2165 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
2167 [keyWindows removeObjectIdenticalTo:window];
2168 if (window == lastTargetWindow)
2169 lastTargetWindow = nil;
2170 if (window == self.mouseCaptureWindow)
2171 self.mouseCaptureWindow = nil;
2172 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
2174 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
2175 [self updateFullscreenWindows];
2178 [windowsBeingDragged removeObject:window];
2179 [self updateCursorClippingState];
2182 [nc addObserver:self
2183 selector:@selector(keyboardSelectionDidChange)
2184 name:NSTextInputContextKeyboardSelectionDidChangeNotification
2187 /* The above notification isn't sent unless the NSTextInputContext
2188 class has initialized itself. Poke it. */
2189 [NSTextInputContext self];
2191 [wsnc addObserver:self
2192 selector:@selector(activeSpaceDidChange)
2193 name:NSWorkspaceActiveSpaceDidChangeNotification
2196 [nc addObserver:self
2197 selector:@selector(releaseMouseCapture)
2198 name:NSMenuDidBeginTrackingNotification
2201 [dnc addObserver:self
2202 selector:@selector(releaseMouseCapture)
2203 name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2205 suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2207 [dnc addObserver:self
2208 selector:@selector(enabledKeyboardInputSourcesChanged)
2209 name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2213 - (BOOL) inputSourceIsInputMethod
2215 if (!inputSourceIsInputMethodValid)
2217 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2220 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2221 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2222 CFRelease(inputSource);
2225 inputSourceIsInputMethod = FALSE;
2226 inputSourceIsInputMethodValid = TRUE;
2229 return inputSourceIsInputMethod;
2232 - (void) releaseMouseCapture
2234 // This might be invoked on a background thread by the distributed
2235 // notification center. Shunt it to the main thread.
2236 if (![NSThread isMainThread])
2238 dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2242 if (mouseCaptureWindow)
2244 macdrv_event* event;
2246 event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2247 [mouseCaptureWindow.queue postEvent:event];
2248 macdrv_release_event(event);
2252 - (void) unminimizeWindowIfNoneVisible
2254 if (![self frontWineWindow])
2256 for (WineWindow* window in [NSApp windows])
2258 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2260 [window deminiaturize:self];
2267 - (void) setRetinaMode:(int)mode
2273 double scale = mode ? 0.5 : 2.0;
2274 cursorClipRect.origin.x *= scale;
2275 cursorClipRect.origin.y *= scale;
2276 cursorClipRect.size.width *= scale;
2277 cursorClipRect.size.height *= scale;
2280 for (WineWindow* window in [NSApp windows])
2282 if ([window isKindOfClass:[WineWindow class]])
2283 [window setRetinaMode:mode];
2289 * ---------- NSApplicationDelegate methods ----------
2291 - (void)applicationDidBecomeActive:(NSNotification *)notification
2293 NSNumber* displayID;
2294 NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2296 latentDisplayModes = [[NSMutableDictionary alloc] init];
2297 for (displayID in modesToRealize)
2299 CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2300 [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2303 [self updateCursorClippingState];
2305 [self updateFullscreenWindows];
2306 [self adjustWindowLevels:YES];
2309 [self unminimizeWindowIfNoneVisible];
2312 // If a Wine process terminates abruptly while it has the display captured
2313 // and switched to a different resolution, Mac OS X will uncapture the
2314 // displays and switch their resolutions back. However, the other Wine
2315 // processes won't have their notion of the desktop rect changed back.
2316 // This can lead them to refuse to draw or acknowledge clicks in certain
2317 // portions of their windows.
2319 // To solve this, we synthesize a displays-changed event whenever we're
2320 // activated. This will provoke a re-synchronization of Wine's notion of
2321 // the desktop rect with the actual state.
2322 [self sendDisplaysChanged:TRUE];
2324 // The cursor probably moved while we were inactive. Accumulated mouse
2325 // movement deltas are invalidated. Make sure the next mouse move event
2326 // starts over from an absolute baseline.
2327 forceNextMouseMoveAbsolute = TRUE;
2330 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2332 primaryScreenHeightValid = FALSE;
2333 [self sendDisplaysChanged:FALSE];
2334 [self adjustWindowLevels];
2336 // When the display configuration changes, the cursor position may jump.
2337 // Accumulated mouse movement deltas are invalidated. Make sure the next
2338 // mouse move event starts over from an absolute baseline.
2339 forceNextMouseMoveAbsolute = TRUE;
2342 - (void)applicationDidResignActive:(NSNotification *)notification
2344 macdrv_event* event;
2345 WineEventQueue* queue;
2347 [self updateCursorClippingState];
2349 [self invalidateGotFocusEvents];
2351 event = macdrv_create_event(APP_DEACTIVATED, nil);
2353 [eventQueuesLock lock];
2354 for (queue in eventQueues)
2355 [queue postEvent:event];
2356 [eventQueuesLock unlock];
2358 macdrv_release_event(event);
2360 [self releaseMouseCapture];
2363 - (void) applicationDidUnhide:(NSNotification*)aNotification
2365 [self adjustWindowLevels];
2368 - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2370 // Note that "flag" is often wrong. WineWindows are NSPanels and NSPanels
2371 // don't count as "visible windows" for this purpose.
2372 [self unminimizeWindowIfNoneVisible];
2376 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2378 NSApplicationTerminateReply ret = NSTerminateNow;
2379 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2380 NSAppleEventDescriptor* desc = [m currentAppleEvent];
2381 macdrv_event* event;
2382 WineEventQueue* queue;
2384 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2386 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2389 case kAEReallyLogOut:
2390 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2392 case kAEShowRestartDialog:
2393 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2395 case kAEShowShutdownDialog:
2396 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2399 event->app_quit_requested.reason = QUIT_REASON_NONE;
2403 [eventQueuesLock lock];
2405 if ([eventQueues count])
2407 for (queue in eventQueues)
2408 [queue postEvent:event];
2409 ret = NSTerminateLater;
2412 [eventQueuesLock unlock];
2414 macdrv_release_event(event);
2419 - (void)applicationWillBecomeActive:(NSNotification *)notification
2421 macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2424 [eventQueuesLock lock];
2425 for (WineEventQueue* queue in eventQueues)
2426 [queue postEvent:event];
2427 [eventQueuesLock unlock];
2429 macdrv_release_event(event);
2432 - (void)applicationWillResignActive:(NSNotification *)notification
2434 [self adjustWindowLevels:NO];
2437 /***********************************************************************
2440 * Run-loop-source perform callback. Pull request blocks from the
2441 * array of queued requests and invoke them.
2443 static void PerformRequest(void *info)
2445 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2446 WineApplicationController* controller = [WineApplicationController sharedController];
2450 __block dispatch_block_t block;
2452 dispatch_sync(controller->requestsManipQueue, ^{
2453 if ([controller->requests count])
2455 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2456 [controller->requests removeObjectAtIndex:0];
2469 pool = [[NSAutoreleasePool alloc] init];
2475 /***********************************************************************
2478 * Run a block on the main thread asynchronously.
2480 void OnMainThreadAsync(dispatch_block_t block)
2482 WineApplicationController* controller = [WineApplicationController sharedController];
2484 block = [block copy];
2485 dispatch_sync(controller->requestsManipQueue, ^{
2486 [controller->requests addObject:block];
2489 CFRunLoopSourceSignal(controller->requestSource);
2490 CFRunLoopWakeUp(CFRunLoopGetMain());
2495 /***********************************************************************
2498 void LogError(const char* func, NSString* format, ...)
2501 va_start(args, format);
2502 LogErrorv(func, format, args);
2506 /***********************************************************************
2509 void LogErrorv(const char* func, NSString* format, va_list args)
2511 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2513 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2514 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2520 /***********************************************************************
2521 * macdrv_window_rejected_focus
2523 * Pass focus to the next window that hasn't already rejected this same
2524 * WINDOW_GOT_FOCUS event.
2526 void macdrv_window_rejected_focus(const macdrv_event *event)
2529 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2533 /***********************************************************************
2534 * macdrv_get_input_source_info
2536 * Returns the keyboard layout uchr data, keyboard type and input source.
2538 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2541 TISInputSourceRef inputSourceLayout;
2543 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2544 if (inputSourceLayout)
2546 CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2547 kTISPropertyUnicodeKeyLayoutData);
2548 *uchr = CFDataCreateCopy(NULL, data);
2549 CFRelease(inputSourceLayout);
2551 *keyboard_type = [WineApplicationController sharedController].keyboardType;
2552 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2554 *input_source = TISCopyCurrentKeyboardInputSource();
2559 /***********************************************************************
2562 * Play the beep sound configured by the user in System Preferences.
2564 void macdrv_beep(void)
2566 OnMainThreadAsync(^{
2571 /***********************************************************************
2572 * macdrv_set_display_mode
2574 int macdrv_set_display_mode(const struct macdrv_display* display,
2575 CGDisplayModeRef display_mode)
2580 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2586 /***********************************************************************
2591 * If name is non-NULL, it is a selector for a class method on NSCursor
2592 * identifying the cursor to set. In that case, frames is ignored. If
2593 * name is NULL, then frames is used.
2595 * frames is an array of dictionaries. Each dictionary is a frame of
2596 * an animated cursor. Under the key "image" is a CGImage for the
2597 * frame. Under the key "duration" is a CFNumber time interval, in
2598 * seconds, for how long that frame is presented before proceeding to
2599 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
2600 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2601 * This is the hot spot, measured in pixels down and to the right of the
2602 * top-left corner of the image.
2604 * If the array has exactly 1 element, the cursor is static, not
2605 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
2607 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2611 sel = NSSelectorFromString((NSString*)name);
2614 OnMainThreadAsync(^{
2615 WineApplicationController* controller = [WineApplicationController sharedController];
2616 [controller setCursorWithFrames:nil];
2617 controller.cursor = [NSCursor performSelector:sel];
2618 [controller unhideCursor];
2623 NSArray* nsframes = (NSArray*)frames;
2624 if ([nsframes count])
2626 OnMainThreadAsync(^{
2627 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2632 OnMainThreadAsync(^{
2633 WineApplicationController* controller = [WineApplicationController sharedController];
2634 [controller setCursorWithFrames:nil];
2635 [controller hideCursor];
2641 /***********************************************************************
2642 * macdrv_get_cursor_position
2644 * Obtains the current cursor position. Returns zero on failure,
2645 * non-zero on success.
2647 int macdrv_get_cursor_position(CGPoint *pos)
2650 NSPoint location = [NSEvent mouseLocation];
2651 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2652 *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2658 /***********************************************************************
2659 * macdrv_set_cursor_position
2661 * Sets the cursor position without generating events. Returns zero on
2662 * failure, non-zero on success.
2664 int macdrv_set_cursor_position(CGPoint pos)
2669 ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2675 /***********************************************************************
2676 * macdrv_clip_cursor
2678 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
2679 * to or larger than the whole desktop region, the cursor is unclipped.
2680 * Returns zero on failure, non-zero on success.
2682 int macdrv_clip_cursor(CGRect r)
2687 WineApplicationController* controller = [WineApplicationController sharedController];
2688 BOOL clipping = FALSE;
2691 if (!CGRectIsInfinite(rect))
2692 rect = cgrect_mac_from_win(rect);
2694 if (!CGRectIsInfinite(rect))
2696 NSRect nsrect = NSRectFromCGRect(rect);
2699 /* Convert the rectangle from top-down coords to bottom-up. */
2700 [controller flipRect:&nsrect];
2703 for (screen in [NSScreen screens])
2705 if (!NSContainsRect(nsrect, [screen frame]))
2714 ret = [controller startClippingCursor:rect];
2716 ret = [controller stopClippingCursor];
2722 /***********************************************************************
2723 * macdrv_set_application_icon
2725 * Set the application icon. The images array contains CGImages. If
2726 * there are more than one, then they represent different sizes or
2727 * color depths from the icon resource. If images is NULL or empty,
2728 * restores the default application image.
2730 void macdrv_set_application_icon(CFArrayRef images)
2732 NSArray* imageArray = (NSArray*)images;
2734 OnMainThreadAsync(^{
2735 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2739 /***********************************************************************
2742 void macdrv_quit_reply(int reply)
2745 [NSApp replyToApplicationShouldTerminate:reply];
2749 /***********************************************************************
2750 * macdrv_using_input_method
2752 int macdrv_using_input_method(void)
2757 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2763 /***********************************************************************
2764 * macdrv_set_mouse_capture_window
2766 void macdrv_set_mouse_capture_window(macdrv_window window)
2768 WineWindow* w = (WineWindow*)window;
2770 [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2773 [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2777 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2778 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2779 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2781 /***********************************************************************
2782 * macdrv_create_input_source_list
2784 CFArrayRef macdrv_create_input_source_list(void)
2786 CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2789 CFArrayRef input_list;
2790 CFDictionaryRef filter_dict;
2791 const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2792 const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2795 filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2796 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2797 input_list = TISCreateInputSourceList(filter_dict, false);
2799 for (i = 0; i < CFArrayGetCount(input_list); i++)
2801 TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2802 CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2803 CFDictionaryRef entry;
2804 const void *input_keys[3] = { macdrv_input_source_input_key,
2805 macdrv_input_source_type_key,
2806 macdrv_input_source_lang_key };
2807 const void *input_values[3];
2809 input_values[0] = input;
2810 input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2811 input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2813 entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2814 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2816 CFArrayAppendValue(ret, entry);
2819 CFRelease(input_list);
2820 CFRelease(filter_dict);
2826 int macdrv_select_input_source(TISInputSourceRef input_source)
2828 __block int ret = FALSE;
2831 ret = (TISSelectInputSource(input_source) == noErr);
2837 void macdrv_set_cocoa_retina_mode(int new_mode)
2840 [[WineApplicationController sharedController] setRetinaMode:new_mode];