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 if (&kCGDisplayShowDuplicateLowResolutionModes != NULL)
805 options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:TRUE]
806 forKey:(NSString*)kCGDisplayShowDuplicateLowResolutionModes];
809 NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
810 for (id candidateModeObject in modes)
812 CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
813 if ([self mode:candidateMode matchesMode:mode])
814 [ret addObject:candidateModeObject];
819 - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
822 NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
823 CGDisplayModeRef originalMode;
825 originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
827 if (originalMode && [self mode:mode matchesMode:originalMode])
829 if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
831 CGRestorePermanentDisplayConfiguration();
832 if (!displaysCapturedForFullscreen)
833 CGReleaseAllDisplays();
834 [originalDisplayModes removeAllObjects];
837 else // ... otherwise, try to restore just the one display
839 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
841 mode = (CGDisplayModeRef)modeObject;
842 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
844 [originalDisplayModes removeObjectForKey:displayIDKey];
853 CGDisplayModeRef currentMode;
856 currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
858 currentMode = CGDisplayCopyDisplayMode(displayID);
859 if (!currentMode) // Invalid display ID
862 if ([self mode:mode matchesMode:currentMode]) // Already there!
864 CGDisplayModeRelease(currentMode);
868 CGDisplayModeRelease(currentMode);
871 modes = [self modesMatchingMode:mode forDisplay:displayID];
875 [self transformProcessToForeground];
877 BOOL active = [NSApp isActive];
879 if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
880 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
884 // If we get here, we have the displays captured. If we don't
885 // know the original mode of the display, the current mode must
886 // be the original. We should re-query the current mode since
887 // another process could have changed it between when we last
888 // checked and when we captured the displays.
890 originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
894 for (id modeObject in modes)
896 mode = (CGDisplayModeRef)modeObject;
897 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
904 if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
905 [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
906 else if (![originalDisplayModes count])
908 CGRestorePermanentDisplayConfiguration();
909 if (!displaysCapturedForFullscreen)
910 CGReleaseAllDisplays();
914 CGDisplayModeRelease(currentMode);
918 [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
925 [self adjustWindowLevels];
930 - (BOOL) areDisplaysCaptured
932 return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
935 - (void) updateCursor:(BOOL)force
937 if (force || lastTargetWindow)
939 if (clientWantsCursorHidden && !cursorHidden)
945 if (!cursorIsCurrent)
948 cursorIsCurrent = TRUE;
951 if (!clientWantsCursorHidden && cursorHidden)
954 cursorHidden = FALSE;
961 [[NSCursor arrowCursor] set];
962 cursorIsCurrent = FALSE;
967 cursorHidden = FALSE;
974 if (!clientWantsCursorHidden)
976 clientWantsCursorHidden = TRUE;
977 [self updateCursor:TRUE];
981 - (void) unhideCursor
983 if (clientWantsCursorHidden)
985 clientWantsCursorHidden = FALSE;
986 [self updateCursor:FALSE];
990 - (void) setCursor:(NSCursor*)newCursor
992 if (newCursor != cursor)
995 cursor = [newCursor retain];
996 cursorIsCurrent = FALSE;
997 [self updateCursor:FALSE];
1003 NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
1004 CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
1005 CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
1006 NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
1007 CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
1010 if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
1011 hotSpot = CGPointZero;
1012 hotSpot = cgpoint_mac_from_win(hotSpot);
1013 self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1015 [self unhideCursor];
1018 - (void) nextCursorFrame:(NSTimer*)theTimer
1020 NSDictionary* frame;
1021 NSTimeInterval duration;
1025 if (cursorFrame >= [cursorFrames count])
1029 frame = [cursorFrames objectAtIndex:cursorFrame];
1030 duration = [[frame objectForKey:@"duration"] doubleValue];
1031 date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1032 [cursorTimer setFireDate:date];
1035 - (void) setCursorWithFrames:(NSArray*)frames
1037 if (self.cursorFrames == frames)
1040 self.cursorFrames = frames;
1042 [cursorTimer invalidate];
1043 self.cursorTimer = nil;
1047 if ([frames count] > 1)
1049 NSDictionary* frame = [frames objectAtIndex:0];
1050 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
1051 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1052 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1055 selector:@selector(nextCursorFrame:)
1057 repeats:YES] autorelease];
1058 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1065 - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1067 NSImage* nsimage = nil;
1071 NSSize bestSize = NSZeroSize;
1074 nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1076 for (image in images)
1078 CGImageRef cgimage = (CGImageRef)image;
1079 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1082 NSSize size = [imageRep size];
1084 [nsimage addRepresentation:imageRep];
1087 if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1092 if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1093 [nsimage setSize:bestSize];
1098 self.applicationIcon = nsimage;
1101 - (void) handleCommandTab
1103 if ([NSApp isActive])
1105 NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1106 NSRunningApplication* app;
1107 NSRunningApplication* otherValidApp = nil;
1109 if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1111 NSNumber* displayID;
1112 for (displayID in originalDisplayModes)
1114 CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1115 [latentDisplayModes setObject:(id)mode forKey:displayID];
1116 CGDisplayModeRelease(mode);
1119 CGRestorePermanentDisplayConfiguration();
1120 CGReleaseAllDisplays();
1121 [originalDisplayModes removeAllObjects];
1122 displaysCapturedForFullscreen = FALSE;
1125 for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1127 if (![app isEqual:thisApp] && !app.terminated &&
1128 app.activationPolicy == NSApplicationActivationPolicyRegular)
1132 // There's another visible app. Just hide ourselves and let
1133 // the system activate the other app.
1139 otherValidApp = app;
1143 // Didn't find a visible GUI app. Try the Finder or, if that's not
1144 // running, the first hidden GUI app. If even that doesn't work, we
1145 // just fail to switch and remain the active app.
1146 app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1147 if (!app) app = otherValidApp;
1149 [app activateWithOptions:0];
1154 * ---------- Cursor clipping methods ----------
1156 * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
1157 * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
1158 * equivalent: CGAssociateMouseAndMouseCursorPosition(false). For the
1159 * general case, we leverage that. We disassociate mouse movements from
1160 * the cursor position and then move the cursor manually, keeping it within
1161 * the clipping rectangle.
1163 * Moving the cursor manually isn't enough. We need to modify the event
1164 * stream so that the events have the new location, too. We need to do
1165 * this at a point before the events enter Cocoa, so that Cocoa will assign
1166 * the correct window to the event. So, we install a Quartz event tap to
1169 * Also, there's a complication when we move the cursor. We use
1170 * CGWarpMouseCursorPosition(). That doesn't generate mouse movement
1171 * events, but the change of cursor position is incorporated into the
1172 * deltas of the next mouse move event. When the mouse is disassociated
1173 * from the cursor position, we need the deltas to only reflect actual
1174 * device movement, not programmatic changes. So, the event tap cancels
1175 * out the change caused by our calls to CGWarpMouseCursorPosition().
1177 - (void) clipCursorLocation:(CGPoint*)location
1179 if (location->x < CGRectGetMinX(cursorClipRect))
1180 location->x = CGRectGetMinX(cursorClipRect);
1181 if (location->y < CGRectGetMinY(cursorClipRect))
1182 location->y = CGRectGetMinY(cursorClipRect);
1183 if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
1184 location->x = CGRectGetMaxX(cursorClipRect) - 1;
1185 if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
1186 location->y = CGRectGetMaxY(cursorClipRect) - 1;
1189 - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
1191 CGPoint oldLocation;
1193 if (currentLocation)
1194 oldLocation = *currentLocation;
1196 oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1198 if (!CGPointEqualToPoint(oldLocation, *newLocation))
1200 WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
1203 warpRecord.from = oldLocation;
1204 warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1206 /* Actually move the cursor. */
1207 err = CGWarpMouseCursorPosition(*newLocation);
1208 if (err != kCGErrorSuccess)
1211 warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
1212 *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1214 if (!CGPointEqualToPoint(oldLocation, *newLocation))
1216 warpRecord.to = *newLocation;
1217 [warpRecords addObject:warpRecord];
1224 - (BOOL) isMouseMoveEventType:(CGEventType)type
1228 case kCGEventMouseMoved:
1229 case kCGEventLeftMouseDragged:
1230 case kCGEventRightMouseDragged:
1231 case kCGEventOtherMouseDragged:
1238 - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
1240 int warpsFinished = 0;
1241 for (WarpRecord* warpRecord in warpRecords)
1243 if (warpRecord.timeAfter < eventTime ||
1244 (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
1250 return warpsFinished;
1253 - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
1254 type:(CGEventType)type
1255 event:(CGEventRef)event
1257 CGEventTimestamp eventTime;
1258 CGPoint eventLocation, cursorLocation;
1260 if (type == kCGEventTapDisabledByUserInput)
1262 if (type == kCGEventTapDisabledByTimeout)
1264 CGEventTapEnable(cursorClippingEventTap, TRUE);
1268 if (!clippingCursor)
1271 eventTime = CGEventGetTimestamp(event);
1272 lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
1274 eventLocation = CGEventGetLocation(event);
1276 cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
1278 if ([self isMouseMoveEventType:type])
1280 double deltaX, deltaY;
1281 int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
1284 deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
1285 deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
1287 for (i = 0; i < warpsFinished; i++)
1289 WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
1290 deltaX -= warpRecord.to.x - warpRecord.from.x;
1291 deltaY -= warpRecord.to.y - warpRecord.from.y;
1292 [warpRecords removeObjectAtIndex:0];
1297 CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
1298 CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
1301 synthesizedLocation.x += deltaX;
1302 synthesizedLocation.y += deltaY;
1305 // If the event is destined for another process, don't clip it. This may
1306 // happen if the user activates Exposé or Mission Control. In that case,
1307 // our app does not resign active status, so clipping is still in effect,
1308 // but the cursor should not actually be clipped.
1310 // In addition, the fact that mouse moves may have been delivered to a
1311 // different process means we have to treat the next one we receive as
1312 // absolute rather than relative.
1313 if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
1314 [self clipCursorLocation:&synthesizedLocation];
1316 lastSetCursorPositionTime = lastEventTapEventTime;
1318 [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
1319 if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
1320 CGEventSetLocation(event, synthesizedLocation);
1325 CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
1326 CGEventRef event, void *refcon)
1328 WineApplicationController* controller = refcon;
1329 return [controller eventTapWithProxy:proxy type:type event:event];
1332 - (BOOL) installEventTap
1334 ProcessSerialNumber psn;
1336 CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown) |
1337 CGEventMaskBit(kCGEventLeftMouseUp) |
1338 CGEventMaskBit(kCGEventRightMouseDown) |
1339 CGEventMaskBit(kCGEventRightMouseUp) |
1340 CGEventMaskBit(kCGEventMouseMoved) |
1341 CGEventMaskBit(kCGEventLeftMouseDragged) |
1342 CGEventMaskBit(kCGEventRightMouseDragged) |
1343 CGEventMaskBit(kCGEventOtherMouseDown) |
1344 CGEventMaskBit(kCGEventOtherMouseUp) |
1345 CGEventMaskBit(kCGEventOtherMouseDragged) |
1346 CGEventMaskBit(kCGEventScrollWheel);
1347 CFRunLoopSourceRef source;
1349 OSErr (*pGetCurrentProcess)(ProcessSerialNumber* PSN);
1351 if (cursorClippingEventTap)
1354 // We need to get the Mac GetCurrentProcess() from the ApplicationServices
1355 // framework with dlsym() because the Win32 function of the same name
1357 appServices = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
1361 pGetCurrentProcess = dlsym(appServices, "GetCurrentProcess");
1362 if (!pGetCurrentProcess)
1364 dlclose(appServices);
1368 err = pGetCurrentProcess(&psn);
1369 dlclose(appServices);
1373 // We create an annotated session event tap rather than a process-specific
1374 // event tap because we need to programmatically move the cursor even when
1375 // mouse moves are directed to other processes. We disable our tap when
1376 // other processes are active, but things like Exposé are handled by other
1377 // processes even when we remain active.
1378 cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
1379 kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
1380 if (!cursorClippingEventTap)
1383 CGEventTapEnable(cursorClippingEventTap, FALSE);
1385 source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
1388 CFRelease(cursorClippingEventTap);
1389 cursorClippingEventTap = NULL;
1393 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
1398 - (BOOL) setCursorPosition:(CGPoint)pos
1402 if ([windowsBeingDragged count])
1404 else if (clippingCursor)
1406 [self clipCursorLocation:&pos];
1408 ret = [self warpCursorTo:&pos from:NULL];
1409 synthesizedLocation = pos;
1412 // We want to discard mouse-move events that have already been
1413 // through the event tap, because it's too late to account for
1414 // the setting of the cursor position with them. However, the
1415 // events that may be queued with times after that but before
1416 // the above warp can still be used. So, use the last event
1417 // tap event time so that -sendEvent: doesn't discard them.
1418 lastSetCursorPositionTime = lastEventTapEventTime;
1423 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1424 // the mouse from the cursor position for 0.25 seconds. This means
1425 // that mouse movement during that interval doesn't move the cursor
1426 // and events carry a constant location (the warped-to position)
1427 // even though they have delta values. For apps which warp the
1428 // cursor frequently (like after every mouse move), this makes
1429 // cursor movement horribly laggy and jerky, as only a fraction of
1430 // mouse move events have any effect.
1432 // On some versions of OS X, it's sufficient to forcibly reassociate
1433 // the mouse and cursor position. On others, it's necessary to set
1434 // the local events suppression interval to 0 for the warp. That's
1435 // deprecated, but I'm not aware of any other way. For good
1436 // measure, we do both.
1437 CGSetLocalEventsSuppressionInterval(0);
1438 ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1439 CGSetLocalEventsSuppressionInterval(0.25);
1442 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1444 CGAssociateMouseAndMouseCursorPosition(true);
1450 WineEventQueue* queue;
1452 // Discard all pending mouse move events.
1453 [eventQueuesLock lock];
1454 for (queue in eventQueues)
1456 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED) |
1457 event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1459 [queue resetMouseEventPositions:pos];
1461 [eventQueuesLock unlock];
1467 - (void) activateCursorClipping
1469 if (cursorClippingEventTap && !CGEventTapIsEnabled(cursorClippingEventTap))
1471 CGEventTapEnable(cursorClippingEventTap, TRUE);
1472 [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1476 - (void) deactivateCursorClipping
1478 if (cursorClippingEventTap && CGEventTapIsEnabled(cursorClippingEventTap))
1480 CGEventTapEnable(cursorClippingEventTap, FALSE);
1481 [warpRecords removeAllObjects];
1482 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1486 - (void) updateCursorClippingState
1488 if (clippingCursor && [NSApp isActive] && ![windowsBeingDragged count])
1489 [self activateCursorClipping];
1491 [self deactivateCursorClipping];
1494 - (void) updateWindowsForCursorClipping
1497 for (window in [NSApp windows])
1499 if ([window isKindOfClass:[WineWindow class]])
1500 [window updateForCursorClipping];
1504 - (BOOL) startClippingCursor:(CGRect)rect
1508 if (!cursorClippingEventTap && ![self installEventTap])
1511 if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect) &&
1512 CGEventTapIsEnabled(cursorClippingEventTap))
1515 err = CGAssociateMouseAndMouseCursorPosition(false);
1516 if (err != kCGErrorSuccess)
1519 clippingCursor = TRUE;
1520 cursorClipRect = rect;
1521 [self updateCursorClippingState];
1522 [self updateWindowsForCursorClipping];
1527 - (BOOL) stopClippingCursor
1529 CGError err = CGAssociateMouseAndMouseCursorPosition(true);
1530 if (err != kCGErrorSuccess)
1533 clippingCursor = FALSE;
1534 [self updateCursorClippingState];
1535 [self updateWindowsForCursorClipping];
1540 - (BOOL) isKeyPressed:(uint16_t)keyCode
1542 int bits = sizeof(pressedKeyCodes[0]) * 8;
1543 int index = keyCode / bits;
1544 uint32_t mask = 1 << (keyCode % bits);
1545 return (pressedKeyCodes[index] & mask) != 0;
1548 - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1550 int bits = sizeof(pressedKeyCodes[0]) * 8;
1551 int index = keyCode / bits;
1552 uint32_t mask = 1 << (keyCode % bits);
1554 pressedKeyCodes[index] |= mask;
1556 pressedKeyCodes[index] &= ~mask;
1559 - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1562 [windowsBeingDragged addObject:window];
1564 [windowsBeingDragged removeObject:window];
1565 [self updateCursorClippingState];
1568 - (void) windowWillOrderOut:(WineWindow*)window
1570 if ([windowsBeingDragged containsObject:window])
1572 [self window:window isBeingDragged:NO];
1574 macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1575 [window.queue postEvent:event];
1576 macdrv_release_event(event);
1580 - (void) handleMouseMove:(NSEvent*)anEvent
1582 WineWindow* targetWindow;
1583 BOOL drag = [anEvent type] != NSMouseMoved;
1585 if ([windowsBeingDragged count])
1587 else if (mouseCaptureWindow)
1588 targetWindow = mouseCaptureWindow;
1590 targetWindow = (WineWindow*)[anEvent window];
1593 /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1594 event indicates its window is the main window, even if the cursor is
1595 over a different window. Find the actual WineWindow that is under the
1596 cursor and post the event as being for that window. */
1597 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1598 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1599 NSInteger windowUnderNumber;
1601 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1602 belowWindowWithWindowNumber:0];
1603 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1604 if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1608 if ([targetWindow isKindOfClass:[WineWindow class]])
1610 CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1611 macdrv_event* event;
1614 // If we recently warped the cursor (other than in our cursor-clipping
1615 // event tap), discard mouse move events until we see an event which is
1616 // later than that time.
1617 if (lastSetCursorPositionTime)
1619 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1622 lastSetCursorPositionTime = 0;
1623 forceNextMouseMoveAbsolute = TRUE;
1626 if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1629 forceNextMouseMoveAbsolute = FALSE;
1633 // Send absolute move events if the cursor is in the interior of
1634 // its range. Only send relative moves if the cursor is pinned to
1635 // the boundaries of where it can go. We compute the position
1636 // that's one additional point in the direction of movement. If
1637 // that is outside of the clipping rect or desktop region (the
1638 // union of the screen frames), then we figure the cursor would
1639 // have moved outside if it could but it was pinned.
1640 CGPoint computedPoint = point;
1641 CGFloat deltaX = [anEvent deltaX];
1642 CGFloat deltaY = [anEvent deltaY];
1646 else if (deltaX < -0.001)
1651 else if (deltaY < -0.001)
1654 // Assume cursor is pinned for now
1656 if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
1658 const CGRect* rects;
1659 NSUInteger count, i;
1661 // Caches screenFrameCGRects if necessary
1662 [self primaryScreenHeight];
1664 rects = [screenFrameCGRects bytes];
1665 count = [screenFrameCGRects length] / sizeof(rects[0]);
1667 for (i = 0; i < count; i++)
1669 if (CGRectContainsPoint(rects[i], computedPoint))
1681 [self clipCursorLocation:&point];
1682 point = cgpoint_win_from_mac(point);
1684 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1685 event->mouse_moved.x = floor(point.x);
1686 event->mouse_moved.y = floor(point.y);
1688 mouseMoveDeltaX = 0;
1689 mouseMoveDeltaY = 0;
1693 double scale = retina_on ? 2 : 1;
1695 /* Add event delta to accumulated delta error */
1696 /* deltaY is already flipped */
1697 mouseMoveDeltaX += [anEvent deltaX];
1698 mouseMoveDeltaY += [anEvent deltaY];
1700 event = macdrv_create_event(MOUSE_MOVED, targetWindow);
1701 event->mouse_moved.x = mouseMoveDeltaX * scale;
1702 event->mouse_moved.y = mouseMoveDeltaY * scale;
1704 /* Keep the remainder after integer truncation. */
1705 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1706 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1709 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1711 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1712 event->mouse_moved.drag = drag;
1714 [targetWindow.queue postEvent:event];
1717 macdrv_release_event(event);
1719 lastTargetWindow = targetWindow;
1722 lastTargetWindow = nil;
1724 [self updateCursor:FALSE];
1727 - (void) handleMouseButton:(NSEvent*)theEvent
1729 WineWindow* window = (WineWindow*)[theEvent window];
1730 NSEventType type = [theEvent type];
1731 WineWindow* windowBroughtForward = nil;
1732 BOOL process = FALSE;
1734 if ([window isKindOfClass:[WineWindow class]] &&
1735 type == NSLeftMouseDown &&
1736 ![theEvent wine_commandKeyDown])
1738 NSWindowButton windowButton;
1740 windowBroughtForward = window;
1742 /* Any left-click on our window anyplace other than the close or
1743 minimize buttons will bring it forward. */
1744 for (windowButton = NSWindowCloseButton;
1745 windowButton <= NSWindowMiniaturizeButton;
1748 NSButton* button = [window standardWindowButton:windowButton];
1751 NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1752 if ([button mouse:point inRect:[button bounds]])
1754 windowBroughtForward = nil;
1761 if ([windowsBeingDragged count])
1763 else if (mouseCaptureWindow)
1764 window = mouseCaptureWindow;
1766 if ([window isKindOfClass:[WineWindow class]])
1768 BOOL pressed = (type == NSLeftMouseDown || type == NSRightMouseDown || type == NSOtherMouseDown);
1769 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1772 [self clipCursorLocation:&pt];
1776 if (mouseCaptureWindow)
1780 // Test if the click was in the window's content area.
1781 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1782 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1783 process = NSMouseInRect(nspoint, contentRect, NO);
1784 if (process && [window styleMask] & NSResizableWindowMask)
1786 // Ignore clicks in the grow box (resize widget).
1787 HIPoint origin = { 0, 0 };
1788 HIThemeGrowBoxDrawInfo info = { 0 };
1792 info.kind = kHIThemeGrowBoxKindNormal;
1793 info.direction = kThemeGrowRight | kThemeGrowDown;
1794 if ([window styleMask] & NSUtilityWindowMask)
1795 info.size = kHIThemeGrowBoxSizeSmall;
1797 info.size = kHIThemeGrowBoxSizeNormal;
1799 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1800 if (status == noErr)
1802 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1803 NSMinY(contentRect),
1805 bounds.size.height);
1806 process = !NSMouseInRect(nspoint, growBox, NO);
1811 unmatchedMouseDowns |= NSEventMaskFromType(type);
1815 NSEventType downType = type - 1;
1816 NSUInteger downMask = NSEventMaskFromType(downType);
1817 process = (unmatchedMouseDowns & downMask) != 0;
1818 unmatchedMouseDowns &= ~downMask;
1823 macdrv_event* event;
1825 pt = cgpoint_win_from_mac(pt);
1827 event = macdrv_create_event(MOUSE_BUTTON, window);
1828 event->mouse_button.button = [theEvent buttonNumber];
1829 event->mouse_button.pressed = pressed;
1830 event->mouse_button.x = floor(pt.x);
1831 event->mouse_button.y = floor(pt.y);
1832 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1834 [window.queue postEvent:event];
1836 macdrv_release_event(event);
1840 if (windowBroughtForward)
1842 WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1843 NSInteger ancestorNumber = [ancestor windowNumber];
1844 NSInteger ancestorLevel = [ancestor level];
1846 for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1848 NSInteger windowNumber = [windowNumberObject integerValue];
1849 if (windowNumber == ancestorNumber)
1851 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1852 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1853 [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1855 [ancestor postBroughtForwardEvent];
1859 if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noActivate)
1860 [self windowGotFocus:windowBroughtForward];
1863 // Since mouse button events deliver absolute cursor position, the
1864 // accumulating delta from move events is invalidated. Make sure
1865 // next mouse move event starts over from an absolute baseline.
1866 // Also, it's at least possible that the title bar widgets (e.g. close
1867 // button, etc.) could enter an internal event loop on a mouse down that
1868 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1869 // dragged events and, after that, any notion of the cursor position
1870 // computed from accumulating deltas would be wrong.
1871 forceNextMouseMoveAbsolute = TRUE;
1874 - (void) handleScrollWheel:(NSEvent*)theEvent
1878 if (mouseCaptureWindow)
1879 window = mouseCaptureWindow;
1881 window = (WineWindow*)[theEvent window];
1883 if ([window isKindOfClass:[WineWindow class]])
1885 CGEventRef cgevent = [theEvent CGEvent];
1886 CGPoint pt = CGEventGetLocation(cgevent);
1890 [self clipCursorLocation:&pt];
1892 if (mouseCaptureWindow)
1896 // Only process the event if it was in the window's content area.
1897 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1898 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1899 process = NSMouseInRect(nspoint, contentRect, NO);
1904 macdrv_event* event;
1906 BOOL continuous = FALSE;
1908 pt = cgpoint_win_from_mac(pt);
1910 event = macdrv_create_event(MOUSE_SCROLL, window);
1911 event->mouse_scroll.x = floor(pt.x);
1912 event->mouse_scroll.y = floor(pt.y);
1913 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1915 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1919 /* Continuous scroll wheel events come from high-precision scrolling
1920 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1921 For these, we can get more precise data from the CGEvent API. */
1922 /* Axis 1 is vertical, axis 2 is horizontal. */
1923 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1924 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1928 double pixelsPerLine = 10;
1929 CGEventSourceRef source;
1931 /* The non-continuous values are in units of "lines", not pixels. */
1932 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1934 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1938 x = pixelsPerLine * [theEvent deltaX];
1939 y = pixelsPerLine * [theEvent deltaY];
1942 /* Mac: negative is right or down, positive is left or up.
1943 Win32: negative is left or down, positive is right or up.
1944 So, negate the X scroll value to translate. */
1947 /* The x,y values so far are in pixels. Win32 expects to receive some
1948 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1949 6 times the pixel value. */
1953 if (use_precise_scrolling)
1955 event->mouse_scroll.x_scroll = x;
1956 event->mouse_scroll.y_scroll = y;
1960 /* For non-continuous "clicky" wheels, if there was any motion, make
1961 sure there was at least WHEEL_DELTA motion. This is so, at slow
1962 speeds where the system's acceleration curve is actually reducing the
1963 scroll distance, the user is sure to get some action out of each click.
1964 For example, this is important for rotating though weapons in a
1965 first-person shooter. */
1966 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1967 event->mouse_scroll.x_scroll = 120;
1968 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1969 event->mouse_scroll.x_scroll = -120;
1971 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1972 event->mouse_scroll.y_scroll = 120;
1973 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1974 event->mouse_scroll.y_scroll = -120;
1979 /* If it's been a while since the last scroll event or if the scrolling has
1980 reversed direction, reset the accumulated scroll value. */
1981 if ([theEvent timestamp] - lastScrollTime > 1)
1982 accumScrollX = accumScrollY = 0;
1985 /* The accumulated scroll value is in the opposite direction/sign of the last
1986 scroll. That's because it's the "debt" resulting from over-scrolling in
1987 that direction. We accumulate by adding in the scroll amount and then, if
1988 it has the same sign as the scroll value, we subtract any whole or partial
1989 WHEEL_DELTAs, leaving it 0 or the opposite sign. So, the user switched
1990 scroll direction if the accumulated debt and the new scroll value have the
1992 if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1994 if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1997 lastScrollTime = [theEvent timestamp];
2002 if (accumScrollX > 0 && x > 0)
2003 event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
2004 if (accumScrollX < 0 && x < 0)
2005 event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
2006 if (accumScrollY > 0 && y > 0)
2007 event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
2008 if (accumScrollY < 0 && y < 0)
2009 event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
2011 accumScrollX -= event->mouse_scroll.x_scroll;
2012 accumScrollY -= event->mouse_scroll.y_scroll;
2015 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
2016 [window.queue postEvent:event];
2018 macdrv_release_event(event);
2020 // Since scroll wheel events deliver absolute cursor position, the
2021 // accumulating delta from move events is invalidated. Make sure next
2022 // mouse move event starts over from an absolute baseline.
2023 forceNextMouseMoveAbsolute = TRUE;
2028 // Returns TRUE if the event was handled and caller should do nothing more
2029 // with it. Returns FALSE if the caller should process it as normal and
2030 // then call -didSendEvent:.
2031 - (BOOL) handleEvent:(NSEvent*)anEvent
2034 NSEventType type = [anEvent type];
2036 if (type == NSFlagsChanged)
2037 self.lastFlagsChanged = anEvent;
2038 else if (type == NSMouseMoved || type == NSLeftMouseDragged ||
2039 type == NSRightMouseDragged || type == NSOtherMouseDragged)
2041 [self handleMouseMove:anEvent];
2042 ret = mouseCaptureWindow && ![windowsBeingDragged count];
2044 else if (type == NSLeftMouseDown || type == NSLeftMouseUp ||
2045 type == NSRightMouseDown || type == NSRightMouseUp ||
2046 type == NSOtherMouseDown || type == NSOtherMouseUp)
2048 [self handleMouseButton:anEvent];
2049 ret = mouseCaptureWindow && ![windowsBeingDragged count];
2051 else if (type == NSScrollWheel)
2053 [self handleScrollWheel:anEvent];
2054 ret = mouseCaptureWindow != nil;
2056 else if (type == NSKeyDown)
2058 // -[NSApplication sendEvent:] seems to consume presses of the Help
2059 // key (Insert key on PC keyboards), so we have to bypass it and
2060 // send the event directly to the window.
2061 if (anEvent.keyCode == kVK_Help)
2063 [anEvent.window sendEvent:anEvent];
2067 else if (type == NSKeyUp)
2069 uint16_t keyCode = [anEvent keyCode];
2070 if ([self isKeyPressed:keyCode])
2072 WineWindow* window = (WineWindow*)[anEvent window];
2073 [self noteKey:keyCode pressed:FALSE];
2074 if ([window isKindOfClass:[WineWindow class]])
2075 [window postKeyEvent:anEvent];
2078 else if (type == NSAppKitDefined)
2080 short subtype = [anEvent subtype];
2082 // These subtypes are not documented but they appear to mean
2083 // "a window is being dragged" and "a window is no longer being
2084 // dragged", respectively.
2085 if (subtype == 20 || subtype == 21)
2087 WineWindow* window = (WineWindow*)[anEvent window];
2088 if ([window isKindOfClass:[WineWindow class]])
2090 macdrv_event* event;
2095 [windowsBeingDragged addObject:window];
2096 eventType = WINDOW_DRAG_BEGIN;
2100 [windowsBeingDragged removeObject:window];
2101 eventType = WINDOW_DRAG_END;
2103 [self updateCursorClippingState];
2105 event = macdrv_create_event(eventType, window);
2106 if (eventType == WINDOW_DRAG_BEGIN)
2107 event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
2108 [window.queue postEvent:event];
2109 macdrv_release_event(event);
2117 - (void) didSendEvent:(NSEvent*)anEvent
2119 NSEventType type = [anEvent type];
2121 if (type == NSKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
2123 NSUInteger modifiers = [anEvent modifierFlags];
2124 if ((modifiers & NSCommandKeyMask) &&
2125 !(modifiers & (NSControlKeyMask | NSAlternateKeyMask)))
2127 // Command-Tab and Command-Shift-Tab would normally be intercepted
2128 // by the system to switch applications. If we're seeing it, it's
2129 // presumably because we've captured the displays, preventing
2130 // normal application switching. Do it manually.
2131 [self handleCommandTab];
2136 - (void) setupObservations
2138 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
2139 NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
2140 NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
2142 [nc addObserverForName:NSWindowDidBecomeKeyNotification
2145 usingBlock:^(NSNotification *note){
2146 NSWindow* window = [note object];
2147 [keyWindows removeObjectIdenticalTo:window];
2148 [keyWindows insertObject:window atIndex:0];
2151 [nc addObserverForName:NSWindowWillCloseNotification
2153 queue:[NSOperationQueue mainQueue]
2154 usingBlock:^(NSNotification *note){
2155 NSWindow* window = [note object];
2156 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
2158 [keyWindows removeObjectIdenticalTo:window];
2159 if (window == lastTargetWindow)
2160 lastTargetWindow = nil;
2161 if (window == self.mouseCaptureWindow)
2162 self.mouseCaptureWindow = nil;
2163 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
2165 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
2166 [self updateFullscreenWindows];
2169 [windowsBeingDragged removeObject:window];
2170 [self updateCursorClippingState];
2173 [nc addObserver:self
2174 selector:@selector(keyboardSelectionDidChange)
2175 name:NSTextInputContextKeyboardSelectionDidChangeNotification
2178 /* The above notification isn't sent unless the NSTextInputContext
2179 class has initialized itself. Poke it. */
2180 [NSTextInputContext self];
2182 [wsnc addObserver:self
2183 selector:@selector(activeSpaceDidChange)
2184 name:NSWorkspaceActiveSpaceDidChangeNotification
2187 [nc addObserver:self
2188 selector:@selector(releaseMouseCapture)
2189 name:NSMenuDidBeginTrackingNotification
2192 [dnc addObserver:self
2193 selector:@selector(releaseMouseCapture)
2194 name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
2196 suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
2198 [dnc addObserver:self
2199 selector:@selector(enabledKeyboardInputSourcesChanged)
2200 name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
2204 - (BOOL) inputSourceIsInputMethod
2206 if (!inputSourceIsInputMethodValid)
2208 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2211 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2212 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2213 CFRelease(inputSource);
2216 inputSourceIsInputMethod = FALSE;
2217 inputSourceIsInputMethodValid = TRUE;
2220 return inputSourceIsInputMethod;
2223 - (void) releaseMouseCapture
2225 // This might be invoked on a background thread by the distributed
2226 // notification center. Shunt it to the main thread.
2227 if (![NSThread isMainThread])
2229 dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2233 if (mouseCaptureWindow)
2235 macdrv_event* event;
2237 event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2238 [mouseCaptureWindow.queue postEvent:event];
2239 macdrv_release_event(event);
2243 - (void) unminimizeWindowIfNoneVisible
2245 if (![self frontWineWindow])
2247 for (WineWindow* window in [NSApp windows])
2249 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2251 [window deminiaturize:self];
2258 - (void) setRetinaMode:(int)mode
2264 double scale = mode ? 0.5 : 2.0;
2265 cursorClipRect.origin.x *= scale;
2266 cursorClipRect.origin.y *= scale;
2267 cursorClipRect.size.width *= scale;
2268 cursorClipRect.size.height *= scale;
2271 for (WineWindow* window in [NSApp windows])
2273 if ([window isKindOfClass:[WineWindow class]])
2274 [window setRetinaMode:mode];
2280 * ---------- NSApplicationDelegate methods ----------
2282 - (void)applicationDidBecomeActive:(NSNotification *)notification
2284 NSNumber* displayID;
2285 NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2287 latentDisplayModes = [[NSMutableDictionary alloc] init];
2288 for (displayID in modesToRealize)
2290 CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2291 [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2294 [self updateCursorClippingState];
2296 [self updateFullscreenWindows];
2297 [self adjustWindowLevels:YES];
2300 [self unminimizeWindowIfNoneVisible];
2303 // If a Wine process terminates abruptly while it has the display captured
2304 // and switched to a different resolution, Mac OS X will uncapture the
2305 // displays and switch their resolutions back. However, the other Wine
2306 // processes won't have their notion of the desktop rect changed back.
2307 // This can lead them to refuse to draw or acknowledge clicks in certain
2308 // portions of their windows.
2310 // To solve this, we synthesize a displays-changed event whenever we're
2311 // activated. This will provoke a re-synchronization of Wine's notion of
2312 // the desktop rect with the actual state.
2313 [self sendDisplaysChanged:TRUE];
2315 // The cursor probably moved while we were inactive. Accumulated mouse
2316 // movement deltas are invalidated. Make sure the next mouse move event
2317 // starts over from an absolute baseline.
2318 forceNextMouseMoveAbsolute = TRUE;
2321 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2323 primaryScreenHeightValid = FALSE;
2324 [self sendDisplaysChanged:FALSE];
2325 [self adjustWindowLevels];
2327 // When the display configuration changes, the cursor position may jump.
2328 // Accumulated mouse movement deltas are invalidated. Make sure the next
2329 // mouse move event starts over from an absolute baseline.
2330 forceNextMouseMoveAbsolute = TRUE;
2333 - (void)applicationDidResignActive:(NSNotification *)notification
2335 macdrv_event* event;
2336 WineEventQueue* queue;
2338 [self updateCursorClippingState];
2340 [self invalidateGotFocusEvents];
2342 event = macdrv_create_event(APP_DEACTIVATED, nil);
2344 [eventQueuesLock lock];
2345 for (queue in eventQueues)
2346 [queue postEvent:event];
2347 [eventQueuesLock unlock];
2349 macdrv_release_event(event);
2351 [self releaseMouseCapture];
2354 - (void) applicationDidUnhide:(NSNotification*)aNotification
2356 [self adjustWindowLevels];
2359 - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2361 // Note that "flag" is often wrong. WineWindows are NSPanels and NSPanels
2362 // don't count as "visible windows" for this purpose.
2363 [self unminimizeWindowIfNoneVisible];
2367 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2369 NSApplicationTerminateReply ret = NSTerminateNow;
2370 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2371 NSAppleEventDescriptor* desc = [m currentAppleEvent];
2372 macdrv_event* event;
2373 WineEventQueue* queue;
2375 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2377 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2380 case kAEReallyLogOut:
2381 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2383 case kAEShowRestartDialog:
2384 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2386 case kAEShowShutdownDialog:
2387 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2390 event->app_quit_requested.reason = QUIT_REASON_NONE;
2394 [eventQueuesLock lock];
2396 if ([eventQueues count])
2398 for (queue in eventQueues)
2399 [queue postEvent:event];
2400 ret = NSTerminateLater;
2403 [eventQueuesLock unlock];
2405 macdrv_release_event(event);
2410 - (void)applicationWillBecomeActive:(NSNotification *)notification
2412 macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2415 [eventQueuesLock lock];
2416 for (WineEventQueue* queue in eventQueues)
2417 [queue postEvent:event];
2418 [eventQueuesLock unlock];
2420 macdrv_release_event(event);
2423 - (void)applicationWillResignActive:(NSNotification *)notification
2425 [self adjustWindowLevels:NO];
2428 /***********************************************************************
2431 * Run-loop-source perform callback. Pull request blocks from the
2432 * array of queued requests and invoke them.
2434 static void PerformRequest(void *info)
2436 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2437 WineApplicationController* controller = [WineApplicationController sharedController];
2441 __block dispatch_block_t block;
2443 dispatch_sync(controller->requestsManipQueue, ^{
2444 if ([controller->requests count])
2446 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2447 [controller->requests removeObjectAtIndex:0];
2460 pool = [[NSAutoreleasePool alloc] init];
2466 /***********************************************************************
2469 * Run a block on the main thread asynchronously.
2471 void OnMainThreadAsync(dispatch_block_t block)
2473 WineApplicationController* controller = [WineApplicationController sharedController];
2475 block = [block copy];
2476 dispatch_sync(controller->requestsManipQueue, ^{
2477 [controller->requests addObject:block];
2480 CFRunLoopSourceSignal(controller->requestSource);
2481 CFRunLoopWakeUp(CFRunLoopGetMain());
2486 /***********************************************************************
2489 void LogError(const char* func, NSString* format, ...)
2492 va_start(args, format);
2493 LogErrorv(func, format, args);
2497 /***********************************************************************
2500 void LogErrorv(const char* func, NSString* format, va_list args)
2502 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2504 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2505 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2511 /***********************************************************************
2512 * macdrv_window_rejected_focus
2514 * Pass focus to the next window that hasn't already rejected this same
2515 * WINDOW_GOT_FOCUS event.
2517 void macdrv_window_rejected_focus(const macdrv_event *event)
2520 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2524 /***********************************************************************
2525 * macdrv_get_input_source_info
2527 * Returns the keyboard layout uchr data, keyboard type and input source.
2529 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2532 TISInputSourceRef inputSourceLayout;
2534 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2535 if (inputSourceLayout)
2537 CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2538 kTISPropertyUnicodeKeyLayoutData);
2539 *uchr = CFDataCreateCopy(NULL, data);
2540 CFRelease(inputSourceLayout);
2542 *keyboard_type = [WineApplicationController sharedController].keyboardType;
2543 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2545 *input_source = TISCopyCurrentKeyboardInputSource();
2550 /***********************************************************************
2553 * Play the beep sound configured by the user in System Preferences.
2555 void macdrv_beep(void)
2557 OnMainThreadAsync(^{
2562 /***********************************************************************
2563 * macdrv_set_display_mode
2565 int macdrv_set_display_mode(const struct macdrv_display* display,
2566 CGDisplayModeRef display_mode)
2571 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2577 /***********************************************************************
2582 * If name is non-NULL, it is a selector for a class method on NSCursor
2583 * identifying the cursor to set. In that case, frames is ignored. If
2584 * name is NULL, then frames is used.
2586 * frames is an array of dictionaries. Each dictionary is a frame of
2587 * an animated cursor. Under the key "image" is a CGImage for the
2588 * frame. Under the key "duration" is a CFNumber time interval, in
2589 * seconds, for how long that frame is presented before proceeding to
2590 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
2591 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2592 * This is the hot spot, measured in pixels down and to the right of the
2593 * top-left corner of the image.
2595 * If the array has exactly 1 element, the cursor is static, not
2596 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
2598 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2602 sel = NSSelectorFromString((NSString*)name);
2605 OnMainThreadAsync(^{
2606 WineApplicationController* controller = [WineApplicationController sharedController];
2607 [controller setCursorWithFrames:nil];
2608 controller.cursor = [NSCursor performSelector:sel];
2609 [controller unhideCursor];
2614 NSArray* nsframes = (NSArray*)frames;
2615 if ([nsframes count])
2617 OnMainThreadAsync(^{
2618 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2623 OnMainThreadAsync(^{
2624 WineApplicationController* controller = [WineApplicationController sharedController];
2625 [controller setCursorWithFrames:nil];
2626 [controller hideCursor];
2632 /***********************************************************************
2633 * macdrv_get_cursor_position
2635 * Obtains the current cursor position. Returns zero on failure,
2636 * non-zero on success.
2638 int macdrv_get_cursor_position(CGPoint *pos)
2641 NSPoint location = [NSEvent mouseLocation];
2642 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2643 *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2649 /***********************************************************************
2650 * macdrv_set_cursor_position
2652 * Sets the cursor position without generating events. Returns zero on
2653 * failure, non-zero on success.
2655 int macdrv_set_cursor_position(CGPoint pos)
2660 ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2666 /***********************************************************************
2667 * macdrv_clip_cursor
2669 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
2670 * to or larger than the whole desktop region, the cursor is unclipped.
2671 * Returns zero on failure, non-zero on success.
2673 int macdrv_clip_cursor(CGRect r)
2678 WineApplicationController* controller = [WineApplicationController sharedController];
2679 BOOL clipping = FALSE;
2682 if (!CGRectIsInfinite(rect))
2683 rect = cgrect_mac_from_win(rect);
2685 if (!CGRectIsInfinite(rect))
2687 NSRect nsrect = NSRectFromCGRect(rect);
2690 /* Convert the rectangle from top-down coords to bottom-up. */
2691 [controller flipRect:&nsrect];
2694 for (screen in [NSScreen screens])
2696 if (!NSContainsRect(nsrect, [screen frame]))
2705 ret = [controller startClippingCursor:rect];
2707 ret = [controller stopClippingCursor];
2713 /***********************************************************************
2714 * macdrv_set_application_icon
2716 * Set the application icon. The images array contains CGImages. If
2717 * there are more than one, then they represent different sizes or
2718 * color depths from the icon resource. If images is NULL or empty,
2719 * restores the default application image.
2721 void macdrv_set_application_icon(CFArrayRef images)
2723 NSArray* imageArray = (NSArray*)images;
2725 OnMainThreadAsync(^{
2726 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2730 /***********************************************************************
2733 void macdrv_quit_reply(int reply)
2736 [NSApp replyToApplicationShouldTerminate:reply];
2740 /***********************************************************************
2741 * macdrv_using_input_method
2743 int macdrv_using_input_method(void)
2748 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2754 /***********************************************************************
2755 * macdrv_set_mouse_capture_window
2757 void macdrv_set_mouse_capture_window(macdrv_window window)
2759 WineWindow* w = (WineWindow*)window;
2761 [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2764 [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2768 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2769 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2770 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2772 /***********************************************************************
2773 * macdrv_create_input_source_list
2775 CFArrayRef macdrv_create_input_source_list(void)
2777 CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2780 CFArrayRef input_list;
2781 CFDictionaryRef filter_dict;
2782 const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2783 const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2786 filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2787 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2788 input_list = TISCreateInputSourceList(filter_dict, false);
2790 for (i = 0; i < CFArrayGetCount(input_list); i++)
2792 TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2793 CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2794 CFDictionaryRef entry;
2795 const void *input_keys[3] = { macdrv_input_source_input_key,
2796 macdrv_input_source_type_key,
2797 macdrv_input_source_lang_key };
2798 const void *input_values[3];
2800 input_values[0] = input;
2801 input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2802 input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2804 entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2805 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2807 CFArrayAppendValue(ret, entry);
2810 CFRelease(input_list);
2811 CFRelease(filter_dict);
2817 int macdrv_select_input_source(TISInputSourceRef input_source)
2819 __block int ret = FALSE;
2822 ret = (TISSelectInputSource(input_source) == noErr);
2828 void macdrv_set_cocoa_retina_mode(int new_mode)
2831 [[WineApplicationController sharedController] setRetinaMode:new_mode];