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:NSCommandKeyMask | NSAlternateKeyMask];
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:NSCommandKeyMask | NSAlternateKeyMask];
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:NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask];
295 [submenu addItem:[NSMenuItem separatorItem]];
296 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
297 action:@selector(arrangeInFront:)
299 item = [[[NSMenuItem alloc] init] autorelease];
300 [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
301 [item setSubmenu:submenu];
302 [mainMenu addItem:item];
304 [NSApp setMainMenu:mainMenu];
305 [NSApp setWindowsMenu:submenu];
307 [NSApp setApplicationIconImage:self.applicationIcon];
311 - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
313 PerformRequest(NULL);
319 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
320 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
322 inMode:NSDefaultRunLoopMode
325 [NSApp sendEvent:event];
329 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
330 } while (!*done && [timeout timeIntervalSinceNow] >= 0);
335 - (BOOL) registerEventQueue:(WineEventQueue*)queue
337 [eventQueuesLock lock];
338 [eventQueues addObject:queue];
339 [eventQueuesLock unlock];
343 - (void) unregisterEventQueue:(WineEventQueue*)queue
345 [eventQueuesLock lock];
346 [eventQueues removeObjectIdenticalTo:queue];
347 [eventQueuesLock unlock];
350 - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
352 eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
355 - (double) ticksForEventTime:(NSTimeInterval)eventTime
357 return (eventTime + eventTimeAdjustment) * 1000;
360 /* Invalidate old focus offers across all queues. */
361 - (void) invalidateGotFocusEvents
363 WineEventQueue* queue;
367 [eventQueuesLock lock];
368 for (queue in eventQueues)
370 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
373 [eventQueuesLock unlock];
376 - (void) windowGotFocus:(WineWindow*)window
380 [self invalidateGotFocusEvents];
382 event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
383 event->window_got_focus.serial = windowFocusSerial;
385 event->window_got_focus.tried_windows = [triedWindows retain];
387 event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
388 [window.queue postEvent:event];
389 macdrv_release_event(event);
392 - (void) windowRejectedFocusEvent:(const macdrv_event*)event
394 if (event->window_got_focus.serial == windowFocusSerial)
396 NSMutableArray* windows = [keyWindows mutableCopy];
397 NSNumber* windowNumber;
400 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
402 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
403 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
404 ![windows containsObject:window])
405 [windows addObject:window];
408 triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
409 [triedWindows addObject:(WineWindow*)event->window];
410 for (window in windows)
412 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
414 [window makeKeyWindow];
423 static BOOL EqualInputSource(TISInputSourceRef source1, TISInputSourceRef source2)
425 if (!source1 && !source2)
427 if (!source1 || !source2)
429 return CFEqual(source1, source2);
432 - (void) keyboardSelectionDidChange:(BOOL)force
434 TISInputSourceRef inputSource, inputSourceLayout;
438 NSTextInputContext* context = [NSTextInputContext currentInputContext];
439 if (!context || ![context client])
443 inputSource = TISCopyCurrentKeyboardInputSource();
444 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
445 if (!force && EqualInputSource(inputSource, lastKeyboardInputSource) &&
446 EqualInputSource(inputSourceLayout, lastKeyboardLayoutInputSource))
448 if (inputSource) CFRelease(inputSource);
449 if (inputSourceLayout) CFRelease(inputSourceLayout);
453 if (lastKeyboardInputSource)
454 CFRelease(lastKeyboardInputSource);
455 lastKeyboardInputSource = inputSource;
456 if (lastKeyboardLayoutInputSource)
457 CFRelease(lastKeyboardLayoutInputSource);
458 lastKeyboardLayoutInputSource = inputSourceLayout;
460 inputSourceIsInputMethodValid = FALSE;
462 if (inputSourceLayout)
465 uchr = TISGetInputSourceProperty(inputSourceLayout,
466 kTISPropertyUnicodeKeyLayoutData);
470 WineEventQueue* queue;
472 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
473 event->keyboard_changed.keyboard_type = self.keyboardType;
474 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
475 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
476 event->keyboard_changed.input_source = (TISInputSourceRef)CFRetain(inputSource);
478 if (event->keyboard_changed.uchr)
480 [eventQueuesLock lock];
482 for (queue in eventQueues)
483 [queue postEvent:event];
485 [eventQueuesLock unlock];
488 macdrv_release_event(event);
493 - (void) keyboardSelectionDidChange
495 [self keyboardSelectionDidChange:NO];
498 - (void) setKeyboardType:(CGEventSourceKeyboardType)newType
500 if (newType != keyboardType)
502 keyboardType = newType;
503 [self keyboardSelectionDidChange:YES];
507 - (void) enabledKeyboardInputSourcesChanged
509 macdrv_layout_list_needs_update = TRUE;
512 - (CGFloat) primaryScreenHeight
514 if (!primaryScreenHeightValid)
516 NSArray* screens = [NSScreen screens];
517 NSUInteger count = [screens count];
524 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
525 primaryScreenHeightValid = TRUE;
527 size = count * sizeof(CGRect);
528 if (!screenFrameCGRects)
529 screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
531 [screenFrameCGRects setLength:size];
533 rect = [screenFrameCGRects mutableBytes];
534 for (screen in screens)
536 CGRect temp = NSRectToCGRect([screen frame]);
537 temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
542 return 1280; /* arbitrary value */
545 return primaryScreenHeight;
548 - (NSPoint) flippedMouseLocation:(NSPoint)point
550 /* This relies on the fact that Cocoa's mouse location points are
551 actually off by one (precisely because they were flipped from
552 Quartz screen coordinates using this same technique). */
553 point.y = [self primaryScreenHeight] - point.y;
557 - (void) flipRect:(NSRect*)rect
559 // We don't use -primaryScreenHeight here so there's no chance of having
560 // out-of-date cached info. This method is called infrequently enough
561 // that getting the screen height each time is not prohibitively expensive.
562 rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
565 - (WineWindow*) frontWineWindow
567 NSNumber* windowNumber;
568 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
570 NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
571 if ([window isKindOfClass:[WineWindow class]] && [window screen])
572 return (WineWindow*)window;
578 - (void) adjustWindowLevels:(BOOL)active
580 NSArray* windowNumbers;
581 NSMutableArray* wineWindows;
582 NSNumber* windowNumber;
583 NSUInteger nextFloatingIndex = 0;
584 __block NSInteger maxLevel = NSIntegerMin;
585 __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
586 __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
587 __block WineWindow* prev = nil;
590 if ([NSApp isHidden]) return;
592 windowNumbers = [NSWindow windowNumbersWithOptions:0];
593 wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
595 // For the most part, we rely on the window server's ordering of the windows
596 // to be authoritative. The one exception is if the "floating" property of
597 // one of the windows has been changed, it may be in the wrong level and thus
598 // in the order. This method is what's supposed to fix that up. So build
599 // a list of Wine windows sorted first by floating-ness and then by order
600 // as indicated by the window server.
601 for (windowNumber in windowNumbers)
603 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
604 if ([window isKindOfClass:[WineWindow class]])
607 [wineWindows insertObject:window atIndex:nextFloatingIndex++];
609 [wineWindows addObject:window];
613 NSDisableScreenUpdates();
615 // Go from back to front so that all windows in front of one which is
616 // elevated for full-screen are also elevated.
617 [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
618 usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
619 WineWindow* window = (WineWindow*)obj;
620 NSInteger origLevel = [window level];
621 NSInteger newLevel = [window minimumLevelForActive:active];
625 if (minFloatingLevel <= maxNonfloatingLevel)
626 minFloatingLevel = maxNonfloatingLevel + 1;
627 if (newLevel < minFloatingLevel)
628 newLevel = minFloatingLevel;
631 if (newLevel < maxLevel)
636 if (!window.floating && maxNonfloatingLevel < newLevel)
637 maxNonfloatingLevel = newLevel;
639 if (newLevel != origLevel)
641 [window setLevel:newLevel];
643 // -setLevel: puts the window at the front of its new level. If
644 // we decreased the level, that's good (it was in front of that
645 // level before, so it should still be now). But if we increased
646 // the level, the window should be toward the back (but still
647 // ahead of the previous windows we did this to).
648 if (origLevel < newLevel)
651 [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
653 [window orderBack:nil];
660 NSEnableScreenUpdates();
662 [wineWindows release];
664 // The above took care of the visible windows on the current space. That
665 // leaves windows on other spaces, minimized windows, and windows which
666 // are not ordered in. We want to leave windows on other spaces alone
667 // so the space remains just as they left it (when viewed in Exposé or
668 // Mission Control, for example). We'll adjust the window levels again
669 // after we switch to another space, anyway. Windows which aren't
670 // ordered in will be handled when we order them in. Minimized windows
671 // on the current space should be set to the level they would have gotten
672 // if they were at the front of the windows with the same floating-ness,
673 // because that's where they'll go if/when they are unminimized. Again,
674 // for good measure we'll adjust window levels again when a window is
676 for (window in [NSApp windows])
678 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
679 [window isOnActiveSpace])
681 NSInteger origLevel = [window level];
682 NSInteger newLevel = [window minimumLevelForActive:YES];
683 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
685 if (newLevel < maxLevelForType)
686 newLevel = maxLevelForType;
688 if (newLevel != origLevel)
689 [window setLevel:newLevel];
694 - (void) adjustWindowLevels
696 [self adjustWindowLevels:[NSApp isActive]];
699 - (void) updateFullscreenWindows
701 if (capture_displays_for_fullscreen && [NSApp isActive])
703 BOOL anyFullscreen = FALSE;
704 NSNumber* windowNumber;
705 for (windowNumber in [NSWindow windowNumbersWithOptions:0])
707 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
708 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
710 anyFullscreen = TRUE;
717 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
718 displaysCapturedForFullscreen = TRUE;
720 else if (displaysCapturedForFullscreen)
722 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
723 displaysCapturedForFullscreen = FALSE;
728 - (void) activeSpaceDidChange
730 [self updateFullscreenWindows];
731 [self adjustWindowLevels];
734 - (void) sendDisplaysChanged:(BOOL)activating
737 WineEventQueue* queue;
739 event = macdrv_create_event(DISPLAYS_CHANGED, nil);
740 event->displays_changed.activating = activating;
742 [eventQueuesLock lock];
744 // If we're activating, then we just need one of our threads to get the
745 // event, so it can send it directly to the desktop window. Otherwise,
746 // we need all of the threads to get it because we don't know which owns
747 // the desktop window and only that one will do anything with it.
748 if (activating) event->deliver = 1;
750 for (queue in eventQueues)
751 [queue postEvent:event];
752 [eventQueuesLock unlock];
754 macdrv_release_event(event);
757 // We can compare two modes directly using CFEqual, but that may require that
758 // they are identical to a level that we don't need. In particular, when the
759 // OS switches between the integrated and discrete GPUs, the set of display
760 // modes can change in subtle ways. We're interested in whether two modes
761 // match in their most salient features, even if they aren't identical.
762 - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
764 NSString *encoding1, *encoding2;
765 uint32_t ioflags1, ioflags2, different;
766 double refresh1, refresh2;
768 if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
769 if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
771 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
772 if (&CGDisplayModeGetPixelWidth != NULL &&
773 CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
774 if (&CGDisplayModeGetPixelHeight != NULL &&
775 CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
778 encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
779 encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
780 if (![encoding1 isEqualToString:encoding2]) return FALSE;
782 ioflags1 = CGDisplayModeGetIOFlags(mode1);
783 ioflags2 = CGDisplayModeGetIOFlags(mode2);
784 different = ioflags1 ^ ioflags2;
785 if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
786 kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
789 refresh1 = CGDisplayModeGetRefreshRate(mode1);
790 if (refresh1 == 0) refresh1 = 60;
791 refresh2 = CGDisplayModeGetRefreshRate(mode2);
792 if (refresh2 == 0) refresh2 = 60;
793 if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
798 - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
800 NSMutableArray* ret = [NSMutableArray array];
801 NSDictionary* options = nil;
803 #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
804 options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:TRUE]
805 forKey:(NSString*)kCGDisplayShowDuplicateLowResolutionModes];
808 NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
809 for (id candidateModeObject in modes)
811 CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
812 if ([self mode:candidateMode matchesMode:mode])
813 [ret addObject:candidateModeObject];
818 - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
821 NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
822 CGDisplayModeRef originalMode;
824 originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
826 if (originalMode && [self mode:mode matchesMode:originalMode])
828 if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
830 CGRestorePermanentDisplayConfiguration();
831 if (!displaysCapturedForFullscreen)
832 CGReleaseAllDisplays();
833 [originalDisplayModes removeAllObjects];
836 else // ... otherwise, try to restore just the one display
838 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
840 mode = (CGDisplayModeRef)modeObject;
841 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
843 [originalDisplayModes removeObjectForKey:displayIDKey];
852 CGDisplayModeRef currentMode;
855 currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
857 currentMode = CGDisplayCopyDisplayMode(displayID);
858 if (!currentMode) // Invalid display ID
861 if ([self mode:mode matchesMode:currentMode]) // Already there!
863 CGDisplayModeRelease(currentMode);
867 CGDisplayModeRelease(currentMode);
870 modes = [self modesMatchingMode:mode forDisplay:displayID];
874 [self transformProcessToForeground];
876 BOOL active = [NSApp isActive];
878 if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
879 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
883 // If we get here, we have the displays captured. If we don't
884 // know the original mode of the display, the current mode must
885 // be the original. We should re-query the current mode since
886 // another process could have changed it between when we last
887 // checked and when we captured the displays.
889 originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
893 for (id modeObject in modes)
895 mode = (CGDisplayModeRef)modeObject;
896 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
903 if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
904 [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
905 else if (![originalDisplayModes count])
907 CGRestorePermanentDisplayConfiguration();
908 if (!displaysCapturedForFullscreen)
909 CGReleaseAllDisplays();
913 CGDisplayModeRelease(currentMode);
917 [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
924 [self adjustWindowLevels];
929 - (BOOL) areDisplaysCaptured
931 return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
934 - (void) updateCursor:(BOOL)force
936 if (force || lastTargetWindow)
938 if (clientWantsCursorHidden && !cursorHidden)
944 if (!cursorIsCurrent)
947 cursorIsCurrent = TRUE;
950 if (!clientWantsCursorHidden && cursorHidden)
953 cursorHidden = FALSE;
960 [[NSCursor arrowCursor] set];
961 cursorIsCurrent = FALSE;
966 cursorHidden = FALSE;
973 if (!clientWantsCursorHidden)
975 clientWantsCursorHidden = TRUE;
976 [self updateCursor:TRUE];
980 - (void) unhideCursor
982 if (clientWantsCursorHidden)
984 clientWantsCursorHidden = FALSE;
985 [self updateCursor:FALSE];
989 - (void) setCursor:(NSCursor*)newCursor
991 if (newCursor != cursor)
994 cursor = [newCursor retain];
995 cursorIsCurrent = FALSE;
996 [self updateCursor:FALSE];
1002 NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
1003 CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
1004 CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
1005 NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
1006 CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
1009 if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
1010 hotSpot = CGPointZero;
1011 hotSpot = cgpoint_mac_from_win(hotSpot);
1012 self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1014 [self unhideCursor];
1017 - (void) nextCursorFrame:(NSTimer*)theTimer
1019 NSDictionary* frame;
1020 NSTimeInterval duration;
1024 if (cursorFrame >= [cursorFrames count])
1028 frame = [cursorFrames objectAtIndex:cursorFrame];
1029 duration = [[frame objectForKey:@"duration"] doubleValue];
1030 date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1031 [cursorTimer setFireDate:date];
1034 - (void) setCursorWithFrames:(NSArray*)frames
1036 if (self.cursorFrames == frames)
1039 self.cursorFrames = frames;
1041 [cursorTimer invalidate];
1042 self.cursorTimer = nil;
1046 if ([frames count] > 1)
1048 NSDictionary* frame = [frames objectAtIndex:0];
1049 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
1050 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1051 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1054 selector:@selector(nextCursorFrame:)
1056 repeats:YES] autorelease];
1057 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1064 - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1066 NSImage* nsimage = nil;
1070 NSSize bestSize = NSZeroSize;
1073 nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1075 for (image in images)
1077 CGImageRef cgimage = (CGImageRef)image;
1078 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1081 NSSize size = [imageRep size];
1083 [nsimage addRepresentation:imageRep];
1086 if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1091 if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1092 [nsimage setSize:bestSize];
1097 self.applicationIcon = nsimage;
1100 - (void) handleCommandTab
1102 if ([NSApp isActive])
1104 NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1105 NSRunningApplication* app;
1106 NSRunningApplication* otherValidApp = nil;
1108 if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1110 NSNumber* displayID;
1111 for (displayID in originalDisplayModes)
1113 CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1114 [latentDisplayModes setObject:(id)mode forKey:displayID];
1115 CGDisplayModeRelease(mode);
1118 CGRestorePermanentDisplayConfiguration();
1119 CGReleaseAllDisplays();
1120 [originalDisplayModes removeAllObjects];
1121 displaysCapturedForFullscreen = FALSE;
1124 for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1126 if (![app isEqual:thisApp] && !app.terminated &&
1127 app.activationPolicy == NSApplicationActivationPolicyRegular)
1131 // There's another visible app. Just hide ourselves and let
1132 // the system activate the other app.
1138 otherValidApp = app;
1142 // Didn't find a visible GUI app. Try the Finder or, if that's not
1143 // running, the first hidden GUI app. If even that doesn't work, we
1144 // just fail to switch and remain the active app.
1145 app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1146 if (!app) app = otherValidApp;
1148 [app activateWithOptions:0];
1153 * ---------- Cursor clipping methods ----------
1155 * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1156 * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1157 * equivalent: CGAssociateMouseAndMouseCursorPosition(false). For the
1158 * general case, we leverage that. We disassociate mouse movements from
1159 * the cursor position and then move the cursor manually, keeping it within
1160 * the clipping rectangle.
1162 * Moving the cursor manually isn't enough. We need to modify the event
1163 * stream so that the events have the new location, too. We need to do
1164 * this at a point before the events enter Cocoa, so that Cocoa will assign
1165 * the correct window to the event. So, we install a Quartz event tap to
1168 * Also, there's a complication when we move the cursor. We use
1169 * CGWarpMouseCursorPosition(). That doesn't generate mouse movement
1170 * events, but the change of cursor position is incorporated into the
1171 * deltas of the next mouse move event. When the mouse is disassociated
1172 * from the cursor position, we need the deltas to only reflect actual
1173 * device movement, not programmatic changes. So, the event tap cancels
1174 * out the change caused by our calls to CGWarpMouseCursorPosition().
1176 - (void) clipCursorLocation:(CGPoint*)location
1178 if (location->x < CGRectGetMinX(cursorClipRect))
1179 location->x = CGRectGetMinX(cursorClipRect);
1180 if (location->y < CGRectGetMinY(cursorClipRect))
1181 location->y = CGRectGetMinY(cursorClipRect);
1182 if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1183 location->x = CGRectGetMaxX(cursorClipRect) - 1;
1184 if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1185 location->y = CGRectGetMaxY(cursorClipRect) - 1;
1188 - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1190 CGPoint oldLocation;
1192 if (currentLocation)
1193 oldLocation = *currentLocation;
1195 oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1197 if (!CGPointEqualToPoint(oldLocation, *newLocation))
1199 WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1202 warpRecord.from = oldLocation;
1203 warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1205 /* Actually move the cursor. */
1206 err = CGWarpMouseCursorPosition(*newLocation);
1207 if (err != kCGErrorSuccess)
1210 warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1211 *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1213 if (!CGPointEqualToPoint(oldLocation, *newLocation))
1215 warpRecord.to = *newLocation;
1216 [warpRecords addObject:warpRecord];
1223 - (BOOL) isMouseMoveEventType:(CGEventType)type
1227 case kCGEventMouseMoved:
1228 case kCGEventLeftMouseDragged:
1229 case kCGEventRightMouseDragged:
1230 case kCGEventOtherMouseDragged:
1237 - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1239 int warpsFinished = 0;
1240 for (WarpRecord* warpRecord in warpRecords)
1242 if (warpRecord.timeAfter < eventTime ||
1243 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1249 return warpsFinished;
1252 - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1253 type:(CGEventType)type
1254 event:(CGEventRef)event
1256 CGEventTimestamp eventTime;
1257 CGPoint eventLocation, cursorLocation;
1259 if (type == kCGEventTapDisabledByUserInput)
1261 if (type == kCGEventTapDisabledByTimeout)
1263 CGEventTapEnable(cursorClippingEventTap, TRUE);
1267 if (!clippingCursor)
1270 eventTime = CGEventGetTimestamp(event);
1271 lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1273 eventLocation = CGEventGetLocation(event);
1275 cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1277 if ([self isMouseMoveEventType:type])
1279 double deltaX, deltaY;
1280 int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1283 deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1284 deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1286 for (i = 0; i < warpsFinished; i++)
1288 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1289 deltaX -= warpRecord.to.x - warpRecord.from.x;
1290 deltaY -= warpRecord.to.y - warpRecord.from.y;
1291 [warpRecords removeObjectAtIndex:0];
1296 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1297 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1300 synthesizedLocation.x += deltaX;
1301 synthesizedLocation.y += deltaY;
1304 // If the event is destined for another process, don't clip it. This may
1305 // happen if the user activates Exposé or Mission Control. In that case,
1306 // our app does not resign active status, so clipping is still in effect,
1307 // but the cursor should not actually be clipped.
1309 // In addition, the fact that mouse moves may have been delivered to a
1310 // different process means we have to treat the next one we receive as
1311 // absolute rather than relative.
1312 if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1313 [self clipCursorLocation:&synthesizedLocation];
1315 lastSetCursorPositionTime = lastEventTapEventTime;
1317 [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1318 if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1319 CGEventSetLocation(event, synthesizedLocation);
1324 CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1325 CGEventRef event, void *refcon)
1327 WineApplicationController* controller = refcon;
1328 return [controller eventTapWithProxy:proxy type:type event:event];
1331 - (BOOL) installEventTap
1333 ProcessSerialNumber psn;
1335 CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown) |
1336 CGEventMaskBit(kCGEventLeftMouseUp) |
1337 CGEventMaskBit(kCGEventRightMouseDown) |
1338 CGEventMaskBit(kCGEventRightMouseUp) |
1339 CGEventMaskBit(kCGEventMouseMoved) |
1340 CGEventMaskBit(kCGEventLeftMouseDragged) |
1341 CGEventMaskBit(kCGEventRightMouseDragged) |
1342 CGEventMaskBit(kCGEventOtherMouseDown) |
1343 CGEventMaskBit(kCGEventOtherMouseUp) |
1344 CGEventMaskBit(kCGEventOtherMouseDragged) |
1345 CGEventMaskBit(kCGEventScrollWheel);
1346 CFRunLoopSourceRef source;
1348 OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1350 if (cursorClippingEventTap)
1353 // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1354 // framework with dlsym() because the Win32 function of the same name
1356 appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1360 pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1361 if (!pGetCurrentProcess)
1363 dlclose(appServices);
1367 err = pGetCurrentProcess(&psn);
1368 dlclose(appServices);
1372 // We create an annotated session event tap rather than a process-specific
1373 // event tap because we need to programmatically move the cursor even when
1374 // mouse moves are directed to other processes. We disable our tap when
1375 // other processes are active, but things like Exposé are handled by other
1376 // processes even when we remain active.
1377 cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1378 kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1379 if (!cursorClippingEventTap)
1382 CGEventTapEnable(cursorClippingEventTap, FALSE);
1384 source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1387 CFRelease(cursorClippingEventTap);
1388 cursorClippingEventTap = NULL;
1392 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1397 - (BOOL) setCursorPosition:(CGPoint)pos
1401 if ([windowsBeingDragged count])
1403 else if (clippingCursor)
1405 [self clipCursorLocation:&pos];
1407 ret = [self warpCursorTo:&pos from:NULL];
1408 synthesizedLocation = pos;
1411 // We want to discard mouse-move events that have already been
1412 // through the event tap, because it's too late to account for
1413 // the setting of the cursor position with them. However, the
1414 // events that may be queued with times after that but before
1415 // the above warp can still be used. So, use the last event
1416 // tap event time so that -sendEvent: doesn't discard them.
1417 lastSetCursorPositionTime = lastEventTapEventTime;
1422 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1423 // the mouse from the cursor position for 0.25 seconds. This means
1424 // that mouse movement during that interval doesn't move the cursor
1425 // and events carry a constant location (the warped-to position)
1426 // even though they have delta values. For apps which warp the
1427 // cursor frequently (like after every mouse move), this makes
1428 // cursor movement horribly laggy and jerky, as only a fraction of
1429 // mouse move events have any effect.
1431 // On some versions of OS X, it's sufficient to forcibly reassociate
1432 // the mouse and cursor position. On others, it's necessary to set
1433 // the local events suppression interval to 0 for the warp. That's
1434 // deprecated, but I'm not aware of any other way. For good
1435 // measure, we do both.
1436 CGSetLocalEventsSuppressionInterval(0);
1437 ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1438 CGSetLocalEventsSuppressionInterval(0.25);
1441 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1443 CGAssociateMouseAndMouseCursorPosition(true);
1449 WineEventQueue* queue;
1451 // Discard all pending mouse move events.
1452 [eventQueuesLock lock];
1453 for (queue in eventQueues)
1455 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1456 event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1458 [queue resetMouseEventPositions:pos];
1460 [eventQueuesLock unlock];
1466 - (void) activateCursorClipping
1468 if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1470 CGEventTapEnable(cursorClippingEventTap, TRUE);
1471 [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1475 - (void) deactivateCursorClipping
1477 if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1479 CGEventTapEnable(cursorClippingEventTap, FALSE);
1480 [warpRecords removeAllObjects];
1481 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1485 - (void) updateCursorClippingState
1487 if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1488 [self activateCursorClipping];
1490 [self deactivateCursorClipping];
1493 - (void) updateWindowsForCursorClipping
1496 for (window in [NSApp windows])
1498 if ([window isKindOfClass:[WineWindow class]])
1499 [window updateForCursorClipping];
1503 - (BOOL) startClippingCursor:(CGRect)rect
1507 if (!cursorClippingEventTap && ![self installEventTap])
1510 if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1511 CGEventTapIsEnabled(cursorClippingEventTap))
1514 err = CGAssociateMouseAndMouseCursorPosition(false);
1515 if (err != kCGErrorSuccess)
1518 clippingCursor = TRUE;
1519 cursorClipRect = rect;
1520 [self updateCursorClippingState];
1521 [self updateWindowsForCursorClipping];
1526 - (BOOL) stopClippingCursor
1528 CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1529 if (err != kCGErrorSuccess)
1532 clippingCursor = FALSE;
1533 [self updateCursorClippingState];
1534 [self updateWindowsForCursorClipping];
1539 - (BOOL) isKeyPressed:(uint16_t)keyCode
1541 int bits = sizeof(pressedKeyCodes[0]) * 8;
1542 int index = keyCode / bits;
1543 uint32_t mask = 1 << (keyCode % bits);
1544 return (pressedKeyCodes[index] & mask) != 0;
1547 - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1549 int bits = sizeof(pressedKeyCodes[0]) * 8;
1550 int index = keyCode / bits;
1551 uint32_t mask = 1 << (keyCode % bits);
1553 pressedKeyCodes[index] |= mask;
1555 pressedKeyCodes[index] &= ~mask;
1558 - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1561 [windowsBeingDragged addObject:window];
1563 [windowsBeingDragged removeObject:window];
1564 [self updateCursorClippingState];
1567 - (void) windowWillOrderOut:(WineWindow*)window
1569 if ([windowsBeingDragged containsObject:window])
1571 [self window:window isBeingDragged:NO];
1573 macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1574 [window.queue postEvent:event];
1575 macdrv_release_event(event);
1579 - (void) handleWindowDrag:(NSEvent*)anEvent begin:(BOOL)begin
1581 WineWindow* window = (WineWindow*)[anEvent window];
1582 if ([window isKindOfClass:[WineWindow class]])
1584 macdrv_event* event;
1589 [windowsBeingDragged addObject:window];
1590 eventType = WINDOW_DRAG_BEGIN;
1594 [windowsBeingDragged removeObject:window];
1595 eventType = WINDOW_DRAG_END;
1597 [self updateCursorClippingState];
1599 event = macdrv_create_event(eventType, window);
1600 if (eventType == WINDOW_DRAG_BEGIN)
1601 event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
1602 [window.queue postEvent:event];
1603 macdrv_release_event(event);
1607 - (void) handleMouseMove:(NSEvent*)anEvent
1609 WineWindow* targetWindow;
1610 BOOL drag = [anEvent type] != NSMouseMoved;
1612 if ([windowsBeingDragged count])
1614 else if (mouseCaptureWindow)
1615 targetWindow = mouseCaptureWindow;
1617 targetWindow = (WineWindow*)[anEvent window];
1620 /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1621 event indicates its window is the main window, even if the cursor is
1622 over a different window. Find the actual WineWindow that is under the
1623 cursor and post the event as being for that window. */
1624 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1625 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1626 NSInteger windowUnderNumber;
1628 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1629 belowWindowWithWindowNumber:0];
1630 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1631 if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1635 if ([targetWindow isKindOfClass:[WineWindow class]])
1637 CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1638 macdrv_event* event;
1641 // If we recently warped the cursor (other than in our cursor-clipping
1642 // event tap), discard mouse move events until we see an event which is
1643 // later than that time.
1644 if (lastSetCursorPositionTime)
1646 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1649 lastSetCursorPositionTime = 0;
1650 forceNextMouseMoveAbsolute = TRUE;
1653 if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1656 forceNextMouseMoveAbsolute = FALSE;
1660 // Send absolute move events if the cursor is in the interior of
1661 // its range. Only send relative moves if the cursor is pinned to
1662 // the boundaries of where it can go. We compute the position
1663 // that's one additional point in the direction of movement. If
1664 // that is outside of the clipping rect or desktop region (the
1665 // union of the screen frames), then we figure the cursor would
1666 // have moved outside if it could but it was pinned.
1667 CGPoint computedPoint = point;
1668 CGFloat deltaX = [anEvent deltaX];
1669 CGFloat deltaY = [anEvent deltaY];
1673 else if (deltaX < -0.001)
1678 else if (deltaY < -0.001)
1681 // Assume cursor is pinned for now
1683 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1685 const CGRect* rects;
1686 NSUInteger count, i;
1688 // Caches screenFrameCGRects if necessary
1689 [self primaryScreenHeight];
1691 rects = [screenFrameCGRects bytes];
1692 count = [screenFrameCGRects length] / sizeof(rects[0]);
1694 for (i = 0; i < count; i++)
1696 if (CGRectContainsPoint(rects[i], computedPoint))
1708 [self clipCursorLocation:&point];
1709 point = cgpoint_win_from_mac(point);
1711 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1712 event->mouse_moved.x = floor(point.x);
1713 event->mouse_moved.y = floor(point.y);
1715 mouseMoveDeltaX = 0;
1716 mouseMoveDeltaY = 0;
1720 double scale = retina_on ? 2 : 1;
1722 /* Add event delta to accumulated delta error */
1723 /* deltaY is already flipped */
1724 mouseMoveDeltaX += [anEvent deltaX];
1725 mouseMoveDeltaY += [anEvent deltaY];
1727 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1728 event->mouse_moved.x = mouseMoveDeltaX * scale;
1729 event->mouse_moved.y = mouseMoveDeltaY * scale;
1731 /* Keep the remainder after integer truncation. */
1732 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1733 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1736 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1738 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1739 event->mouse_moved.drag = drag;
1741 [targetWindow.queue postEvent:event];
1744 macdrv_release_event(event);
1746 lastTargetWindow = targetWindow;
1749 lastTargetWindow = nil;
1751 [self updateCursor:FALSE];
1754 - (void) handleMouseButton:(NSEvent*)theEvent
1756 WineWindow* window = (WineWindow*)[theEvent window];
1757 NSEventType type = [theEvent type];
1758 WineWindow* windowBroughtForward = nil;
1759 BOOL process = FALSE;
1761 if (type == NSLeftMouseUp && [windowsBeingDragged count])
1762 [self handleWindowDrag:theEvent begin:NO];
1764 if ([window isKindOfClass:[WineWindow class]] &&
1765 type == NSLeftMouseDown &&
1766 ![theEvent wine_commandKeyDown])
1768 NSWindowButton windowButton;
1770 windowBroughtForward = window;
1772 /* Any left-click on our window anyplace other than the close or
1773 minimize buttons will bring it forward. */
1774 for (windowButton = NSWindowCloseButton;
1775 windowButton <= NSWindowMiniaturizeButton;
1778 NSButton* button = [window standardWindowButton:windowButton];
1781 NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1782 if ([button mouse:point inRect:[button bounds]])
1784 windowBroughtForward = nil;
1791 if ([windowsBeingDragged count])
1793 else if (mouseCaptureWindow)
1794 window = mouseCaptureWindow;
1796 if ([window isKindOfClass:[WineWindow class]])
1798 BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1799 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1802 [self clipCursorLocation:&pt];
1806 if (mouseCaptureWindow)
1810 // Test if the click was in the window's content area.
1811 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1812 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1813 process = NSMouseInRect(nspoint, contentRect, NO);
1814 if (process && [window styleMask] & NSResizableWindowMask)
1816 // Ignore clicks in the grow box (resize widget).
1817 HIPoint origin = { 0, 0 };
1818 HIThemeGrowBoxDrawInfo info = { 0 };
1822 info.kind = kHIThemeGrowBoxKindNormal;
1823 info.direction = kThemeGrowRight | kThemeGrowDown;
1824 if ([window styleMask] & NSUtilityWindowMask)
1825 info.size = kHIThemeGrowBoxSizeSmall;
1827 info.size = kHIThemeGrowBoxSizeNormal;
1829 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1830 if (status == noErr)
1832 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1833 NSMinY(contentRect),
1835 bounds.size.height);
1836 process = !NSMouseInRect(nspoint, growBox, NO);
1841 unmatchedMouseDowns |= NSEventMaskFromType(type);
1845 NSEventType downType = type - 1;
1846 NSUInteger downMask = NSEventMaskFromType(downType);
1847 process = (unmatchedMouseDowns & downMask) != 0;
1848 unmatchedMouseDowns &= ~downMask;
1853 macdrv_event* event;
1855 pt = cgpoint_win_from_mac(pt);
1857 event = macdrv_create_event(MOUSE_BUTTON, window);
1858 event->mouse_button.button = [theEvent buttonNumber];
1859 event->mouse_button.pressed = pressed;
1860 event->mouse_button.x = floor(pt.x);
1861 event->mouse_button.y = floor(pt.y);
1862 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1864 [window.queue postEvent:event];
1866 macdrv_release_event(event);
1870 if (windowBroughtForward)
1872 WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1873 NSInteger ancestorNumber = [ancestor windowNumber];
1874 NSInteger ancestorLevel = [ancestor level];
1876 for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1878 NSInteger windowNumber = [windowNumberObject integerValue];
1879 if (windowNumber == ancestorNumber)
1881 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1882 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1883 [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1885 [ancestor postBroughtForwardEvent];
1889 if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate)
1890 [self windowGotFocus:windowBroughtForward];
1893 // Since mouse button events deliver absolute cursor position, the
1894 // accumulating delta from move events is invalidated. Make sure
1895 // next mouse move event starts over from an absolute baseline.
1896 // Also, it's at least possible that the title bar widgets (e.g. close
1897 // button, etc.) could enter an internal event loop on a mouse down that
1898 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1899 // dragged events and, after that, any notion of the cursor position
1900 // computed from accumulating deltas would be wrong.
1901 forceNextMouseMoveAbsolute = TRUE;
1904 - (void) handleScrollWheel:(NSEvent*)theEvent
1908 if (mouseCaptureWindow)
1909 window = mouseCaptureWindow;
1911 window = (WineWindow*)[theEvent window];
1913 if ([window isKindOfClass:[WineWindow class]])
1915 CGEventRef cgevent = [theEvent CGEvent];
1916 CGPoint pt = CGEventGetLocation(cgevent);
1920 [self clipCursorLocation:&pt];
1922 if (mouseCaptureWindow)
1926 // Only process the event if it was in the window's content area.
1927 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1928 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1929 process = NSMouseInRect(nspoint, contentRect, NO);
1934 macdrv_event* event;
1936 BOOL continuous = FALSE;
1938 pt = cgpoint_win_from_mac(pt);
1940 event = macdrv_create_event(MOUSE_SCROLL, window);
1941 event->mouse_scroll.x = floor(pt.x);
1942 event->mouse_scroll.y = floor(pt.y);
1943 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1945 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1949 /* Continuous scroll wheel events come from high-precision scrolling
1950 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1951 For these, we can get more precise data from the CGEvent API. */
1952 /* Axis 1 is vertical, axis 2 is horizontal. */
1953 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1954 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1958 double pixelsPerLine = 10;
1959 CGEventSourceRef source;
1961 /* The non-continuous values are in units of "lines", not pixels. */
1962 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1964 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1968 x = pixelsPerLine * [theEvent deltaX];
1969 y = pixelsPerLine * [theEvent deltaY];
1972 /* Mac: negative is right or down, positive is left or up.
1973 Win32: negative is left or down, positive is right or up.
1974 So, negate the X scroll value to translate. */
1977 /* The x,y values so far are in pixels. Win32 expects to receive some
1978 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1979 6 times the pixel value. */
1983 if (use_precise_scrolling)
1985 event->mouse_scroll.x_scroll = x;
1986 event->mouse_scroll.y_scroll = y;
1990 /* For non-continuous "clicky" wheels, if there was any motion, make
1991 sure there was at least WHEEL_DELTA motion. This is so, at slow
1992 speeds where the system's acceleration curve is actually reducing the
1993 scroll distance, the user is sure to get some action out of each click.
1994 For example, this is important for rotating though weapons in a
1995 first-person shooter. */
1996 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1997 event->mouse_scroll.x_scroll = 120;
1998 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1999 event->mouse_scroll.x_scroll = -120;
2001 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
2002 event->mouse_scroll.y_scroll = 120;
2003 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
2004 event->mouse_scroll.y_scroll = -120;
2009 /* If it's been a while since the last scroll event or if the scrolling has
2010 reversed direction, reset the accumulated scroll value. */
2011 if ([theEvent timestamp] - lastScrollTime > 1)
2012 accumScrollX = accumScrollY = 0;
2015 /* The accumulated scroll value is in the opposite direction/sign of the last
2016 scroll. That's because it's the "debt" resulting from over-scrolling in
2017 that direction. We accumulate by adding in the scroll amount and then, if
2018 it has the same sign as the scroll value, we subtract any whole or partial
2019 WHEEL_DELTAs, leaving it 0 or the opposite sign. So, the user switched
2020 scroll direction if the accumulated debt and the new scroll value have the
2022 if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
2024 if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
2027 lastScrollTime = [theEvent timestamp];
2032 if (accumScrollX > 0 && x > 0)
2033 event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
2034 if (accumScrollX < 0 && x < 0)
2035 event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
2036 if (accumScrollY > 0 && y > 0)
2037 event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
2038 if (accumScrollY < 0 && y < 0)
2039 event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
2041 accumScrollX -= event->mouse_scroll.x_scroll;
2042 accumScrollY -= event->mouse_scroll.y_scroll;
2045 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
2046 [window.queue postEvent:event];
2048 macdrv_release_event(event);
2050 // Since scroll wheel events deliver absolute cursor position, the
2051 // accumulating delta from move events is invalidated. Make sure next
2052 // mouse move event starts over from an absolute baseline.
2053 forceNextMouseMoveAbsolute = TRUE;
2058 // Returns TRUE if the event was handled and caller should do nothing more
2059 // with it. Returns FALSE if the caller should process it as normal and
2060 // then call -didSendEvent:.
2061 - (BOOL) handleEvent:(NSEvent*)anEvent
2064 NSEventType type = [anEvent type];
2066 if (type == NSFlagsChanged)
2067 self.lastFlagsChanged = anEvent;
2068 else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
2069 type == NSRightMouseDragged || type == NSOtherMouseDragged)
2071 [self handleMouseMove:anEvent];
2072 ret = mouseCaptureWindow && ![windowsBeingDragged count];
2074 else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
2075 type == NSRightMouseDown || type == NSRightMouseUp ||
2076 type == NSOtherMouseDown || type == NSOtherMouseUp)
2078 [self handleMouseButton:anEvent];
2079 ret = mouseCaptureWindow && ![windowsBeingDragged count];
2081 else if (type == NSScrollWheel)
2083 [self handleScrollWheel:anEvent];
2084 ret = mouseCaptureWindow != nil;
2086 else if (type == NSKeyDown)
2088 // -[NSApplication sendEvent:] seems to consume presses of the Help
2089 // key (Insert key on PC keyboards), so we have to bypass it and
2090 // send the event directly to the window.
2091 if (anEvent.keyCode == kVK_Help)
2093 [anEvent.window sendEvent:anEvent];
2097 else if (type == NSKeyUp)
2099 uint16_t keyCode = [anEvent keyCode];
2100 if ([self isKeyPressed:keyCode])
2102 WineWindow* window = (WineWindow*)[anEvent window];
2103 [self noteKey:keyCode pressed:FALSE];
2104 if ([window isKindOfClass:[WineWindow class]])
2105 [window postKeyEvent:anEvent];
2108 else if (type == NSAppKitDefined)
2110 short subtype = [anEvent subtype];
2112 // These subtypes are not documented but they appear to mean
2113 // "a window is being dragged" and "a window is no longer being
2114 // dragged", respectively.
2115 if (subtype == 20 || subtype == 21)
2116 [self handleWindowDrag:anEvent begin:(subtype == 20)];
2122 - (void) didSendEvent:(NSEvent*)anEvent
2124 NSEventType type = [anEvent type];
2126 if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
2128 NSUInteger modifiers = [anEvent modifierFlags];
2129 if ((modifiers & NSCommandKeyMask) &&
2130 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
2132 // Command-Tab and Command-Shift-Tab would normally be intercepted
2133 // by the system to switch applications. If we're seeing it, it's
2134 // presumably because we've captured the displays, preventing
2135 // normal application switching. Do it manually.
2136 [self handleCommandTab];
2141 - (void) setupObservations
2143 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2144 NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
2145 NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
2147 [nc addObserverForName:NSWindowDidBecomeKeyNotification
2150 usingBlock:^(NSNotification *note){
2151 NSWindow* window = [note object];
2152 [keyWindows removeObjectIdenticalTo:window];
2153 [keyWindows insertObject:window atIndex:0];
2156 [nc addObserverForName:NSWindowWillCloseNotification
2158 queue:[NSOperationQueue mainQueue]
2159 usingBlock:^(NSNotification *note){
2160 NSWindow* window = [note object];
2161 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
2163 [keyWindows removeObjectIdenticalTo:window];
2164 if (window == lastTargetWindow)
2165 lastTargetWindow = nil;
2166 if (window == self.mouseCaptureWindow)
2167 self.mouseCaptureWindow = nil;
2168 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
2170 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
2171 [self updateFullscreenWindows];
2174 [windowsBeingDragged removeObject:window];
2175 [self updateCursorClippingState];
2178 [nc addObserver:self
2179 selector:@selector(keyboardSelectionDidChange)
2180 name:NSTextInputContextKeyboardSelectionDidChangeNotification
2183 /* The above notification isn't sent unless the NSTextInputContext
2184 class has initialized itself. Poke it. */
2185 [NSTextInputContext self];
2187 [wsnc addObserver:self
2188 selector:@selector(activeSpaceDidChange)
2189 name:NSWorkspaceActiveSpaceDidChangeNotification
2192 [nc addObserver:self
2193 selector:@selector(releaseMouseCapture)
2194 name:NSMenuDidBeginTrackingNotification
2197 [dnc addObserver:self
2198 selector:@selector(releaseMouseCapture)
2199 name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2201 suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2203 [dnc addObserver:self
2204 selector:@selector(enabledKeyboardInputSourcesChanged)
2205 name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2209 - (BOOL) inputSourceIsInputMethod
2211 if (!inputSourceIsInputMethodValid)
2213 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2216 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2217 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2218 CFRelease(inputSource);
2221 inputSourceIsInputMethod = FALSE;
2222 inputSourceIsInputMethodValid = TRUE;
2225 return inputSourceIsInputMethod;
2228 - (void) releaseMouseCapture
2230 // This might be invoked on a background thread by the distributed
2231 // notification center. Shunt it to the main thread.
2232 if (![NSThread isMainThread])
2234 dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2238 if (mouseCaptureWindow)
2240 macdrv_event* event;
2242 event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2243 [mouseCaptureWindow.queue postEvent:event];
2244 macdrv_release_event(event);
2248 - (void) unminimizeWindowIfNoneVisible
2250 if (![self frontWineWindow])
2252 for (WineWindow* window in [NSApp windows])
2254 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2256 [window deminiaturize:self];
2263 - (void) setRetinaMode:(int)mode
2269 double scale = mode ? 0.5 : 2.0;
2270 cursorClipRect.origin.x *= scale;
2271 cursorClipRect.origin.y *= scale;
2272 cursorClipRect.size.width *= scale;
2273 cursorClipRect.size.height *= scale;
2276 for (WineWindow* window in [NSApp windows])
2278 if ([window isKindOfClass:[WineWindow class]])
2279 [window setRetinaMode:mode];
2285 * ---------- NSApplicationDelegate methods ----------
2287 - (void)applicationDidBecomeActive:(NSNotification *)notification
2289 NSNumber* displayID;
2290 NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2292 latentDisplayModes = [[NSMutableDictionary alloc] init];
2293 for (displayID in modesToRealize)
2295 CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2296 [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2299 [self updateCursorClippingState];
2301 [self updateFullscreenWindows];
2302 [self adjustWindowLevels:YES];
2305 [self unminimizeWindowIfNoneVisible];
2308 // If a Wine process terminates abruptly while it has the display captured
2309 // and switched to a different resolution, Mac OS X will uncapture the
2310 // displays and switch their resolutions back. However, the other Wine
2311 // processes won't have their notion of the desktop rect changed back.
2312 // This can lead them to refuse to draw or acknowledge clicks in certain
2313 // portions of their windows.
2315 // To solve this, we synthesize a displays-changed event whenever we're
2316 // activated. This will provoke a re-synchronization of Wine's notion of
2317 // the desktop rect with the actual state.
2318 [self sendDisplaysChanged:TRUE];
2320 // The cursor probably moved while we were inactive. Accumulated mouse
2321 // movement deltas are invalidated. Make sure the next mouse move event
2322 // starts over from an absolute baseline.
2323 forceNextMouseMoveAbsolute = TRUE;
2326 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2328 primaryScreenHeightValid = FALSE;
2329 [self sendDisplaysChanged:FALSE];
2330 [self adjustWindowLevels];
2332 // When the display configuration changes, the cursor position may jump.
2333 // Accumulated mouse movement deltas are invalidated. Make sure the next
2334 // mouse move event starts over from an absolute baseline.
2335 forceNextMouseMoveAbsolute = TRUE;
2338 - (void)applicationDidResignActive:(NSNotification *)notification
2340 macdrv_event* event;
2341 WineEventQueue* queue;
2343 [self updateCursorClippingState];
2345 [self invalidateGotFocusEvents];
2347 event = macdrv_create_event(APP_DEACTIVATED, nil);
2349 [eventQueuesLock lock];
2350 for (queue in eventQueues)
2351 [queue postEvent:event];
2352 [eventQueuesLock unlock];
2354 macdrv_release_event(event);
2356 [self releaseMouseCapture];
2359 - (void) applicationDidUnhide:(NSNotification*)aNotification
2361 [self adjustWindowLevels];
2364 - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2366 // Note that "flag" is often wrong. WineWindows are NSPanels and NSPanels
2367 // don't count as "visible windows" for this purpose.
2368 [self unminimizeWindowIfNoneVisible];
2372 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2374 NSApplicationTerminateReply ret = NSTerminateNow;
2375 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2376 NSAppleEventDescriptor* desc = [m currentAppleEvent];
2377 macdrv_event* event;
2378 WineEventQueue* queue;
2380 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2382 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2385 case kAEReallyLogOut:
2386 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2388 case kAEShowRestartDialog:
2389 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2391 case kAEShowShutdownDialog:
2392 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2395 event->app_quit_requested.reason = QUIT_REASON_NONE;
2399 [eventQueuesLock lock];
2401 if ([eventQueues count])
2403 for (queue in eventQueues)
2404 [queue postEvent:event];
2405 ret = NSTerminateLater;
2408 [eventQueuesLock unlock];
2410 macdrv_release_event(event);
2415 - (void)applicationWillBecomeActive:(NSNotification *)notification
2417 macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2420 [eventQueuesLock lock];
2421 for (WineEventQueue* queue in eventQueues)
2422 [queue postEvent:event];
2423 [eventQueuesLock unlock];
2425 macdrv_release_event(event);
2428 - (void)applicationWillResignActive:(NSNotification *)notification
2430 [self adjustWindowLevels:NO];
2433 /***********************************************************************
2436 * Run-loop-source perform callback. Pull request blocks from the
2437 * array of queued requests and invoke them.
2439 static void PerformRequest(void *info)
2441 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2442 WineApplicationController* controller = [WineApplicationController sharedController];
2446 __block dispatch_block_t block;
2448 dispatch_sync(controller->requestsManipQueue, ^{
2449 if ([controller->requests count])
2451 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2452 [controller->requests removeObjectAtIndex:0];
2465 pool = [[NSAutoreleasePool alloc] init];
2471 /***********************************************************************
2474 * Run a block on the main thread asynchronously.
2476 void OnMainThreadAsync(dispatch_block_t block)
2478 WineApplicationController* controller = [WineApplicationController sharedController];
2480 block = [block copy];
2481 dispatch_sync(controller->requestsManipQueue, ^{
2482 [controller->requests addObject:block];
2485 CFRunLoopSourceSignal(controller->requestSource);
2486 CFRunLoopWakeUp(CFRunLoopGetMain());
2491 /***********************************************************************
2494 void LogError(const char* func, NSString* format, ...)
2497 va_start(args, format);
2498 LogErrorv(func, format, args);
2502 /***********************************************************************
2505 void LogErrorv(const char* func, NSString* format, va_list args)
2507 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2509 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2510 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2516 /***********************************************************************
2517 * macdrv_window_rejected_focus
2519 * Pass focus to the next window that hasn't already rejected this same
2520 * WINDOW_GOT_FOCUS event.
2522 void macdrv_window_rejected_focus(const macdrv_event *event)
2525 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2529 /***********************************************************************
2530 * macdrv_get_input_source_info
2532 * Returns the keyboard layout uchr data, keyboard type and input source.
2534 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2537 TISInputSourceRef inputSourceLayout;
2539 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2540 if (inputSourceLayout)
2542 CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2543 kTISPropertyUnicodeKeyLayoutData);
2544 *uchr = CFDataCreateCopy(NULL, data);
2545 CFRelease(inputSourceLayout);
2547 *keyboard_type = [WineApplicationController sharedController].keyboardType;
2548 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2550 *input_source = TISCopyCurrentKeyboardInputSource();
2555 /***********************************************************************
2558 * Play the beep sound configured by the user in System Preferences.
2560 void macdrv_beep(void)
2562 OnMainThreadAsync(^{
2567 /***********************************************************************
2568 * macdrv_set_display_mode
2570 int macdrv_set_display_mode(const struct macdrv_display* display,
2571 CGDisplayModeRef display_mode)
2576 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2582 /***********************************************************************
2587 * If name is non-NULL, it is a selector for a class method on NSCursor
2588 * identifying the cursor to set. In that case, frames is ignored. If
2589 * name is NULL, then frames is used.
2591 * frames is an array of dictionaries. Each dictionary is a frame of
2592 * an animated cursor. Under the key "image" is a CGImage for the
2593 * frame. Under the key "duration" is a CFNumber time interval, in
2594 * seconds, for how long that frame is presented before proceeding to
2595 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
2596 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2597 * This is the hot spot, measured in pixels down and to the right of the
2598 * top-left corner of the image.
2600 * If the array has exactly 1 element, the cursor is static, not
2601 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
2603 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2607 sel = NSSelectorFromString((NSString*)name);
2610 OnMainThreadAsync(^{
2611 WineApplicationController* controller = [WineApplicationController sharedController];
2612 [controller setCursorWithFrames:nil];
2613 controller.cursor = [NSCursor performSelector:sel];
2614 [controller unhideCursor];
2619 NSArray* nsframes = (NSArray*)frames;
2620 if ([nsframes count])
2622 OnMainThreadAsync(^{
2623 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2628 OnMainThreadAsync(^{
2629 WineApplicationController* controller = [WineApplicationController sharedController];
2630 [controller setCursorWithFrames:nil];
2631 [controller hideCursor];
2637 /***********************************************************************
2638 * macdrv_get_cursor_position
2640 * Obtains the current cursor position. Returns zero on failure,
2641 * non-zero on success.
2643 int macdrv_get_cursor_position(CGPoint *pos)
2646 NSPoint location = [NSEvent mouseLocation];
2647 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2648 *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2654 /***********************************************************************
2655 * macdrv_set_cursor_position
2657 * Sets the cursor position without generating events. Returns zero on
2658 * failure, non-zero on success.
2660 int macdrv_set_cursor_position(CGPoint pos)
2665 ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2671 /***********************************************************************
2672 * macdrv_clip_cursor
2674 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
2675 * to or larger than the whole desktop region, the cursor is unclipped.
2676 * Returns zero on failure, non-zero on success.
2678 int macdrv_clip_cursor(CGRect r)
2683 WineApplicationController* controller = [WineApplicationController sharedController];
2684 BOOL clipping = FALSE;
2687 if (!CGRectIsInfinite(rect))
2688 rect = cgrect_mac_from_win(rect);
2690 if (!CGRectIsInfinite(rect))
2692 NSRect nsrect = NSRectFromCGRect(rect);
2695 /* Convert the rectangle from top-down coords to bottom-up. */
2696 [controller flipRect:&nsrect];
2699 for (screen in [NSScreen screens])
2701 if (!NSContainsRect(nsrect, [screen frame]))
2710 ret = [controller startClippingCursor:rect];
2712 ret = [controller stopClippingCursor];
2718 /***********************************************************************
2719 * macdrv_set_application_icon
2721 * Set the application icon. The images array contains CGImages. If
2722 * there are more than one, then they represent different sizes or
2723 * color depths from the icon resource. If images is NULL or empty,
2724 * restores the default application image.
2726 void macdrv_set_application_icon(CFArrayRef images)
2728 NSArray* imageArray = (NSArray*)images;
2730 OnMainThreadAsync(^{
2731 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2735 /***********************************************************************
2738 void macdrv_quit_reply(int reply)
2741 [NSApp replyToApplicationShouldTerminate:reply];
2745 /***********************************************************************
2746 * macdrv_using_input_method
2748 int macdrv_using_input_method(void)
2753 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2759 /***********************************************************************
2760 * macdrv_set_mouse_capture_window
2762 void macdrv_set_mouse_capture_window(macdrv_window window)
2764 WineWindow* w = (WineWindow*)window;
2766 [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2769 [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2773 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2774 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2775 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2777 /***********************************************************************
2778 * macdrv_create_input_source_list
2780 CFArrayRef macdrv_create_input_source_list(void)
2782 CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2785 CFArrayRef input_list;
2786 CFDictionaryRef filter_dict;
2787 const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2788 const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2791 filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2792 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2793 input_list = TISCreateInputSourceList(filter_dict, false);
2795 for (i = 0; i < CFArrayGetCount(input_list); i++)
2797 TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2798 CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2799 CFDictionaryRef entry;
2800 const void *input_keys[3] = { macdrv_input_source_input_key,
2801 macdrv_input_source_type_key,
2802 macdrv_input_source_lang_key };
2803 const void *input_values[3];
2805 input_values[0] = input;
2806 input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2807 input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2809 entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2810 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2812 CFArrayAppendValue(ret, entry);
2815 CFRelease(input_list);
2816 CFRelease(filter_dict);
2822 int macdrv_select_input_source(TISInputSourceRef input_source)
2824 __block int ret = FALSE;
2827 ret = (TISSelectInputSource(input_source) == noErr);
2833 void macdrv_set_cocoa_retina_mode(int new_mode)
2836 [[WineApplicationController sharedController] setRetinaMode:new_mode];