2 * MACDRV Cocoa application class
4 * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #import <Carbon/Carbon.h>
24 #import "cocoa_cursorclipping.h"
25 #import "cocoa_event.h"
26 #import "cocoa_window.h"
29 static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponseMode";
31 // Private notifications that are reliably dispatched when a window is moved by dragging its titlebar.
32 // The object of the notification is the window being dragged.
33 // Available in macOS 10.12+
34 static NSString* const NSWindowWillStartDraggingNotification = @"NSWindowWillStartDraggingNotification";
35 static NSString* const NSWindowDidEndDraggingNotification = @"NSWindowDidEndDraggingNotification";
37 // Internal distributed notification to handle cooperative app activation in Sonoma.
38 static NSString* const WineAppWillActivateNotification = @"WineAppWillActivateNotification";
39 static NSString* const WineActivatingAppPIDKey = @"ActivatingAppPID";
40 static NSString* const WineActivatingAppPrefixKey = @"ActivatingAppPrefix";
41 static NSString* const WineActivatingAppConfigDirKey = @"ActivatingAppConfigDir";
47 #if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
48 @interface NSWindow (WineAutoTabbingExtensions)
50 + (void) setAllowsAutomaticWindowTabbing:(BOOL)allows;
56 #if !defined(MAC_OS_VERSION_14_0) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_14_0
57 @interface NSApplication (CooperativeActivationSelectorsForOldSDKs)
60 - (void)yieldActivationToApplication:(NSRunningApplication *)application;
61 - (void)yieldActivationToApplicationWithBundleIdentifier:(NSString *)bundleIdentifier;
65 @interface NSRunningApplication (CooperativeActivationSelectorsForOldSDKs)
67 - (BOOL)activateFromApplication:(NSRunningApplication *)application
68 options:(NSApplicationActivationOptions)options;
74 /***********************************************************************
77 * Look up a localized string by its ID in the dictionary.
79 static NSString* WineLocalizedString(unsigned int stringID)
81 return ((NSDictionary*)localized_strings)[@(stringID)];
85 @implementation WineApplication
87 @synthesize wineController;
89 - (void) sendEvent:(NSEvent*)anEvent
91 if (![wineController handleEvent:anEvent])
93 [super sendEvent:anEvent];
94 [wineController didSendEvent:anEvent];
98 - (void) setWineController:(WineApplicationController*)newController
100 wineController = newController;
101 [self setDelegate:wineController];
107 @interface WineApplicationController ()
109 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
110 @property (copy, nonatomic) NSArray* cursorFrames;
111 @property (retain, nonatomic) NSTimer* cursorTimer;
112 @property (retain, nonatomic) NSCursor* cursor;
113 @property (retain, nonatomic) NSImage* applicationIcon;
114 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
115 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
117 - (void) setupObservations;
118 - (void) applicationDidBecomeActive:(NSNotification *)notification;
120 static void PerformRequest(void *info);
125 @implementation WineApplicationController
127 @synthesize keyboardType, lastFlagsChanged;
128 @synthesize applicationIcon;
129 @synthesize cursorFrames, cursorTimer, cursor;
130 @synthesize mouseCaptureWindow;
131 @synthesize lastSetCursorPositionTime;
135 if (self == [WineApplicationController class])
137 NSDictionary<NSString *, id> *defaults =
139 @"NSQuotedKeystrokeBinding" : @"",
140 @"NSRepeatCountBinding" : @"",
141 @"ApplePressAndHoldEnabled" : @NO
144 [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
146 if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
147 [NSWindow setAllowsAutomaticWindowTabbing:NO];
151 + (WineApplicationController*) sharedController
153 static WineApplicationController* sharedController;
154 static dispatch_once_t once;
156 dispatch_once(&once, ^{
157 sharedController = [[self alloc] init];
160 return sharedController;
168 CFRunLoopSourceContext context = { 0 };
169 context.perform = PerformRequest;
170 requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
176 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
177 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
179 requests = [[NSMutableArray alloc] init];
180 requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
182 eventQueues = [[NSMutableArray alloc] init];
183 eventQueuesLock = [[NSLock alloc] init];
185 keyWindows = [[NSMutableArray alloc] init];
187 originalDisplayModes = [[NSMutableDictionary alloc] init];
188 latentDisplayModes = [[NSMutableDictionary alloc] init];
190 windowsBeingDragged = [[NSMutableSet alloc] init];
192 // On macOS 10.12+, use notifications to more reliably detect when windows are being dragged.
193 if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)])
195 NSOperatingSystemVersion requiredVersion = { 10, 12, 0 };
196 useDragNotifications = [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:requiredVersion];
199 useDragNotifications = NO;
201 if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
202 !keyWindows || !originalDisplayModes || !latentDisplayModes)
208 [self setupObservations];
210 keyboardType = LMGetKbdType();
212 if ([NSApp isActive])
213 [self applicationDidBecomeActive:nil];
220 [windowsBeingDragged release];
222 [screenFrameCGRects release];
223 [applicationIcon release];
224 [clipCursorHandler release];
225 [cursorTimer release];
226 [cursorFrames release];
227 [latentDisplayModes release];
228 [originalDisplayModes release];
229 [keyWindows release];
230 [eventQueues release];
231 [eventQueuesLock release];
232 if (requestsManipQueue) dispatch_release(requestsManipQueue);
236 CFRunLoopSourceInvalidate(requestSource);
237 CFRelease(requestSource);
242 - (void) transformProcessToForeground:(BOOL)activateIfTransformed
244 if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
248 NSString* bundleName;
252 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
254 if (activateIfTransformed)
255 [self tryToActivateIgnoringOtherApps:YES];
257 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
258 if (!enable_app_nap && [NSProcessInfo instancesRespondToSelector:@selector(beginActivityWithOptions:reason:)])
260 [[[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep
261 reason:@"Running Windows program"] retain]; // intentional leak
265 mainMenu = [[[NSMenu alloc] init] autorelease];
268 submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
269 bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
271 if ([bundleName length])
272 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
274 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
275 item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
277 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
278 action:@selector(hideOtherApplications:)
280 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
282 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
283 action:@selector(unhideAllApplications:)
286 [submenu addItem:[NSMenuItem separatorItem]];
288 if ([bundleName length])
289 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
291 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
292 item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
293 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
294 item = [[[NSMenuItem alloc] init] autorelease];
295 [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
296 [item setSubmenu:submenu];
297 [mainMenu addItem:item];
300 submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
301 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
302 action:@selector(performMiniaturize:)
304 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
305 action:@selector(performZoom:)
307 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
308 action:@selector(toggleFullScreen:)
310 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand |
311 NSEventModifierFlagOption |
312 NSEventModifierFlagControl];
313 [submenu addItem:[NSMenuItem separatorItem]];
314 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
315 action:@selector(arrangeInFront:)
317 item = [[[NSMenuItem alloc] init] autorelease];
318 [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
319 [item setSubmenu:submenu];
320 [mainMenu addItem:item];
322 [NSApp setMainMenu:mainMenu];
323 [NSApp setWindowsMenu:submenu];
325 [NSApp setApplicationIconImage:self.applicationIcon];
329 - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
331 PerformRequest(NULL);
339 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
341 inMode:NSDefaultRunLoopMode
344 [NSApp sendEvent:event];
348 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
349 } while (!*done && [timeout timeIntervalSinceNow] >= 0);
354 - (BOOL) registerEventQueue:(WineEventQueue*)queue
356 [eventQueuesLock lock];
357 [eventQueues addObject:queue];
358 [eventQueuesLock unlock];
362 - (void) unregisterEventQueue:(WineEventQueue*)queue
364 [eventQueuesLock lock];
365 [eventQueues removeObjectIdenticalTo:queue];
366 [eventQueuesLock unlock];
369 - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
371 eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
374 - (double) ticksForEventTime:(NSTimeInterval)eventTime
376 return (eventTime + eventTimeAdjustment) * 1000;
379 /* Invalidate old focus offers across all queues. */
380 - (void) invalidateGotFocusEvents
382 WineEventQueue* queue;
386 [eventQueuesLock lock];
387 for (queue in eventQueues)
389 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
392 [eventQueuesLock unlock];
395 - (void) windowGotFocus:(WineWindow*)window
399 [self invalidateGotFocusEvents];
401 event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
402 event->window_got_focus.serial = windowFocusSerial;
404 event->window_got_focus.tried_windows = [triedWindows retain];
406 event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
407 [window.queue postEvent:event];
408 macdrv_release_event(event);
411 - (void) windowRejectedFocusEvent:(const macdrv_event*)event
413 if (event->window_got_focus.serial == windowFocusSerial)
415 NSMutableArray* windows = [keyWindows mutableCopy];
416 NSNumber* windowNumber;
419 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
421 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
422 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
423 ![windows containsObject:window])
424 [windows addObject:window];
427 triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
428 [triedWindows addObject:(WineWindow*)event->window];
429 for (window in windows)
431 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
433 [window makeKeyWindow];
442 static BOOL EqualInputSource(TISInputSourceRef source1, TISInputSourceRef source2)
444 if (!source1 && !source2)
446 if (!source1 || !source2)
448 return CFEqual(source1, source2);
451 - (void) keyboardSelectionDidChange:(BOOL)force
453 TISInputSourceRef inputSource, inputSourceLayout;
457 NSTextInputContext* context = [NSTextInputContext currentInputContext];
458 if (!context || ![context client])
462 inputSource = TISCopyCurrentKeyboardInputSource();
463 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
464 if (!force && EqualInputSource(inputSource, lastKeyboardInputSource) &&
465 EqualInputSource(inputSourceLayout, lastKeyboardLayoutInputSource))
467 if (inputSource) CFRelease(inputSource);
468 if (inputSourceLayout) CFRelease(inputSourceLayout);
472 if (lastKeyboardInputSource)
473 CFRelease(lastKeyboardInputSource);
474 lastKeyboardInputSource = inputSource;
475 if (lastKeyboardLayoutInputSource)
476 CFRelease(lastKeyboardLayoutInputSource);
477 lastKeyboardLayoutInputSource = inputSourceLayout;
479 inputSourceIsInputMethodValid = FALSE;
481 if (inputSourceLayout)
484 uchr = TISGetInputSourceProperty(inputSourceLayout,
485 kTISPropertyUnicodeKeyLayoutData);
489 WineEventQueue* queue;
491 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
492 event->keyboard_changed.keyboard_type = self.keyboardType;
493 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
494 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
495 event->keyboard_changed.input_source = (TISInputSourceRef)CFRetain(inputSource);
497 if (event->keyboard_changed.uchr)
499 [eventQueuesLock lock];
501 for (queue in eventQueues)
502 [queue postEvent:event];
504 [eventQueuesLock unlock];
507 macdrv_release_event(event);
512 - (void) keyboardSelectionDidChange
514 [self keyboardSelectionDidChange:NO];
517 - (void) setKeyboardType:(CGEventSourceKeyboardType)newType
519 if (newType != keyboardType)
521 keyboardType = newType;
522 [self keyboardSelectionDidChange:YES];
526 - (void) enabledKeyboardInputSourcesChanged
528 macdrv_layout_list_needs_update = TRUE;
531 - (CGFloat) primaryScreenHeight
533 if (!primaryScreenHeightValid)
535 NSArray* screens = [NSScreen screens];
536 NSUInteger count = [screens count];
543 primaryScreenHeight = NSHeight([screens[0] frame]);
544 primaryScreenHeightValid = TRUE;
546 size = count * sizeof(CGRect);
547 if (!screenFrameCGRects)
548 screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
550 [screenFrameCGRects setLength:size];
552 rect = [screenFrameCGRects mutableBytes];
553 for (screen in screens)
555 CGRect temp = NSRectToCGRect([screen frame]);
556 temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
561 return 1280; /* arbitrary value */
564 return primaryScreenHeight;
567 - (NSPoint) flippedMouseLocation:(NSPoint)point
569 /* This relies on the fact that Cocoa's mouse location points are
570 actually off by one (precisely because they were flipped from
571 Quartz screen coordinates using this same technique). */
572 point.y = [self primaryScreenHeight] - point.y;
576 - (void) flipRect:(NSRect*)rect
578 // We don't use -primaryScreenHeight here so there's no chance of having
579 // out-of-date cached info. This method is called infrequently enough
580 // that getting the screen height each time is not prohibitively expensive.
581 rect->origin.y = NSMaxY([[NSScreen screens][0] frame]) - NSMaxY(*rect);
584 - (WineWindow*) frontWineWindow
586 NSNumber* windowNumber;
587 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
589 NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
590 if ([window isKindOfClass:[WineWindow class]] && [window screen])
591 return (WineWindow*)window;
597 - (void) adjustWindowLevels:(BOOL)active
599 NSArray* windowNumbers;
600 NSMutableArray* wineWindows;
601 NSNumber* windowNumber;
602 NSUInteger nextFloatingIndex = 0;
603 __block NSInteger maxLevel = NSIntegerMin;
604 __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
605 __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
606 __block WineWindow* prev = nil;
609 if ([NSApp isHidden]) return;
611 windowNumbers = [NSWindow windowNumbersWithOptions:0];
612 wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
614 // For the most part, we rely on the window server's ordering of the windows
615 // to be authoritative. The one exception is if the "floating" property of
616 // one of the windows has been changed, it may be in the wrong level and thus
617 // in the order. This method is what's supposed to fix that up. So build
618 // a list of Wine windows sorted first by floating-ness and then by order
619 // as indicated by the window server.
620 for (windowNumber in windowNumbers)
622 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
623 if ([window isKindOfClass:[WineWindow class]])
626 [wineWindows insertObject:window atIndex:nextFloatingIndex++];
628 [wineWindows addObject:window];
632 NSDisableScreenUpdates();
634 // Go from back to front so that all windows in front of one which is
635 // elevated for full-screen are also elevated.
636 [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
637 usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
638 WineWindow* window = (WineWindow*)obj;
639 NSInteger origLevel = [window level];
640 NSInteger newLevel = [window minimumLevelForActive:active];
644 if (minFloatingLevel <= maxNonfloatingLevel)
645 minFloatingLevel = maxNonfloatingLevel + 1;
646 if (newLevel < minFloatingLevel)
647 newLevel = minFloatingLevel;
650 if (newLevel < maxLevel)
655 if (!window.floating && maxNonfloatingLevel < newLevel)
656 maxNonfloatingLevel = newLevel;
658 if (newLevel != origLevel)
660 [window setLevel:newLevel];
662 if (origLevel < newLevel)
664 // If we increased the level, the window should be toward the
665 // back of its new level (but still ahead of the previous
666 // windows we did this to).
668 [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
670 [window orderBack:nil];
674 // If we decreased the level, we want the window at the top
675 // of its new level. -setLevel: is documented to do that on
676 // its own, but that's buggy on Ventura. Since we're looping
677 // back-to-front here, -orderFront: will do the right thing.
678 [window orderFront:nil];
685 NSEnableScreenUpdates();
687 [wineWindows release];
689 // The above took care of the visible windows on the current space. That
690 // leaves windows on other spaces, minimized windows, and windows which
691 // are not ordered in. We want to leave windows on other spaces alone
692 // so the space remains just as they left it (when viewed in Exposé or
693 // Mission Control, for example). We'll adjust the window levels again
694 // after we switch to another space, anyway. Windows which aren't
695 // ordered in will be handled when we order them in. Minimized windows
696 // on the current space should be set to the level they would have gotten
697 // if they were at the front of the windows with the same floating-ness,
698 // because that's where they'll go if/when they are unminimized. Again,
699 // for good measure we'll adjust window levels again when a window is
701 for (window in [NSApp windows])
703 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
704 [window isOnActiveSpace])
706 NSInteger origLevel = [window level];
707 NSInteger newLevel = [window minimumLevelForActive:YES];
708 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
710 if (newLevel < maxLevelForType)
711 newLevel = maxLevelForType;
713 if (newLevel != origLevel)
714 [window setLevel:newLevel];
719 - (void) adjustWindowLevels
721 [self adjustWindowLevels:[NSApp isActive]];
724 - (void) updateFullscreenWindows
726 if (capture_displays_for_fullscreen && [NSApp isActive])
728 BOOL anyFullscreen = FALSE;
729 NSNumber* windowNumber;
730 for (windowNumber in [NSWindow windowNumbersWithOptions:0])
732 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
733 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
735 anyFullscreen = TRUE;
742 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
743 displaysCapturedForFullscreen = TRUE;
745 else if (displaysCapturedForFullscreen)
747 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
748 displaysCapturedForFullscreen = FALSE;
753 - (void) activeSpaceDidChange
755 [self updateFullscreenWindows];
756 [self adjustWindowLevels];
759 - (void) sendDisplaysChanged:(BOOL)activating
762 WineEventQueue* queue;
764 event = macdrv_create_event(DISPLAYS_CHANGED, nil);
765 event->displays_changed.activating = activating;
767 [eventQueuesLock lock];
769 // If we're activating, then we just need one of our threads to get the
770 // event, so it can send it directly to the desktop window. Otherwise,
771 // we need all of the threads to get it because we don't know which owns
772 // the desktop window and only that one will do anything with it.
773 if (activating) event->deliver = 1;
775 for (queue in eventQueues)
776 [queue postEvent:event];
777 [eventQueuesLock unlock];
779 macdrv_release_event(event);
782 // We can compare two modes directly using CFEqual, but that may require that
783 // they are identical to a level that we don't need. In particular, when the
784 // OS switches between the integrated and discrete GPUs, the set of display
785 // modes can change in subtle ways. We're interested in whether two modes
786 // match in their most salient features, even if they aren't identical.
787 - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
789 NSString *encoding1, *encoding2;
790 uint32_t ioflags1, ioflags2, different;
791 double refresh1, refresh2;
793 if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
794 if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
795 if (CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
796 if (CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
798 encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
799 encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
800 if (![encoding1 isEqualToString:encoding2]) return FALSE;
802 ioflags1 = CGDisplayModeGetIOFlags(mode1);
803 ioflags2 = CGDisplayModeGetIOFlags(mode2);
804 different = ioflags1 ^ ioflags2;
805 if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
806 kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
809 refresh1 = CGDisplayModeGetRefreshRate(mode1);
810 if (refresh1 == 0) refresh1 = 60;
811 refresh2 = CGDisplayModeGetRefreshRate(mode2);
812 if (refresh2 == 0) refresh2 = 60;
813 if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
818 - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
820 NSMutableArray* ret = [NSMutableArray array];
821 NSDictionary* options = @{ (NSString*)kCGDisplayShowDuplicateLowResolutionModes: @YES };
823 NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
824 for (id candidateModeObject in modes)
826 CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
827 if ([self mode:candidateMode matchesMode:mode])
828 [ret addObject:candidateModeObject];
833 - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
836 NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
837 CGDisplayModeRef originalMode;
839 originalMode = (CGDisplayModeRef)originalDisplayModes[displayIDKey];
841 if (originalMode && [self mode:mode matchesMode:originalMode])
843 if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
845 CGRestorePermanentDisplayConfiguration();
846 if (!displaysCapturedForFullscreen)
847 CGReleaseAllDisplays();
848 [originalDisplayModes removeAllObjects];
851 else // ... otherwise, try to restore just the one display
853 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
855 mode = (CGDisplayModeRef)modeObject;
856 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
858 [originalDisplayModes removeObjectForKey:displayIDKey];
867 CGDisplayModeRef currentMode;
870 currentMode = CGDisplayModeRetain((CGDisplayModeRef)latentDisplayModes[displayIDKey]);
872 currentMode = CGDisplayCopyDisplayMode(displayID);
873 if (!currentMode) // Invalid display ID
876 if ([self mode:mode matchesMode:currentMode]) // Already there!
878 CGDisplayModeRelease(currentMode);
882 CGDisplayModeRelease(currentMode);
885 modes = [self modesMatchingMode:mode forDisplay:displayID];
889 [self transformProcessToForeground:YES];
891 BOOL active = [NSApp isActive];
893 if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
894 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
898 // If we get here, we have the displays captured. If we don't
899 // know the original mode of the display, the current mode must
900 // be the original. We should re-query the current mode since
901 // another process could have changed it between when we last
902 // checked and when we captured the displays.
904 originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
908 for (id modeObject in modes)
910 mode = (CGDisplayModeRef)modeObject;
911 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
918 if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
919 [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
920 else if (![originalDisplayModes count])
922 CGRestorePermanentDisplayConfiguration();
923 if (!displaysCapturedForFullscreen)
924 CGReleaseAllDisplays();
928 CGDisplayModeRelease(currentMode);
932 [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
939 [self adjustWindowLevels];
944 - (BOOL) areDisplaysCaptured
946 return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
949 - (void) updateCursor:(BOOL)force
951 if (force || lastTargetWindow)
953 if (clientWantsCursorHidden && !cursorHidden)
959 if (!cursorIsCurrent)
962 cursorIsCurrent = TRUE;
965 if (!clientWantsCursorHidden && cursorHidden)
968 cursorHidden = FALSE;
975 [[NSCursor arrowCursor] set];
976 cursorIsCurrent = FALSE;
981 cursorHidden = FALSE;
988 if (!clientWantsCursorHidden)
990 clientWantsCursorHidden = TRUE;
991 [self updateCursor:TRUE];
995 - (void) unhideCursor
997 if (clientWantsCursorHidden)
999 clientWantsCursorHidden = FALSE;
1000 [self updateCursor:FALSE];
1004 - (void) setCursor:(NSCursor*)newCursor
1006 if (newCursor != cursor)
1009 cursor = [newCursor retain];
1010 cursorIsCurrent = FALSE;
1011 [self updateCursor:FALSE];
1017 NSDictionary* frame = cursorFrames[cursorFrame];
1018 CGImageRef cgimage = (CGImageRef)frame[@"image"];
1019 CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
1020 NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
1021 CFDictionaryRef hotSpotDict = (CFDictionaryRef)frame[@"hotSpot"];
1024 if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
1025 hotSpot = CGPointZero;
1026 hotSpot = cgpoint_mac_from_win(hotSpot);
1027 self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1029 [self unhideCursor];
1032 - (void) nextCursorFrame:(NSTimer*)theTimer
1034 NSDictionary* frame;
1035 NSTimeInterval duration;
1039 if (cursorFrame >= [cursorFrames count])
1043 frame = cursorFrames[cursorFrame];
1044 duration = [frame[@"duration"] doubleValue];
1045 date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1046 [cursorTimer setFireDate:date];
1049 - (void) setCursorWithFrames:(NSArray*)frames
1051 if (self.cursorFrames == frames)
1054 self.cursorFrames = frames;
1056 [cursorTimer invalidate];
1057 self.cursorTimer = nil;
1061 if ([frames count] > 1)
1063 NSDictionary* frame = frames[0];
1064 NSTimeInterval duration = [frame[@"duration"] doubleValue];
1065 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1066 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1069 selector:@selector(nextCursorFrame:)
1071 repeats:YES] autorelease];
1072 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1079 - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1081 NSImage* nsimage = nil;
1085 NSSize bestSize = NSZeroSize;
1088 nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1090 for (image in images)
1092 CGImageRef cgimage = (CGImageRef)image;
1093 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1096 NSSize size = [imageRep size];
1098 [nsimage addRepresentation:imageRep];
1101 if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1106 if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1107 [nsimage setSize:bestSize];
1112 self.applicationIcon = nsimage;
1115 - (void) handleCommandTab
1117 if ([NSApp isActive])
1119 NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1120 NSRunningApplication* app;
1121 NSRunningApplication* otherValidApp = nil;
1123 if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1125 NSNumber* displayID;
1126 for (displayID in originalDisplayModes)
1128 CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1129 [latentDisplayModes setObject:(id)mode forKey:displayID];
1130 CGDisplayModeRelease(mode);
1133 CGRestorePermanentDisplayConfiguration();
1134 CGReleaseAllDisplays();
1135 [originalDisplayModes removeAllObjects];
1136 displaysCapturedForFullscreen = FALSE;
1139 for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1141 if (![app isEqual:thisApp] && !app.terminated &&
1142 app.activationPolicy == NSApplicationActivationPolicyRegular)
1146 // There's another visible app. Just hide ourselves and let
1147 // the system activate the other app.
1153 otherValidApp = app;
1157 // Didn't find a visible GUI app. Try the Finder or, if that's not
1158 // running, the first hidden GUI app. If even that doesn't work, we
1159 // just fail to switch and remain the active app.
1160 app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1161 if (!app) app = otherValidApp;
1163 [app activateWithOptions:0];
1167 - (BOOL) setCursorPosition:(CGPoint)pos
1171 if ([windowsBeingDragged count])
1173 else if (self.clippingCursor && [clipCursorHandler respondsToSelector:@selector(setCursorPosition:)])
1174 ret = [clipCursorHandler setCursorPosition:pos];
1177 if (self.clippingCursor)
1178 [clipCursorHandler clipCursorLocation:&pos];
1180 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1181 // the mouse from the cursor position for 0.25 seconds. This means
1182 // that mouse movement during that interval doesn't move the cursor
1183 // and events carry a constant location (the warped-to position)
1184 // even though they have delta values. For apps which warp the
1185 // cursor frequently (like after every mouse move), this makes
1186 // cursor movement horribly laggy and jerky, as only a fraction of
1187 // mouse move events have any effect.
1189 // On some versions of OS X, it's sufficient to forcibly reassociate
1190 // the mouse and cursor position. On others, it's necessary to set
1191 // the local events suppression interval to 0 for the warp. That's
1192 // deprecated, but I'm not aware of any other way. For good
1193 // measure, we do both.
1194 CGSetLocalEventsSuppressionInterval(0);
1195 ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1196 CGSetLocalEventsSuppressionInterval(0.25);
1199 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1201 CGAssociateMouseAndMouseCursorPosition(true);
1207 WineEventQueue* queue;
1209 // Discard all pending mouse move events.
1210 [eventQueuesLock lock];
1211 for (queue in eventQueues)
1213 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED_RELATIVE) |
1214 event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1216 [queue resetMouseEventPositions:pos];
1218 [eventQueuesLock unlock];
1224 - (void) updateWindowsForCursorClipping
1227 for (window in [NSApp windows])
1229 if ([window isKindOfClass:[WineWindow class]])
1230 [window updateForCursorClipping];
1234 - (BOOL) startClippingCursor:(CGRect)rect
1236 if (!clipCursorHandler) {
1237 if (use_confinement_cursor_clipping && [WineConfinementClipCursorHandler isAvailable])
1238 clipCursorHandler = [[WineConfinementClipCursorHandler alloc] init];
1240 clipCursorHandler = [[WineEventTapClipCursorHandler alloc] init];
1243 if (self.clippingCursor && CGRectEqualToRect(rect, clipCursorHandler.cursorClipRect))
1246 if (![clipCursorHandler startClippingCursor:rect])
1249 [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1251 [self updateWindowsForCursorClipping];
1256 - (BOOL) stopClippingCursor
1258 if (!self.clippingCursor)
1261 if (![clipCursorHandler stopClippingCursor])
1264 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1266 [self updateWindowsForCursorClipping];
1271 - (BOOL) clippingCursor
1273 return clipCursorHandler.clippingCursor;
1276 - (BOOL) isKeyPressed:(uint16_t)keyCode
1278 int bits = sizeof(pressedKeyCodes[0]) * 8;
1279 int index = keyCode / bits;
1280 uint32_t mask = 1 << (keyCode % bits);
1281 return (pressedKeyCodes[index] & mask) != 0;
1284 - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1286 int bits = sizeof(pressedKeyCodes[0]) * 8;
1287 int index = keyCode / bits;
1288 uint32_t mask = 1 << (keyCode % bits);
1290 pressedKeyCodes[index] |= mask;
1292 pressedKeyCodes[index] &= ~mask;
1295 - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1298 [windowsBeingDragged addObject:window];
1300 [windowsBeingDragged removeObject:window];
1303 - (void) windowWillOrderOut:(WineWindow*)window
1305 if ([windowsBeingDragged containsObject:window])
1307 [self window:window isBeingDragged:NO];
1309 macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1310 [window.queue postEvent:event];
1311 macdrv_release_event(event);
1315 - (BOOL) isAnyWineWindowVisible
1317 for (WineWindow* w in [NSApp windows])
1319 if ([w isKindOfClass:[WineWindow class]] && ![w isMiniaturized] && [w isVisible])
1326 - (void) handleWindowDrag:(WineWindow*)window begin:(BOOL)begin
1328 macdrv_event* event;
1333 [windowsBeingDragged addObject:window];
1334 eventType = WINDOW_DRAG_BEGIN;
1338 [windowsBeingDragged removeObject:window];
1339 eventType = WINDOW_DRAG_END;
1342 event = macdrv_create_event(eventType, window);
1343 if (eventType == WINDOW_DRAG_BEGIN)
1344 event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
1345 [window.queue postEvent:event];
1346 macdrv_release_event(event);
1349 - (void) handleMouseMove:(NSEvent*)anEvent
1351 WineWindow* targetWindow;
1352 BOOL drag = [anEvent type] != NSEventTypeMouseMoved;
1354 if ([windowsBeingDragged count])
1356 else if (mouseCaptureWindow)
1357 targetWindow = mouseCaptureWindow;
1359 targetWindow = (WineWindow*)[anEvent window];
1362 /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1363 event indicates its window is the main window, even if the cursor is
1364 over a different window. Find the actual WineWindow that is under the
1365 cursor and post the event as being for that window. */
1366 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1367 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1368 NSInteger windowUnderNumber;
1370 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1371 belowWindowWithWindowNumber:0];
1372 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1373 if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1377 if ([targetWindow isKindOfClass:[WineWindow class]])
1379 CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1380 macdrv_event* event;
1383 // If we recently warped the cursor (other than in our cursor-clipping
1384 // event tap), discard mouse move events until we see an event which is
1385 // later than that time.
1386 if (lastSetCursorPositionTime)
1388 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1391 lastSetCursorPositionTime = 0;
1392 forceNextMouseMoveAbsolute = TRUE;
1395 if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1398 forceNextMouseMoveAbsolute = FALSE;
1402 // Send absolute move events if the cursor is in the interior of
1403 // its range. Only send relative moves if the cursor is pinned to
1404 // the boundaries of where it can go. We compute the position
1405 // that's one additional point in the direction of movement. If
1406 // that is outside of the clipping rect or desktop region (the
1407 // union of the screen frames), then we figure the cursor would
1408 // have moved outside if it could but it was pinned.
1409 CGPoint computedPoint = point;
1410 CGFloat deltaX = [anEvent deltaX];
1411 CGFloat deltaY = [anEvent deltaY];
1415 else if (deltaX < -0.001)
1420 else if (deltaY < -0.001)
1423 // Assume cursor is pinned for now
1425 if (!self.clippingCursor || CGRectContainsPoint(clipCursorHandler.cursorClipRect, computedPoint))
1427 const CGRect* rects;
1428 NSUInteger count, i;
1430 // Caches screenFrameCGRects if necessary
1431 [self primaryScreenHeight];
1433 rects = [screenFrameCGRects bytes];
1434 count = [screenFrameCGRects length] / sizeof(rects[0]);
1436 for (i = 0; i < count; i++)
1438 if (CGRectContainsPoint(rects[i], computedPoint))
1449 if (self.clippingCursor)
1450 [clipCursorHandler clipCursorLocation:&point];
1451 point = cgpoint_win_from_mac(point);
1453 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1454 event->mouse_moved.x = floor(point.x);
1455 event->mouse_moved.y = floor(point.y);
1457 mouseMoveDeltaX = 0;
1458 mouseMoveDeltaY = 0;
1462 double scale = retina_on ? 2 : 1;
1464 /* Add event delta to accumulated delta error */
1465 /* deltaY is already flipped */
1466 mouseMoveDeltaX += [anEvent deltaX];
1467 mouseMoveDeltaY += [anEvent deltaY];
1469 event = macdrv_create_event(MOUSE_MOVED_RELATIVE, targetWindow);
1470 event->mouse_moved.x = mouseMoveDeltaX * scale;
1471 event->mouse_moved.y = mouseMoveDeltaY * scale;
1473 /* Keep the remainder after integer truncation. */
1474 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1475 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1478 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1480 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1481 event->mouse_moved.drag = drag;
1483 [targetWindow.queue postEvent:event];
1486 macdrv_release_event(event);
1488 lastTargetWindow = targetWindow;
1491 lastTargetWindow = nil;
1493 [self updateCursor:FALSE];
1496 - (void) handleMouseButton:(NSEvent*)theEvent
1498 WineWindow* window = (WineWindow*)[theEvent window];
1499 NSEventType type = [theEvent type];
1500 WineWindow* windowBroughtForward = nil;
1501 BOOL process = FALSE;
1503 if ([window isKindOfClass:[WineWindow class]] &&
1504 type == NSEventTypeLeftMouseDown &&
1505 ![theEvent wine_commandKeyDown])
1507 NSWindowButton windowButton;
1509 windowBroughtForward = window;
1511 /* Any left-click on our window anyplace other than the close or
1512 minimize buttons will bring it forward. */
1513 for (windowButton = NSWindowCloseButton;
1514 windowButton <= NSWindowMiniaturizeButton;
1517 NSButton* button = [window standardWindowButton:windowButton];
1520 NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1521 if ([button mouse:point inRect:[button bounds]])
1523 windowBroughtForward = nil;
1530 if ([windowsBeingDragged count])
1532 else if (mouseCaptureWindow)
1533 window = mouseCaptureWindow;
1535 if ([window isKindOfClass:[WineWindow class]])
1537 BOOL pressed = (type == NSEventTypeLeftMouseDown ||
1538 type == NSEventTypeRightMouseDown ||
1539 type == NSEventTypeOtherMouseDown);
1540 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1542 if (self.clippingCursor)
1543 [clipCursorHandler clipCursorLocation:&pt];
1547 if (mouseCaptureWindow)
1551 // Test if the click was in the window's content area.
1552 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1553 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1554 process = NSMouseInRect(nspoint, contentRect, NO);
1555 if (process && [window styleMask] & NSWindowStyleMaskResizable)
1557 // Ignore clicks in the grow box (resize widget).
1558 HIPoint origin = { 0, 0 };
1559 HIThemeGrowBoxDrawInfo info = { 0 };
1563 info.kind = kHIThemeGrowBoxKindNormal;
1564 info.direction = kThemeGrowRight | kThemeGrowDown;
1565 if ([window styleMask] & NSWindowStyleMaskUtilityWindow)
1566 info.size = kHIThemeGrowBoxSizeSmall;
1568 info.size = kHIThemeGrowBoxSizeNormal;
1570 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1571 if (status == noErr)
1573 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1574 NSMinY(contentRect),
1576 bounds.size.height);
1577 process = !NSMouseInRect(nspoint, growBox, NO);
1582 unmatchedMouseDowns |= NSEventMaskFromType(type);
1586 NSEventType downType = type - 1;
1587 NSUInteger downMask = NSEventMaskFromType(downType);
1588 process = (unmatchedMouseDowns & downMask) != 0;
1589 unmatchedMouseDowns &= ~downMask;
1594 macdrv_event* event;
1596 pt = cgpoint_win_from_mac(pt);
1598 event = macdrv_create_event(MOUSE_BUTTON, window);
1599 event->mouse_button.button = [theEvent buttonNumber];
1600 event->mouse_button.pressed = pressed;
1601 event->mouse_button.x = floor(pt.x);
1602 event->mouse_button.y = floor(pt.y);
1603 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1605 [window.queue postEvent:event];
1607 macdrv_release_event(event);
1611 if (windowBroughtForward)
1613 WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1614 NSInteger ancestorNumber = [ancestor windowNumber];
1615 NSInteger ancestorLevel = [ancestor level];
1617 for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1619 NSInteger windowNumber = [windowNumberObject integerValue];
1620 if (windowNumber == ancestorNumber)
1622 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1623 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1624 [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1626 [ancestor postBroughtForwardEvent];
1630 if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noForeground)
1631 [self windowGotFocus:windowBroughtForward];
1634 // Since mouse button events deliver absolute cursor position, the
1635 // accumulating delta from move events is invalidated. Make sure
1636 // next mouse move event starts over from an absolute baseline.
1637 // Also, it's at least possible that the title bar widgets (e.g. close
1638 // button, etc.) could enter an internal event loop on a mouse down that
1639 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1640 // dragged events and, after that, any notion of the cursor position
1641 // computed from accumulating deltas would be wrong.
1642 forceNextMouseMoveAbsolute = TRUE;
1645 - (void) handleScrollWheel:(NSEvent*)theEvent
1649 if (mouseCaptureWindow)
1650 window = mouseCaptureWindow;
1652 window = (WineWindow*)[theEvent window];
1654 if ([window isKindOfClass:[WineWindow class]])
1656 CGEventRef cgevent = [theEvent CGEvent];
1657 CGPoint pt = CGEventGetLocation(cgevent);
1660 if (self.clippingCursor)
1661 [clipCursorHandler clipCursorLocation:&pt];
1663 if (mouseCaptureWindow)
1667 // Only process the event if it was in the window's content area.
1668 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1669 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1670 process = NSMouseInRect(nspoint, contentRect, NO);
1675 macdrv_event* event;
1677 BOOL continuous = FALSE;
1679 pt = cgpoint_win_from_mac(pt);
1681 event = macdrv_create_event(MOUSE_SCROLL, window);
1682 event->mouse_scroll.x = floor(pt.x);
1683 event->mouse_scroll.y = floor(pt.y);
1684 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1686 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1690 /* Continuous scroll wheel events come from high-precision scrolling
1691 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1692 For these, we can get more precise data from the CGEvent API. */
1693 /* Axis 1 is vertical, axis 2 is horizontal. */
1694 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1695 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1699 double pixelsPerLine = 10;
1700 CGEventSourceRef source;
1702 /* The non-continuous values are in units of "lines", not pixels. */
1703 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1705 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1709 x = pixelsPerLine * [theEvent deltaX];
1710 y = pixelsPerLine * [theEvent deltaY];
1713 /* Mac: negative is right or down, positive is left or up.
1714 Win32: negative is left or down, positive is right or up.
1715 So, negate the X scroll value to translate. */
1718 /* The x,y values so far are in pixels. Win32 expects to receive some
1719 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1720 6 times the pixel value. */
1724 if (use_precise_scrolling)
1726 event->mouse_scroll.x_scroll = x;
1727 event->mouse_scroll.y_scroll = y;
1731 /* For non-continuous "clicky" wheels, if there was any motion, make
1732 sure there was at least WHEEL_DELTA motion. This is so, at slow
1733 speeds where the system's acceleration curve is actually reducing the
1734 scroll distance, the user is sure to get some action out of each click.
1735 For example, this is important for rotating though weapons in a
1736 first-person shooter. */
1737 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1738 event->mouse_scroll.x_scroll = 120;
1739 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1740 event->mouse_scroll.x_scroll = -120;
1742 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1743 event->mouse_scroll.y_scroll = 120;
1744 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1745 event->mouse_scroll.y_scroll = -120;
1750 /* If it's been a while since the last scroll event or if the scrolling has
1751 reversed direction, reset the accumulated scroll value. */
1752 if ([theEvent timestamp] - lastScrollTime > 1)
1753 accumScrollX = accumScrollY = 0;
1756 /* The accumulated scroll value is in the opposite direction/sign of the last
1757 scroll. That's because it's the "debt" resulting from over-scrolling in
1758 that direction. We accumulate by adding in the scroll amount and then, if
1759 it has the same sign as the scroll value, we subtract any whole or partial
1760 WHEEL_DELTAs, leaving it 0 or the opposite sign. So, the user switched
1761 scroll direction if the accumulated debt and the new scroll value have the
1763 if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1765 if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1768 lastScrollTime = [theEvent timestamp];
1773 if (accumScrollX > 0 && x > 0)
1774 event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1775 if (accumScrollX < 0 && x < 0)
1776 event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1777 if (accumScrollY > 0 && y > 0)
1778 event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1779 if (accumScrollY < 0 && y < 0)
1780 event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1782 accumScrollX -= event->mouse_scroll.x_scroll;
1783 accumScrollY -= event->mouse_scroll.y_scroll;
1786 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1787 [window.queue postEvent:event];
1789 macdrv_release_event(event);
1791 // Since scroll wheel events deliver absolute cursor position, the
1792 // accumulating delta from move events is invalidated. Make sure next
1793 // mouse move event starts over from an absolute baseline.
1794 forceNextMouseMoveAbsolute = TRUE;
1799 // Returns TRUE if the event was handled and caller should do nothing more
1800 // with it. Returns FALSE if the caller should process it as normal and
1801 // then call -didSendEvent:.
1802 - (BOOL) handleEvent:(NSEvent*)anEvent
1805 NSEventType type = [anEvent type];
1807 if (type == NSEventTypeFlagsChanged)
1808 self.lastFlagsChanged = anEvent;
1809 else if (type == NSEventTypeMouseMoved || type == NSEventTypeLeftMouseDragged ||
1810 type == NSEventTypeRightMouseDragged || type == NSEventTypeOtherMouseDragged)
1812 [self handleMouseMove:anEvent];
1813 ret = mouseCaptureWindow && ![windowsBeingDragged count];
1815 else if (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp ||
1816 type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp ||
1817 type == NSEventTypeOtherMouseDown || type == NSEventTypeOtherMouseUp)
1819 [self handleMouseButton:anEvent];
1820 ret = mouseCaptureWindow && ![windowsBeingDragged count];
1822 else if (type == NSEventTypeScrollWheel)
1824 [self handleScrollWheel:anEvent];
1825 ret = mouseCaptureWindow != nil;
1827 else if (type == NSEventTypeKeyDown)
1829 // -[NSApplication sendEvent:] seems to consume presses of the Help
1830 // key (Insert key on PC keyboards), so we have to bypass it and
1831 // send the event directly to the window.
1832 if (anEvent.keyCode == kVK_Help)
1834 [anEvent.window sendEvent:anEvent];
1838 else if (type == NSEventTypeKeyUp)
1840 uint16_t keyCode = [anEvent keyCode];
1841 if ([self isKeyPressed:keyCode])
1843 WineWindow* window = (WineWindow*)[anEvent window];
1844 [self noteKey:keyCode pressed:FALSE];
1845 if ([window isKindOfClass:[WineWindow class]])
1846 [window postKeyEvent:anEvent];
1849 else if (!useDragNotifications && type == NSEventTypeAppKitDefined)
1851 WineWindow *window = (WineWindow *)[anEvent window];
1852 short subtype = [anEvent subtype];
1854 // These subtypes are not documented but they appear to mean
1855 // "a window is being dragged" and "a window is no longer being
1856 // dragged", respectively.
1857 if ((subtype == 20 || subtype == 21) && [window isKindOfClass:[WineWindow class]])
1858 [self handleWindowDrag:window begin:(subtype == 20)];
1864 - (void) didSendEvent:(NSEvent*)anEvent
1866 NSEventType type = [anEvent type];
1868 if (type == NSEventTypeKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1870 NSUInteger modifiers = [anEvent modifierFlags];
1871 if ((modifiers & NSEventModifierFlagCommand) &&
1872 !(modifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)))
1874 // Command-Tab and Command-Shift-Tab would normally be intercepted
1875 // by the system to switch applications. If we're seeing it, it's
1876 // presumably because we've captured the displays, preventing
1877 // normal application switching. Do it manually.
1878 [self handleCommandTab];
1883 - (void) setupObservations
1885 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1886 NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1887 NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
1889 [nc addObserverForName:NSWindowDidBecomeKeyNotification
1892 usingBlock:^(NSNotification *note){
1893 NSWindow* window = [note object];
1894 [keyWindows removeObjectIdenticalTo:window];
1895 [keyWindows insertObject:window atIndex:0];
1898 [nc addObserverForName:NSWindowWillCloseNotification
1900 queue:[NSOperationQueue mainQueue]
1901 usingBlock:^(NSNotification *note){
1902 NSWindow* window = [note object];
1903 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
1905 [keyWindows removeObjectIdenticalTo:window];
1906 if (window == lastTargetWindow)
1907 lastTargetWindow = nil;
1908 if (window == self.mouseCaptureWindow)
1909 self.mouseCaptureWindow = nil;
1910 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
1912 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
1913 [self updateFullscreenWindows];
1916 [windowsBeingDragged removeObject:window];
1919 if (useDragNotifications) {
1920 [nc addObserverForName:NSWindowWillStartDraggingNotification
1922 queue:[NSOperationQueue mainQueue]
1923 usingBlock:^(NSNotification *note){
1924 NSWindow* window = [note object];
1925 if ([window isKindOfClass:[WineWindow class]])
1926 [self handleWindowDrag:(WineWindow *)window begin:YES];
1929 [nc addObserverForName:NSWindowDidEndDraggingNotification
1931 queue:[NSOperationQueue mainQueue]
1932 usingBlock:^(NSNotification *note){
1933 NSWindow* window = [note object];
1934 if ([window isKindOfClass:[WineWindow class]])
1935 [self handleWindowDrag:(WineWindow *)window begin:NO];
1939 [nc addObserver:self
1940 selector:@selector(keyboardSelectionDidChange)
1941 name:NSTextInputContextKeyboardSelectionDidChangeNotification
1944 /* The above notification isn't sent unless the NSTextInputContext
1945 class has initialized itself. Poke it. */
1946 [NSTextInputContext self];
1948 [wsnc addObserver:self
1949 selector:@selector(activeSpaceDidChange)
1950 name:NSWorkspaceActiveSpaceDidChangeNotification
1953 [nc addObserver:self
1954 selector:@selector(releaseMouseCapture)
1955 name:NSMenuDidBeginTrackingNotification
1958 [dnc addObserver:self
1959 selector:@selector(releaseMouseCapture)
1960 name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
1962 suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
1964 [dnc addObserver:self
1965 selector:@selector(enabledKeyboardInputSourcesChanged)
1966 name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
1969 if ([NSApplication instancesRespondToSelector:@selector(yieldActivationToApplication:)])
1971 /* App activation cooperation, starting in macOS 14 Sonoma. */
1972 [dnc addObserver:self
1973 selector:@selector(otherWineAppWillActivate:)
1974 name:WineAppWillActivateNotification
1976 suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
1980 - (void) otherWineAppWillActivate:(NSNotification *)note
1982 NSProcessInfo *ourProcess;
1984 NSString *ourConfigDir, *otherConfigDir, *ourPrefix, *otherPrefix;
1985 NSRunningApplication *otherApp;
1987 /* No point in yielding if we're not the foreground app. */
1988 if (![NSApp isActive]) return;
1990 /* Ignore requests from ourself, dead processes, and other prefixes. */
1991 ourProcess = [NSProcessInfo processInfo];
1992 otherPID = [note.userInfo[WineActivatingAppPIDKey] integerValue];
1993 if (otherPID == ourProcess.processIdentifier) return;
1995 otherApp = [NSRunningApplication runningApplicationWithProcessIdentifier:otherPID];
1996 if (!otherApp) return;
1998 ourConfigDir = ourProcess.environment[@"WINECONFIGDIR"];
1999 otherConfigDir = note.userInfo[WineActivatingAppConfigDirKey];
2000 if (ourConfigDir.length && otherConfigDir.length &&
2001 ![ourConfigDir isEqualToString:otherConfigDir])
2006 ourPrefix = ourProcess.environment[@"WINEPREFIX"];
2007 otherPrefix = note.userInfo[WineActivatingAppPrefixKey];
2008 if (ourPrefix.length && otherPrefix.length &&
2009 ![ourPrefix isEqualToString:otherPrefix])
2014 /* There's a race condition here. The requesting app sends out
2015 WineAppWillActivateNotification and then activates itself, but since
2016 distributed notifications are asynchronous, we may not have yielded
2017 in time. So we call activateFromApplication: on the other app here,
2018 which will work around that race if it happened. If we didn't hit the
2019 race, the activateFromApplication: call will be a no-op. */
2021 /* We only add this observer if NSApplication responds to the yield
2022 methods, so they're safe to call without checking here. */
2023 [NSApp yieldActivationToApplication:otherApp];
2024 [otherApp activateFromApplication:[NSRunningApplication currentApplication]
2028 - (void) tryToActivateIgnoringOtherApps:(BOOL)ignore
2030 NSProcessInfo *processInfo;
2031 NSString *configDir, *prefix;
2032 NSDictionary *userInfo;
2034 if ([NSApp isActive]) return; /* Nothing to do. */
2037 ![NSApplication instancesRespondToSelector:@selector(yieldActivationToApplication:)])
2039 /* Either we don't need to force activation, or the OS is old enough
2040 that this is our only option. */
2041 [NSApp activateIgnoringOtherApps:ignore];
2045 /* Ask other Wine apps to yield activation to us. */
2046 processInfo = [NSProcessInfo processInfo];
2047 configDir = processInfo.environment[@"WINECONFIGDIR"];
2048 prefix = processInfo.environment[@"WINEPREFIX"];
2050 WineActivatingAppPIDKey: @(processInfo.processIdentifier),
2051 WineActivatingAppPrefixKey: prefix ? prefix : @"",
2052 WineActivatingAppConfigDirKey: configDir ? configDir : @""
2055 [[NSDistributedNotificationCenter defaultCenter]
2056 postNotificationName:WineAppWillActivateNotification
2059 deliverImmediately:YES];
2061 /* This is racy. See the note in otherWineAppWillActivate:. */
2065 - (BOOL) inputSourceIsInputMethod
2067 if (!inputSourceIsInputMethodValid)
2069 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2072 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2073 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2074 CFRelease(inputSource);
2077 inputSourceIsInputMethod = FALSE;
2078 inputSourceIsInputMethodValid = TRUE;
2081 return inputSourceIsInputMethod;
2084 - (void) releaseMouseCapture
2086 // This might be invoked on a background thread by the distributed
2087 // notification center. Shunt it to the main thread.
2088 if (![NSThread isMainThread])
2090 dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2094 if (mouseCaptureWindow)
2096 macdrv_event* event;
2098 event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2099 [mouseCaptureWindow.queue postEvent:event];
2100 macdrv_release_event(event);
2104 - (void) unminimizeWindowIfNoneVisible
2106 if (![self frontWineWindow])
2108 for (WineWindow* window in [NSApp windows])
2110 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2112 [window deminiaturize:self];
2119 - (void) setRetinaMode:(int)mode
2123 [clipCursorHandler setRetinaMode:mode];
2125 for (WineWindow* window in [NSApp windows])
2127 if ([window isKindOfClass:[WineWindow class]])
2128 [window setRetinaMode:mode];
2134 * ---------- NSApplicationDelegate methods ----------
2136 - (void)applicationDidBecomeActive:(NSNotification *)notification
2138 NSNumber* displayID;
2139 NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2141 latentDisplayModes = [[NSMutableDictionary alloc] init];
2142 for (displayID in modesToRealize)
2144 CGDisplayModeRef mode = (CGDisplayModeRef)modesToRealize[displayID];
2145 [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2148 [self updateFullscreenWindows];
2149 [self adjustWindowLevels:YES];
2152 [self unminimizeWindowIfNoneVisible];
2155 // If a Wine process terminates abruptly while it has the display captured
2156 // and switched to a different resolution, Mac OS X will uncapture the
2157 // displays and switch their resolutions back. However, the other Wine
2158 // processes won't have their notion of the desktop rect changed back.
2159 // This can lead them to refuse to draw or acknowledge clicks in certain
2160 // portions of their windows.
2162 // To solve this, we synthesize a displays-changed event whenever we're
2163 // activated. This will provoke a re-synchronization of Wine's notion of
2164 // the desktop rect with the actual state.
2165 [self sendDisplaysChanged:TRUE];
2167 // The cursor probably moved while we were inactive. Accumulated mouse
2168 // movement deltas are invalidated. Make sure the next mouse move event
2169 // starts over from an absolute baseline.
2170 forceNextMouseMoveAbsolute = TRUE;
2173 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2175 primaryScreenHeightValid = FALSE;
2176 [self sendDisplaysChanged:FALSE];
2177 [self adjustWindowLevels];
2179 // When the display configuration changes, the cursor position may jump.
2180 // Accumulated mouse movement deltas are invalidated. Make sure the next
2181 // mouse move event starts over from an absolute baseline.
2182 forceNextMouseMoveAbsolute = TRUE;
2185 - (void)applicationDidResignActive:(NSNotification *)notification
2187 macdrv_event* event;
2188 WineEventQueue* queue;
2190 [self invalidateGotFocusEvents];
2192 event = macdrv_create_event(APP_DEACTIVATED, nil);
2194 [eventQueuesLock lock];
2195 for (queue in eventQueues)
2196 [queue postEvent:event];
2197 [eventQueuesLock unlock];
2199 macdrv_release_event(event);
2201 [self releaseMouseCapture];
2204 - (void) applicationDidUnhide:(NSNotification*)aNotification
2206 [self adjustWindowLevels];
2209 - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2211 // Note that "flag" is often wrong. WineWindows are NSPanels and NSPanels
2212 // don't count as "visible windows" for this purpose.
2213 [self unminimizeWindowIfNoneVisible];
2217 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2219 NSApplicationTerminateReply ret = NSTerminateNow;
2220 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2221 NSAppleEventDescriptor* desc = [m currentAppleEvent];
2222 macdrv_event* event;
2223 WineEventQueue* queue;
2225 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2227 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2230 case kAEReallyLogOut:
2231 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2233 case kAEShowRestartDialog:
2234 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2236 case kAEShowShutdownDialog:
2237 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2240 event->app_quit_requested.reason = QUIT_REASON_NONE;
2244 [eventQueuesLock lock];
2246 if ([eventQueues count])
2248 for (queue in eventQueues)
2249 [queue postEvent:event];
2250 ret = NSTerminateLater;
2253 [eventQueuesLock unlock];
2255 macdrv_release_event(event);
2260 - (void)applicationWillBecomeActive:(NSNotification *)notification
2262 macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2265 [eventQueuesLock lock];
2266 for (WineEventQueue* queue in eventQueues)
2267 [queue postEvent:event];
2268 [eventQueuesLock unlock];
2270 macdrv_release_event(event);
2273 - (void)applicationWillResignActive:(NSNotification *)notification
2275 [self adjustWindowLevels:NO];
2278 /***********************************************************************
2281 * Run-loop-source perform callback. Pull request blocks from the
2282 * array of queued requests and invoke them.
2284 static void PerformRequest(void *info)
2288 WineApplicationController* controller = [WineApplicationController sharedController];
2294 __block dispatch_block_t block;
2296 dispatch_sync(controller->requestsManipQueue, ^{
2297 if ([controller->requests count])
2299 block = (dispatch_block_t)[controller->requests[0] retain];
2300 [controller->requests removeObjectAtIndex:0];
2316 /***********************************************************************
2319 * Run a block on the main thread asynchronously.
2321 void OnMainThreadAsync(dispatch_block_t block)
2323 WineApplicationController* controller = [WineApplicationController sharedController];
2325 block = [block copy];
2326 dispatch_sync(controller->requestsManipQueue, ^{
2327 [controller->requests addObject:block];
2330 CFRunLoopSourceSignal(controller->requestSource);
2331 CFRunLoopWakeUp(CFRunLoopGetMain());
2336 /***********************************************************************
2339 void LogError(const char* func, NSString* format, ...)
2342 va_start(args, format);
2343 LogErrorv(func, format, args);
2347 /***********************************************************************
2350 void LogErrorv(const char* func, NSString* format, va_list args)
2354 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2355 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2360 /***********************************************************************
2361 * macdrv_window_rejected_focus
2363 * Pass focus to the next window that hasn't already rejected this same
2364 * WINDOW_GOT_FOCUS event.
2366 void macdrv_window_rejected_focus(const macdrv_event *event)
2369 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2373 /***********************************************************************
2374 * macdrv_get_input_source_info
2376 * Returns the keyboard layout uchr data, keyboard type and input source.
2378 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2381 TISInputSourceRef inputSourceLayout;
2383 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2384 if (inputSourceLayout)
2386 CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2387 kTISPropertyUnicodeKeyLayoutData);
2388 *uchr = CFDataCreateCopy(NULL, data);
2389 CFRelease(inputSourceLayout);
2391 *keyboard_type = [WineApplicationController sharedController].keyboardType;
2392 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2394 *input_source = TISCopyCurrentKeyboardInputSource();
2399 /***********************************************************************
2402 * Play the beep sound configured by the user in System Preferences.
2404 void macdrv_beep(void)
2406 OnMainThreadAsync(^{
2411 /***********************************************************************
2412 * macdrv_set_display_mode
2414 int macdrv_set_display_mode(const struct macdrv_display* display,
2415 CGDisplayModeRef display_mode)
2420 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2426 /***********************************************************************
2431 * If name is non-NULL, it is a selector for a class method on NSCursor
2432 * identifying the cursor to set. In that case, frames is ignored. If
2433 * name is NULL, then frames is used.
2435 * frames is an array of dictionaries. Each dictionary is a frame of
2436 * an animated cursor. Under the key "image" is a CGImage for the
2437 * frame. Under the key "duration" is a CFNumber time interval, in
2438 * seconds, for how long that frame is presented before proceeding to
2439 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
2440 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2441 * This is the hot spot, measured in pixels down and to the right of the
2442 * top-left corner of the image.
2444 * If the array has exactly 1 element, the cursor is static, not
2445 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
2447 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2451 sel = NSSelectorFromString((NSString*)name);
2454 OnMainThreadAsync(^{
2455 WineApplicationController* controller = [WineApplicationController sharedController];
2456 [controller setCursorWithFrames:nil];
2457 controller.cursor = [NSCursor performSelector:sel];
2458 [controller unhideCursor];
2463 NSArray* nsframes = (NSArray*)frames;
2464 if ([nsframes count])
2466 OnMainThreadAsync(^{
2467 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2472 OnMainThreadAsync(^{
2473 WineApplicationController* controller = [WineApplicationController sharedController];
2474 [controller setCursorWithFrames:nil];
2475 [controller hideCursor];
2481 /***********************************************************************
2482 * macdrv_get_cursor_position
2484 * Obtains the current cursor position. Returns zero on failure,
2485 * non-zero on success.
2487 int macdrv_get_cursor_position(CGPoint *pos)
2490 NSPoint location = [NSEvent mouseLocation];
2491 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2492 *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2498 /***********************************************************************
2499 * macdrv_set_cursor_position
2501 * Sets the cursor position without generating events. Returns zero on
2502 * failure, non-zero on success.
2504 int macdrv_set_cursor_position(CGPoint pos)
2509 ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2515 /***********************************************************************
2516 * macdrv_clip_cursor
2518 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
2519 * to or larger than the whole desktop region, the cursor is unclipped.
2520 * Returns zero on failure, non-zero on success.
2522 int macdrv_clip_cursor(CGRect r)
2527 WineApplicationController* controller = [WineApplicationController sharedController];
2528 BOOL clipping = FALSE;
2531 if (!CGRectIsInfinite(rect))
2532 rect = cgrect_mac_from_win(rect);
2534 if (!CGRectIsInfinite(rect))
2536 NSRect nsrect = NSRectFromCGRect(rect);
2539 /* Convert the rectangle from top-down coords to bottom-up. */
2540 [controller flipRect:&nsrect];
2543 for (screen in [NSScreen screens])
2545 if (!NSContainsRect(nsrect, [screen frame]))
2554 ret = [controller startClippingCursor:rect];
2556 ret = [controller stopClippingCursor];
2562 /***********************************************************************
2563 * macdrv_set_application_icon
2565 * Set the application icon. The images array contains CGImages. If
2566 * there are more than one, then they represent different sizes or
2567 * color depths from the icon resource. If images is NULL or empty,
2568 * restores the default application image.
2570 void macdrv_set_application_icon(CFArrayRef images)
2572 NSArray* imageArray = (NSArray*)images;
2574 OnMainThreadAsync(^{
2575 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2579 /***********************************************************************
2582 void macdrv_quit_reply(int reply)
2585 [NSApp replyToApplicationShouldTerminate:reply];
2589 /***********************************************************************
2590 * macdrv_using_input_method
2592 int macdrv_using_input_method(void)
2597 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2603 /***********************************************************************
2604 * macdrv_set_mouse_capture_window
2606 void macdrv_set_mouse_capture_window(macdrv_window window)
2608 WineWindow* w = (WineWindow*)window;
2610 [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2613 [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2617 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2618 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2619 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2621 /***********************************************************************
2622 * macdrv_create_input_source_list
2624 CFArrayRef macdrv_create_input_source_list(void)
2626 CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2629 CFArrayRef input_list;
2630 CFDictionaryRef filter_dict;
2631 const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2632 const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2635 filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2636 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2637 input_list = TISCreateInputSourceList(filter_dict, false);
2639 for (i = 0; i < CFArrayGetCount(input_list); i++)
2641 TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2642 CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2643 CFDictionaryRef entry;
2644 const void *input_keys[3] = { macdrv_input_source_input_key,
2645 macdrv_input_source_type_key,
2646 macdrv_input_source_lang_key };
2647 const void *input_values[3];
2649 input_values[0] = input;
2650 input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2651 input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2653 entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2654 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2656 CFArrayAppendValue(ret, entry);
2659 CFRelease(input_list);
2660 CFRelease(filter_dict);
2666 int macdrv_select_input_source(TISInputSourceRef input_source)
2668 __block int ret = FALSE;
2671 ret = (TISSelectInputSource(input_source) == noErr);
2677 void macdrv_set_cocoa_retina_mode(int new_mode)
2680 [[WineApplicationController sharedController] setRetinaMode:new_mode];
2684 int macdrv_is_any_wine_window_visible(void)
2686 __block int ret = FALSE;
2689 ret = [[WineApplicationController sharedController] isAnyWineWindowVisible];