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 NSNumber* key = [NSNumber numberWithUnsignedInt:stringID];
82 return [(NSDictionary*)localized_strings objectForKey:key];
86 @implementation WineApplication
88 @synthesize wineController;
90 - (void) sendEvent:(NSEvent*)anEvent
92 if (![wineController handleEvent:anEvent])
94 [super sendEvent:anEvent];
95 [wineController didSendEvent:anEvent];
99 - (void) setWineController:(WineApplicationController*)newController
101 wineController = newController;
102 [self setDelegate:wineController];
108 @interface WineApplicationController ()
110 @property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
111 @property (copy, nonatomic) NSArray* cursorFrames;
112 @property (retain, nonatomic) NSTimer* cursorTimer;
113 @property (retain, nonatomic) NSCursor* cursor;
114 @property (retain, nonatomic) NSImage* applicationIcon;
115 @property (readonly, nonatomic) BOOL inputSourceIsInputMethod;
116 @property (retain, nonatomic) WineWindow* mouseCaptureWindow;
118 - (void) setupObservations;
119 - (void) applicationDidBecomeActive:(NSNotification *)notification;
121 static void PerformRequest(void *info);
126 @implementation WineApplicationController
128 @synthesize keyboardType, lastFlagsChanged;
129 @synthesize applicationIcon;
130 @synthesize cursorFrames, cursorTimer, cursor;
131 @synthesize mouseCaptureWindow;
132 @synthesize lastSetCursorPositionTime;
136 if (self == [WineApplicationController class])
138 NSDictionary* defaults = [NSDictionary dictionaryWithObjectsAndKeys:
139 @"", @"NSQuotedKeystrokeBinding",
140 @"", @"NSRepeatCountBinding",
141 [NSNumber numberWithBool:NO], @"ApplePressAndHoldEnabled",
143 [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
145 if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
146 [NSWindow setAllowsAutomaticWindowTabbing:NO];
150 + (WineApplicationController*) sharedController
152 static WineApplicationController* sharedController;
153 static dispatch_once_t once;
155 dispatch_once(&once, ^{
156 sharedController = [[self alloc] init];
159 return sharedController;
167 CFRunLoopSourceContext context = { 0 };
168 context.perform = PerformRequest;
169 requestSource = CFRunLoopSourceCreate(NULL, 0, &context);
175 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, kCFRunLoopCommonModes);
176 CFRunLoopAddSource(CFRunLoopGetMain(), requestSource, (CFStringRef)WineAppWaitQueryResponseMode);
178 requests = [[NSMutableArray alloc] init];
179 requestsManipQueue = dispatch_queue_create("org.winehq.WineAppRequestManipQueue", NULL);
181 eventQueues = [[NSMutableArray alloc] init];
182 eventQueuesLock = [[NSLock alloc] init];
184 keyWindows = [[NSMutableArray alloc] init];
186 originalDisplayModes = [[NSMutableDictionary alloc] init];
187 latentDisplayModes = [[NSMutableDictionary alloc] init];
189 windowsBeingDragged = [[NSMutableSet alloc] init];
191 // On macOS 10.12+, use notifications to more reliably detect when windows are being dragged.
192 if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)])
194 NSOperatingSystemVersion requiredVersion = { 10, 12, 0 };
195 useDragNotifications = [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:requiredVersion];
198 useDragNotifications = NO;
200 if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
201 !keyWindows || !originalDisplayModes || !latentDisplayModes)
207 [self setupObservations];
209 keyboardType = LMGetKbdType();
211 if ([NSApp isActive])
212 [self applicationDidBecomeActive:nil];
219 [windowsBeingDragged release];
221 [screenFrameCGRects release];
222 [applicationIcon release];
223 [clipCursorHandler release];
224 [cursorTimer release];
225 [cursorFrames release];
226 [latentDisplayModes release];
227 [originalDisplayModes release];
228 [keyWindows release];
229 [eventQueues release];
230 [eventQueuesLock release];
231 if (requestsManipQueue) dispatch_release(requestsManipQueue);
235 CFRunLoopSourceInvalidate(requestSource);
236 CFRelease(requestSource);
241 - (void) transformProcessToForeground:(BOOL)activateIfTransformed
243 if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular)
247 NSString* bundleName;
251 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
253 if (activateIfTransformed)
254 [self tryToActivateIgnoringOtherApps:YES];
256 #if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
257 if (!enable_app_nap && [NSProcessInfo instancesRespondToSelector:@selector(beginActivityWithOptions:reason:)])
259 [[[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep
260 reason:@"Running Windows program"] retain]; // intentional leak
264 mainMenu = [[[NSMenu alloc] init] autorelease];
267 submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINE)] autorelease];
268 bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleNameKey];
270 if ([bundleName length])
271 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_HIDE_APPNAME), bundleName];
273 title = WineLocalizedString(STRING_MENU_ITEM_HIDE);
274 item = [submenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@""];
276 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_HIDE_OTHERS)
277 action:@selector(hideOtherApplications:)
279 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
281 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_SHOW_ALL)
282 action:@selector(unhideAllApplications:)
285 [submenu addItem:[NSMenuItem separatorItem]];
287 if ([bundleName length])
288 title = [NSString stringWithFormat:WineLocalizedString(STRING_MENU_ITEM_QUIT_APPNAME), bundleName];
290 title = WineLocalizedString(STRING_MENU_ITEM_QUIT);
291 item = [submenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
292 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
293 item = [[[NSMenuItem alloc] init] autorelease];
294 [item setTitle:WineLocalizedString(STRING_MENU_WINE)];
295 [item setSubmenu:submenu];
296 [mainMenu addItem:item];
299 submenu = [[[NSMenu alloc] initWithTitle:WineLocalizedString(STRING_MENU_WINDOW)] autorelease];
300 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_MINIMIZE)
301 action:@selector(performMiniaturize:)
303 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ZOOM)
304 action:@selector(performZoom:)
306 item = [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_ENTER_FULL_SCREEN)
307 action:@selector(toggleFullScreen:)
309 [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand |
310 NSEventModifierFlagOption |
311 NSEventModifierFlagControl];
312 [submenu addItem:[NSMenuItem separatorItem]];
313 [submenu addItemWithTitle:WineLocalizedString(STRING_MENU_ITEM_BRING_ALL_TO_FRONT)
314 action:@selector(arrangeInFront:)
316 item = [[[NSMenuItem alloc] init] autorelease];
317 [item setTitle:WineLocalizedString(STRING_MENU_WINDOW)];
318 [item setSubmenu:submenu];
319 [mainMenu addItem:item];
321 [NSApp setMainMenu:mainMenu];
322 [NSApp setWindowsMenu:submenu];
324 [NSApp setApplicationIconImage:self.applicationIcon];
328 - (BOOL) waitUntilQueryDone:(int*)done timeout:(NSDate*)timeout processEvents:(BOOL)processEvents
330 PerformRequest(NULL);
336 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
337 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
339 inMode:NSDefaultRunLoopMode
342 [NSApp sendEvent:event];
346 [[NSRunLoop currentRunLoop] runMode:WineAppWaitQueryResponseMode beforeDate:timeout];
347 } while (!*done && [timeout timeIntervalSinceNow] >= 0);
352 - (BOOL) registerEventQueue:(WineEventQueue*)queue
354 [eventQueuesLock lock];
355 [eventQueues addObject:queue];
356 [eventQueuesLock unlock];
360 - (void) unregisterEventQueue:(WineEventQueue*)queue
362 [eventQueuesLock lock];
363 [eventQueues removeObjectIdenticalTo:queue];
364 [eventQueuesLock unlock];
367 - (void) computeEventTimeAdjustmentFromTicks:(unsigned long long)tickcount uptime:(uint64_t)uptime_ns
369 eventTimeAdjustment = (tickcount / 1000.0) - (uptime_ns / (double)NSEC_PER_SEC);
372 - (double) ticksForEventTime:(NSTimeInterval)eventTime
374 return (eventTime + eventTimeAdjustment) * 1000;
377 /* Invalidate old focus offers across all queues. */
378 - (void) invalidateGotFocusEvents
380 WineEventQueue* queue;
384 [eventQueuesLock lock];
385 for (queue in eventQueues)
387 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_GOT_FOCUS)
390 [eventQueuesLock unlock];
393 - (void) windowGotFocus:(WineWindow*)window
397 [self invalidateGotFocusEvents];
399 event = macdrv_create_event(WINDOW_GOT_FOCUS, window);
400 event->window_got_focus.serial = windowFocusSerial;
402 event->window_got_focus.tried_windows = [triedWindows retain];
404 event->window_got_focus.tried_windows = [[NSMutableSet alloc] init];
405 [window.queue postEvent:event];
406 macdrv_release_event(event);
409 - (void) windowRejectedFocusEvent:(const macdrv_event*)event
411 if (event->window_got_focus.serial == windowFocusSerial)
413 NSMutableArray* windows = [keyWindows mutableCopy];
414 NSNumber* windowNumber;
417 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
419 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
420 if ([window isKindOfClass:[WineWindow class]] && [window screen] &&
421 ![windows containsObject:window])
422 [windows addObject:window];
425 triedWindows = (NSMutableSet*)event->window_got_focus.tried_windows;
426 [triedWindows addObject:(WineWindow*)event->window];
427 for (window in windows)
429 if (![triedWindows containsObject:window] && [window canBecomeKeyWindow])
431 [window makeKeyWindow];
440 static BOOL EqualInputSource(TISInputSourceRef source1, TISInputSourceRef source2)
442 if (!source1 && !source2)
444 if (!source1 || !source2)
446 return CFEqual(source1, source2);
449 - (void) keyboardSelectionDidChange:(BOOL)force
451 TISInputSourceRef inputSource, inputSourceLayout;
455 NSTextInputContext* context = [NSTextInputContext currentInputContext];
456 if (!context || ![context client])
460 inputSource = TISCopyCurrentKeyboardInputSource();
461 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
462 if (!force && EqualInputSource(inputSource, lastKeyboardInputSource) &&
463 EqualInputSource(inputSourceLayout, lastKeyboardLayoutInputSource))
465 if (inputSource) CFRelease(inputSource);
466 if (inputSourceLayout) CFRelease(inputSourceLayout);
470 if (lastKeyboardInputSource)
471 CFRelease(lastKeyboardInputSource);
472 lastKeyboardInputSource = inputSource;
473 if (lastKeyboardLayoutInputSource)
474 CFRelease(lastKeyboardLayoutInputSource);
475 lastKeyboardLayoutInputSource = inputSourceLayout;
477 inputSourceIsInputMethodValid = FALSE;
479 if (inputSourceLayout)
482 uchr = TISGetInputSourceProperty(inputSourceLayout,
483 kTISPropertyUnicodeKeyLayoutData);
487 WineEventQueue* queue;
489 event = macdrv_create_event(KEYBOARD_CHANGED, nil);
490 event->keyboard_changed.keyboard_type = self.keyboardType;
491 event->keyboard_changed.iso_keyboard = (KBGetLayoutType(self.keyboardType) == kKeyboardISO);
492 event->keyboard_changed.uchr = CFDataCreateCopy(NULL, uchr);
493 event->keyboard_changed.input_source = (TISInputSourceRef)CFRetain(inputSource);
495 if (event->keyboard_changed.uchr)
497 [eventQueuesLock lock];
499 for (queue in eventQueues)
500 [queue postEvent:event];
502 [eventQueuesLock unlock];
505 macdrv_release_event(event);
510 - (void) keyboardSelectionDidChange
512 [self keyboardSelectionDidChange:NO];
515 - (void) setKeyboardType:(CGEventSourceKeyboardType)newType
517 if (newType != keyboardType)
519 keyboardType = newType;
520 [self keyboardSelectionDidChange:YES];
524 - (void) enabledKeyboardInputSourcesChanged
526 macdrv_layout_list_needs_update = TRUE;
529 - (CGFloat) primaryScreenHeight
531 if (!primaryScreenHeightValid)
533 NSArray* screens = [NSScreen screens];
534 NSUInteger count = [screens count];
541 primaryScreenHeight = NSHeight([[screens objectAtIndex:0] frame]);
542 primaryScreenHeightValid = TRUE;
544 size = count * sizeof(CGRect);
545 if (!screenFrameCGRects)
546 screenFrameCGRects = [[NSMutableData alloc] initWithLength:size];
548 [screenFrameCGRects setLength:size];
550 rect = [screenFrameCGRects mutableBytes];
551 for (screen in screens)
553 CGRect temp = NSRectToCGRect([screen frame]);
554 temp.origin.y = primaryScreenHeight - CGRectGetMaxY(temp);
559 return 1280; /* arbitrary value */
562 return primaryScreenHeight;
565 - (NSPoint) flippedMouseLocation:(NSPoint)point
567 /* This relies on the fact that Cocoa's mouse location points are
568 actually off by one (precisely because they were flipped from
569 Quartz screen coordinates using this same technique). */
570 point.y = [self primaryScreenHeight] - point.y;
574 - (void) flipRect:(NSRect*)rect
576 // We don't use -primaryScreenHeight here so there's no chance of having
577 // out-of-date cached info. This method is called infrequently enough
578 // that getting the screen height each time is not prohibitively expensive.
579 rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
582 - (WineWindow*) frontWineWindow
584 NSNumber* windowNumber;
585 for (windowNumber in [NSWindow windowNumbersWithOptions:NSWindowNumberListAllSpaces])
587 NSWindow* window = [NSApp windowWithWindowNumber:[windowNumber integerValue]];
588 if ([window isKindOfClass:[WineWindow class]] && [window screen])
589 return (WineWindow*)window;
595 - (void) adjustWindowLevels:(BOOL)active
597 NSArray* windowNumbers;
598 NSMutableArray* wineWindows;
599 NSNumber* windowNumber;
600 NSUInteger nextFloatingIndex = 0;
601 __block NSInteger maxLevel = NSIntegerMin;
602 __block NSInteger maxNonfloatingLevel = NSNormalWindowLevel;
603 __block NSInteger minFloatingLevel = NSFloatingWindowLevel;
604 __block WineWindow* prev = nil;
607 if ([NSApp isHidden]) return;
609 windowNumbers = [NSWindow windowNumbersWithOptions:0];
610 wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
612 // For the most part, we rely on the window server's ordering of the windows
613 // to be authoritative. The one exception is if the "floating" property of
614 // one of the windows has been changed, it may be in the wrong level and thus
615 // in the order. This method is what's supposed to fix that up. So build
616 // a list of Wine windows sorted first by floating-ness and then by order
617 // as indicated by the window server.
618 for (windowNumber in windowNumbers)
620 window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
621 if ([window isKindOfClass:[WineWindow class]])
624 [wineWindows insertObject:window atIndex:nextFloatingIndex++];
626 [wineWindows addObject:window];
630 NSDisableScreenUpdates();
632 // Go from back to front so that all windows in front of one which is
633 // elevated for full-screen are also elevated.
634 [wineWindows enumerateObjectsWithOptions:NSEnumerationReverse
635 usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
636 WineWindow* window = (WineWindow*)obj;
637 NSInteger origLevel = [window level];
638 NSInteger newLevel = [window minimumLevelForActive:active];
642 if (minFloatingLevel <= maxNonfloatingLevel)
643 minFloatingLevel = maxNonfloatingLevel + 1;
644 if (newLevel < minFloatingLevel)
645 newLevel = minFloatingLevel;
648 if (newLevel < maxLevel)
653 if (!window.floating && maxNonfloatingLevel < newLevel)
654 maxNonfloatingLevel = newLevel;
656 if (newLevel != origLevel)
658 [window setLevel:newLevel];
660 if (origLevel < newLevel)
662 // If we increased the level, the window should be toward the
663 // back of its new level (but still ahead of the previous
664 // windows we did this to).
666 [window orderWindow:NSWindowAbove relativeTo:[prev windowNumber]];
668 [window orderBack:nil];
672 // If we decreased the level, we want the window at the top
673 // of its new level. -setLevel: is documented to do that on
674 // its own, but that's buggy on Ventura. Since we're looping
675 // back-to-front here, -orderFront: will do the right thing.
676 [window orderFront:nil];
683 NSEnableScreenUpdates();
685 [wineWindows release];
687 // The above took care of the visible windows on the current space. That
688 // leaves windows on other spaces, minimized windows, and windows which
689 // are not ordered in. We want to leave windows on other spaces alone
690 // so the space remains just as they left it (when viewed in Exposé or
691 // Mission Control, for example). We'll adjust the window levels again
692 // after we switch to another space, anyway. Windows which aren't
693 // ordered in will be handled when we order them in. Minimized windows
694 // on the current space should be set to the level they would have gotten
695 // if they were at the front of the windows with the same floating-ness,
696 // because that's where they'll go if/when they are unminimized. Again,
697 // for good measure we'll adjust window levels again when a window is
699 for (window in [NSApp windows])
701 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized] &&
702 [window isOnActiveSpace])
704 NSInteger origLevel = [window level];
705 NSInteger newLevel = [window minimumLevelForActive:YES];
706 NSInteger maxLevelForType = window.floating ? maxLevel : maxNonfloatingLevel;
708 if (newLevel < maxLevelForType)
709 newLevel = maxLevelForType;
711 if (newLevel != origLevel)
712 [window setLevel:newLevel];
717 - (void) adjustWindowLevels
719 [self adjustWindowLevels:[NSApp isActive]];
722 - (void) updateFullscreenWindows
724 if (capture_displays_for_fullscreen && [NSApp isActive])
726 BOOL anyFullscreen = FALSE;
727 NSNumber* windowNumber;
728 for (windowNumber in [NSWindow windowNumbersWithOptions:0])
730 WineWindow* window = (WineWindow*)[NSApp windowWithWindowNumber:[windowNumber integerValue]];
731 if ([window isKindOfClass:[WineWindow class]] && window.fullscreen)
733 anyFullscreen = TRUE;
740 if ([self areDisplaysCaptured] || CGCaptureAllDisplays() == CGDisplayNoErr)
741 displaysCapturedForFullscreen = TRUE;
743 else if (displaysCapturedForFullscreen)
745 if ([originalDisplayModes count] || CGReleaseAllDisplays() == CGDisplayNoErr)
746 displaysCapturedForFullscreen = FALSE;
751 - (void) activeSpaceDidChange
753 [self updateFullscreenWindows];
754 [self adjustWindowLevels];
757 - (void) sendDisplaysChanged:(BOOL)activating
760 WineEventQueue* queue;
762 event = macdrv_create_event(DISPLAYS_CHANGED, nil);
763 event->displays_changed.activating = activating;
765 [eventQueuesLock lock];
767 // If we're activating, then we just need one of our threads to get the
768 // event, so it can send it directly to the desktop window. Otherwise,
769 // we need all of the threads to get it because we don't know which owns
770 // the desktop window and only that one will do anything with it.
771 if (activating) event->deliver = 1;
773 for (queue in eventQueues)
774 [queue postEvent:event];
775 [eventQueuesLock unlock];
777 macdrv_release_event(event);
780 // We can compare two modes directly using CFEqual, but that may require that
781 // they are identical to a level that we don't need. In particular, when the
782 // OS switches between the integrated and discrete GPUs, the set of display
783 // modes can change in subtle ways. We're interested in whether two modes
784 // match in their most salient features, even if they aren't identical.
785 - (BOOL) mode:(CGDisplayModeRef)mode1 matchesMode:(CGDisplayModeRef)mode2
787 NSString *encoding1, *encoding2;
788 uint32_t ioflags1, ioflags2, different;
789 double refresh1, refresh2;
791 if (CGDisplayModeGetWidth(mode1) != CGDisplayModeGetWidth(mode2)) return FALSE;
792 if (CGDisplayModeGetHeight(mode1) != CGDisplayModeGetHeight(mode2)) return FALSE;
793 if (CGDisplayModeGetPixelWidth(mode1) != CGDisplayModeGetPixelWidth(mode2)) return FALSE;
794 if (CGDisplayModeGetPixelHeight(mode1) != CGDisplayModeGetPixelHeight(mode2)) return FALSE;
796 encoding1 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode1) autorelease];
797 encoding2 = [(NSString*)CGDisplayModeCopyPixelEncoding(mode2) autorelease];
798 if (![encoding1 isEqualToString:encoding2]) return FALSE;
800 ioflags1 = CGDisplayModeGetIOFlags(mode1);
801 ioflags2 = CGDisplayModeGetIOFlags(mode2);
802 different = ioflags1 ^ ioflags2;
803 if (different & (kDisplayModeValidFlag | kDisplayModeSafeFlag | kDisplayModeStretchedFlag |
804 kDisplayModeInterlacedFlag | kDisplayModeTelevisionFlag))
807 refresh1 = CGDisplayModeGetRefreshRate(mode1);
808 if (refresh1 == 0) refresh1 = 60;
809 refresh2 = CGDisplayModeGetRefreshRate(mode2);
810 if (refresh2 == 0) refresh2 = 60;
811 if (fabs(refresh1 - refresh2) > 0.1) return FALSE;
816 - (NSArray*)modesMatchingMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
818 NSMutableArray* ret = [NSMutableArray array];
819 NSDictionary* options = @{ (NSString*)kCGDisplayShowDuplicateLowResolutionModes: @YES };
821 NSArray *modes = [(NSArray*)CGDisplayCopyAllDisplayModes(displayID, (CFDictionaryRef)options) autorelease];
822 for (id candidateModeObject in modes)
824 CGDisplayModeRef candidateMode = (CGDisplayModeRef)candidateModeObject;
825 if ([self mode:candidateMode matchesMode:mode])
826 [ret addObject:candidateModeObject];
831 - (BOOL) setMode:(CGDisplayModeRef)mode forDisplay:(CGDirectDisplayID)displayID
834 NSNumber* displayIDKey = [NSNumber numberWithUnsignedInt:displayID];
835 CGDisplayModeRef originalMode;
837 originalMode = (CGDisplayModeRef)[originalDisplayModes objectForKey:displayIDKey];
839 if (originalMode && [self mode:mode matchesMode:originalMode])
841 if ([originalDisplayModes count] == 1) // If this is the last changed display, do a blanket reset
843 CGRestorePermanentDisplayConfiguration();
844 if (!displaysCapturedForFullscreen)
845 CGReleaseAllDisplays();
846 [originalDisplayModes removeAllObjects];
849 else // ... otherwise, try to restore just the one display
851 for (id modeObject in [self modesMatchingMode:mode forDisplay:displayID])
853 mode = (CGDisplayModeRef)modeObject;
854 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
856 [originalDisplayModes removeObjectForKey:displayIDKey];
865 CGDisplayModeRef currentMode;
868 currentMode = CGDisplayModeRetain((CGDisplayModeRef)[latentDisplayModes objectForKey:displayIDKey]);
870 currentMode = CGDisplayCopyDisplayMode(displayID);
871 if (!currentMode) // Invalid display ID
874 if ([self mode:mode matchesMode:currentMode]) // Already there!
876 CGDisplayModeRelease(currentMode);
880 CGDisplayModeRelease(currentMode);
883 modes = [self modesMatchingMode:mode forDisplay:displayID];
887 [self transformProcessToForeground:YES];
889 BOOL active = [NSApp isActive];
891 if ([originalDisplayModes count] || displaysCapturedForFullscreen ||
892 !active || CGCaptureAllDisplays() == CGDisplayNoErr)
896 // If we get here, we have the displays captured. If we don't
897 // know the original mode of the display, the current mode must
898 // be the original. We should re-query the current mode since
899 // another process could have changed it between when we last
900 // checked and when we captured the displays.
902 originalMode = currentMode = CGDisplayCopyDisplayMode(displayID);
906 for (id modeObject in modes)
908 mode = (CGDisplayModeRef)modeObject;
909 if (CGDisplaySetDisplayMode(displayID, mode, NULL) == CGDisplayNoErr)
916 if (ret && !(currentMode && [self mode:mode matchesMode:currentMode]))
917 [originalDisplayModes setObject:(id)originalMode forKey:displayIDKey];
918 else if (![originalDisplayModes count])
920 CGRestorePermanentDisplayConfiguration();
921 if (!displaysCapturedForFullscreen)
922 CGReleaseAllDisplays();
926 CGDisplayModeRelease(currentMode);
930 [latentDisplayModes setObject:(id)mode forKey:displayIDKey];
937 [self adjustWindowLevels];
942 - (BOOL) areDisplaysCaptured
944 return ([originalDisplayModes count] > 0 || displaysCapturedForFullscreen);
947 - (void) updateCursor:(BOOL)force
949 if (force || lastTargetWindow)
951 if (clientWantsCursorHidden && !cursorHidden)
957 if (!cursorIsCurrent)
960 cursorIsCurrent = TRUE;
963 if (!clientWantsCursorHidden && cursorHidden)
966 cursorHidden = FALSE;
973 [[NSCursor arrowCursor] set];
974 cursorIsCurrent = FALSE;
979 cursorHidden = FALSE;
986 if (!clientWantsCursorHidden)
988 clientWantsCursorHidden = TRUE;
989 [self updateCursor:TRUE];
993 - (void) unhideCursor
995 if (clientWantsCursorHidden)
997 clientWantsCursorHidden = FALSE;
998 [self updateCursor:FALSE];
1002 - (void) setCursor:(NSCursor*)newCursor
1004 if (newCursor != cursor)
1007 cursor = [newCursor retain];
1008 cursorIsCurrent = FALSE;
1009 [self updateCursor:FALSE];
1015 NSDictionary* frame = [cursorFrames objectAtIndex:cursorFrame];
1016 CGImageRef cgimage = (CGImageRef)[frame objectForKey:@"image"];
1017 CGSize size = CGSizeMake(CGImageGetWidth(cgimage), CGImageGetHeight(cgimage));
1018 NSImage* image = [[NSImage alloc] initWithCGImage:cgimage size:NSSizeFromCGSize(cgsize_mac_from_win(size))];
1019 CFDictionaryRef hotSpotDict = (CFDictionaryRef)[frame objectForKey:@"hotSpot"];
1022 if (!CGPointMakeWithDictionaryRepresentation(hotSpotDict, &hotSpot))
1023 hotSpot = CGPointZero;
1024 hotSpot = cgpoint_mac_from_win(hotSpot);
1025 self.cursor = [[[NSCursor alloc] initWithImage:image hotSpot:NSPointFromCGPoint(hotSpot)] autorelease];
1027 [self unhideCursor];
1030 - (void) nextCursorFrame:(NSTimer*)theTimer
1032 NSDictionary* frame;
1033 NSTimeInterval duration;
1037 if (cursorFrame >= [cursorFrames count])
1041 frame = [cursorFrames objectAtIndex:cursorFrame];
1042 duration = [[frame objectForKey:@"duration"] doubleValue];
1043 date = [[theTimer fireDate] dateByAddingTimeInterval:duration];
1044 [cursorTimer setFireDate:date];
1047 - (void) setCursorWithFrames:(NSArray*)frames
1049 if (self.cursorFrames == frames)
1052 self.cursorFrames = frames;
1054 [cursorTimer invalidate];
1055 self.cursorTimer = nil;
1059 if ([frames count] > 1)
1061 NSDictionary* frame = [frames objectAtIndex:0];
1062 NSTimeInterval duration = [[frame objectForKey:@"duration"] doubleValue];
1063 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:duration];
1064 self.cursorTimer = [[[NSTimer alloc] initWithFireDate:date
1067 selector:@selector(nextCursorFrame:)
1069 repeats:YES] autorelease];
1070 [[NSRunLoop currentRunLoop] addTimer:cursorTimer forMode:NSRunLoopCommonModes];
1077 - (void) setApplicationIconFromCGImageArray:(NSArray*)images
1079 NSImage* nsimage = nil;
1083 NSSize bestSize = NSZeroSize;
1086 nsimage = [[[NSImage alloc] initWithSize:NSZeroSize] autorelease];
1088 for (image in images)
1090 CGImageRef cgimage = (CGImageRef)image;
1091 NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
1094 NSSize size = [imageRep size];
1096 [nsimage addRepresentation:imageRep];
1099 if (MIN(size.width, size.height) > MIN(bestSize.width, bestSize.height))
1104 if ([[nsimage representations] count] && bestSize.width && bestSize.height)
1105 [nsimage setSize:bestSize];
1110 self.applicationIcon = nsimage;
1113 - (void) handleCommandTab
1115 if ([NSApp isActive])
1117 NSRunningApplication* thisApp = [NSRunningApplication currentApplication];
1118 NSRunningApplication* app;
1119 NSRunningApplication* otherValidApp = nil;
1121 if ([originalDisplayModes count] || displaysCapturedForFullscreen)
1123 NSNumber* displayID;
1124 for (displayID in originalDisplayModes)
1126 CGDisplayModeRef mode = CGDisplayCopyDisplayMode([displayID unsignedIntValue]);
1127 [latentDisplayModes setObject:(id)mode forKey:displayID];
1128 CGDisplayModeRelease(mode);
1131 CGRestorePermanentDisplayConfiguration();
1132 CGReleaseAllDisplays();
1133 [originalDisplayModes removeAllObjects];
1134 displaysCapturedForFullscreen = FALSE;
1137 for (app in [[NSWorkspace sharedWorkspace] runningApplications])
1139 if (![app isEqual:thisApp] && !app.terminated &&
1140 app.activationPolicy == NSApplicationActivationPolicyRegular)
1144 // There's another visible app. Just hide ourselves and let
1145 // the system activate the other app.
1151 otherValidApp = app;
1155 // Didn't find a visible GUI app. Try the Finder or, if that's not
1156 // running, the first hidden GUI app. If even that doesn't work, we
1157 // just fail to switch and remain the active app.
1158 app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.finder"] lastObject];
1159 if (!app) app = otherValidApp;
1161 [app activateWithOptions:0];
1165 - (BOOL) setCursorPosition:(CGPoint)pos
1169 if ([windowsBeingDragged count])
1171 else if (self.clippingCursor && [clipCursorHandler respondsToSelector:@selector(setCursorPosition:)])
1172 ret = [clipCursorHandler setCursorPosition:pos];
1175 if (self.clippingCursor)
1176 [clipCursorHandler clipCursorLocation:&pos];
1178 // Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
1179 // the mouse from the cursor position for 0.25 seconds. This means
1180 // that mouse movement during that interval doesn't move the cursor
1181 // and events carry a constant location (the warped-to position)
1182 // even though they have delta values. For apps which warp the
1183 // cursor frequently (like after every mouse move), this makes
1184 // cursor movement horribly laggy and jerky, as only a fraction of
1185 // mouse move events have any effect.
1187 // On some versions of OS X, it's sufficient to forcibly reassociate
1188 // the mouse and cursor position. On others, it's necessary to set
1189 // the local events suppression interval to 0 for the warp. That's
1190 // deprecated, but I'm not aware of any other way. For good
1191 // measure, we do both.
1192 CGSetLocalEventsSuppressionInterval(0);
1193 ret = (CGWarpMouseCursorPosition(pos) == kCGErrorSuccess);
1194 CGSetLocalEventsSuppressionInterval(0.25);
1197 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1199 CGAssociateMouseAndMouseCursorPosition(true);
1205 WineEventQueue* queue;
1207 // Discard all pending mouse move events.
1208 [eventQueuesLock lock];
1209 for (queue in eventQueues)
1211 [queue discardEventsMatchingMask:event_mask_for_type(MOUSE_MOVED_RELATIVE) |
1212 event_mask_for_type(MOUSE_MOVED_ABSOLUTE)
1214 [queue resetMouseEventPositions:pos];
1216 [eventQueuesLock unlock];
1222 - (void) updateWindowsForCursorClipping
1225 for (window in [NSApp windows])
1227 if ([window isKindOfClass:[WineWindow class]])
1228 [window updateForCursorClipping];
1232 - (BOOL) startClippingCursor:(CGRect)rect
1234 if (!clipCursorHandler) {
1235 if (use_confinement_cursor_clipping && [WineConfinementClipCursorHandler isAvailable])
1236 clipCursorHandler = [[WineConfinementClipCursorHandler alloc] init];
1238 clipCursorHandler = [[WineEventTapClipCursorHandler alloc] init];
1241 if (self.clippingCursor && CGRectEqualToRect(rect, clipCursorHandler.cursorClipRect))
1244 if (![clipCursorHandler startClippingCursor:rect])
1247 [self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
1249 [self updateWindowsForCursorClipping];
1254 - (BOOL) stopClippingCursor
1256 if (!self.clippingCursor)
1259 if (![clipCursorHandler stopClippingCursor])
1262 lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
1264 [self updateWindowsForCursorClipping];
1269 - (BOOL) clippingCursor
1271 return clipCursorHandler.clippingCursor;
1274 - (BOOL) isKeyPressed:(uint16_t)keyCode
1276 int bits = sizeof(pressedKeyCodes[0]) * 8;
1277 int index = keyCode / bits;
1278 uint32_t mask = 1 << (keyCode % bits);
1279 return (pressedKeyCodes[index] & mask) != 0;
1282 - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed
1284 int bits = sizeof(pressedKeyCodes[0]) * 8;
1285 int index = keyCode / bits;
1286 uint32_t mask = 1 << (keyCode % bits);
1288 pressedKeyCodes[index] |= mask;
1290 pressedKeyCodes[index] &= ~mask;
1293 - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged
1296 [windowsBeingDragged addObject:window];
1298 [windowsBeingDragged removeObject:window];
1301 - (void) windowWillOrderOut:(WineWindow*)window
1303 if ([windowsBeingDragged containsObject:window])
1305 [self window:window isBeingDragged:NO];
1307 macdrv_event* event = macdrv_create_event(WINDOW_DRAG_END, window);
1308 [window.queue postEvent:event];
1309 macdrv_release_event(event);
1313 - (BOOL) isAnyWineWindowVisible
1315 for (WineWindow* w in [NSApp windows])
1317 if ([w isKindOfClass:[WineWindow class]] && ![w isMiniaturized] && [w isVisible])
1324 - (void) handleWindowDrag:(WineWindow*)window begin:(BOOL)begin
1326 macdrv_event* event;
1331 [windowsBeingDragged addObject:window];
1332 eventType = WINDOW_DRAG_BEGIN;
1336 [windowsBeingDragged removeObject:window];
1337 eventType = WINDOW_DRAG_END;
1340 event = macdrv_create_event(eventType, window);
1341 if (eventType == WINDOW_DRAG_BEGIN)
1342 event->window_drag_begin.no_activate = [NSEvent wine_commandKeyDown];
1343 [window.queue postEvent:event];
1344 macdrv_release_event(event);
1347 - (void) handleMouseMove:(NSEvent*)anEvent
1349 WineWindow* targetWindow;
1350 BOOL drag = [anEvent type] != NSEventTypeMouseMoved;
1352 if ([windowsBeingDragged count])
1354 else if (mouseCaptureWindow)
1355 targetWindow = mouseCaptureWindow;
1357 targetWindow = (WineWindow*)[anEvent window];
1360 /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
1361 event indicates its window is the main window, even if the cursor is
1362 over a different window. Find the actual WineWindow that is under the
1363 cursor and post the event as being for that window. */
1364 CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
1365 NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
1366 NSInteger windowUnderNumber;
1368 windowUnderNumber = [NSWindow windowNumberAtPoint:point
1369 belowWindowWithWindowNumber:0];
1370 targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
1371 if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
1375 if ([targetWindow isKindOfClass:[WineWindow class]])
1377 CGPoint point = CGEventGetLocation([anEvent CGEvent]);
1378 macdrv_event* event;
1381 // If we recently warped the cursor (other than in our cursor-clipping
1382 // event tap), discard mouse move events until we see an event which is
1383 // later than that time.
1384 if (lastSetCursorPositionTime)
1386 if ([anEvent timestamp] <= lastSetCursorPositionTime)
1389 lastSetCursorPositionTime = 0;
1390 forceNextMouseMoveAbsolute = TRUE;
1393 if (forceNextMouseMoveAbsolute || targetWindow != lastTargetWindow)
1396 forceNextMouseMoveAbsolute = FALSE;
1400 // Send absolute move events if the cursor is in the interior of
1401 // its range. Only send relative moves if the cursor is pinned to
1402 // the boundaries of where it can go. We compute the position
1403 // that's one additional point in the direction of movement. If
1404 // that is outside of the clipping rect or desktop region (the
1405 // union of the screen frames), then we figure the cursor would
1406 // have moved outside if it could but it was pinned.
1407 CGPoint computedPoint = point;
1408 CGFloat deltaX = [anEvent deltaX];
1409 CGFloat deltaY = [anEvent deltaY];
1413 else if (deltaX < -0.001)
1418 else if (deltaY < -0.001)
1421 // Assume cursor is pinned for now
1423 if (!self.clippingCursor || CGRectContainsPoint(clipCursorHandler.cursorClipRect, computedPoint))
1425 const CGRect* rects;
1426 NSUInteger count, i;
1428 // Caches screenFrameCGRects if necessary
1429 [self primaryScreenHeight];
1431 rects = [screenFrameCGRects bytes];
1432 count = [screenFrameCGRects length] / sizeof(rects[0]);
1434 for (i = 0; i < count; i++)
1436 if (CGRectContainsPoint(rects[i], computedPoint))
1447 if (self.clippingCursor)
1448 [clipCursorHandler clipCursorLocation:&point];
1449 point = cgpoint_win_from_mac(point);
1451 event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
1452 event->mouse_moved.x = floor(point.x);
1453 event->mouse_moved.y = floor(point.y);
1455 mouseMoveDeltaX = 0;
1456 mouseMoveDeltaY = 0;
1460 double scale = retina_on ? 2 : 1;
1462 /* Add event delta to accumulated delta error */
1463 /* deltaY is already flipped */
1464 mouseMoveDeltaX += [anEvent deltaX];
1465 mouseMoveDeltaY += [anEvent deltaY];
1467 event = macdrv_create_event(MOUSE_MOVED_RELATIVE, targetWindow);
1468 event->mouse_moved.x = mouseMoveDeltaX * scale;
1469 event->mouse_moved.y = mouseMoveDeltaY * scale;
1471 /* Keep the remainder after integer truncation. */
1472 mouseMoveDeltaX -= event->mouse_moved.x / scale;
1473 mouseMoveDeltaY -= event->mouse_moved.y / scale;
1476 if (event->type == MOUSE_MOVED_ABSOLUTE || event->mouse_moved.x || event->mouse_moved.y)
1478 event->mouse_moved.time_ms = [self ticksForEventTime:[anEvent timestamp]];
1479 event->mouse_moved.drag = drag;
1481 [targetWindow.queue postEvent:event];
1484 macdrv_release_event(event);
1486 lastTargetWindow = targetWindow;
1489 lastTargetWindow = nil;
1491 [self updateCursor:FALSE];
1494 - (void) handleMouseButton:(NSEvent*)theEvent
1496 WineWindow* window = (WineWindow*)[theEvent window];
1497 NSEventType type = [theEvent type];
1498 WineWindow* windowBroughtForward = nil;
1499 BOOL process = FALSE;
1501 if ([window isKindOfClass:[WineWindow class]] &&
1502 type == NSEventTypeLeftMouseDown &&
1503 ![theEvent wine_commandKeyDown])
1505 NSWindowButton windowButton;
1507 windowBroughtForward = window;
1509 /* Any left-click on our window anyplace other than the close or
1510 minimize buttons will bring it forward. */
1511 for (windowButton = NSWindowCloseButton;
1512 windowButton <= NSWindowMiniaturizeButton;
1515 NSButton* button = [window standardWindowButton:windowButton];
1518 NSPoint point = [button convertPoint:[theEvent locationInWindow] fromView:nil];
1519 if ([button mouse:point inRect:[button bounds]])
1521 windowBroughtForward = nil;
1528 if ([windowsBeingDragged count])
1530 else if (mouseCaptureWindow)
1531 window = mouseCaptureWindow;
1533 if ([window isKindOfClass:[WineWindow class]])
1535 BOOL pressed = (type == NSEventTypeLeftMouseDown ||
1536 type == NSEventTypeRightMouseDown ||
1537 type == NSEventTypeOtherMouseDown);
1538 CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
1540 if (self.clippingCursor)
1541 [clipCursorHandler clipCursorLocation:&pt];
1545 if (mouseCaptureWindow)
1549 // Test if the click was in the window's content area.
1550 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1551 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1552 process = NSMouseInRect(nspoint, contentRect, NO);
1553 if (process && [window styleMask] & NSWindowStyleMaskResizable)
1555 // Ignore clicks in the grow box (resize widget).
1556 HIPoint origin = { 0, 0 };
1557 HIThemeGrowBoxDrawInfo info = { 0 };
1561 info.kind = kHIThemeGrowBoxKindNormal;
1562 info.direction = kThemeGrowRight | kThemeGrowDown;
1563 if ([window styleMask] & NSWindowStyleMaskUtilityWindow)
1564 info.size = kHIThemeGrowBoxSizeSmall;
1566 info.size = kHIThemeGrowBoxSizeNormal;
1568 status = HIThemeGetGrowBoxBounds(&origin, &info, &bounds);
1569 if (status == noErr)
1571 NSRect growBox = NSMakeRect(NSMaxX(contentRect) - bounds.size.width,
1572 NSMinY(contentRect),
1574 bounds.size.height);
1575 process = !NSMouseInRect(nspoint, growBox, NO);
1580 unmatchedMouseDowns |= NSEventMaskFromType(type);
1584 NSEventType downType = type - 1;
1585 NSUInteger downMask = NSEventMaskFromType(downType);
1586 process = (unmatchedMouseDowns & downMask) != 0;
1587 unmatchedMouseDowns &= ~downMask;
1592 macdrv_event* event;
1594 pt = cgpoint_win_from_mac(pt);
1596 event = macdrv_create_event(MOUSE_BUTTON, window);
1597 event->mouse_button.button = [theEvent buttonNumber];
1598 event->mouse_button.pressed = pressed;
1599 event->mouse_button.x = floor(pt.x);
1600 event->mouse_button.y = floor(pt.y);
1601 event->mouse_button.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1603 [window.queue postEvent:event];
1605 macdrv_release_event(event);
1609 if (windowBroughtForward)
1611 WineWindow* ancestor = [windowBroughtForward ancestorWineWindow];
1612 NSInteger ancestorNumber = [ancestor windowNumber];
1613 NSInteger ancestorLevel = [ancestor level];
1615 for (NSNumber* windowNumberObject in [NSWindow windowNumbersWithOptions:0])
1617 NSInteger windowNumber = [windowNumberObject integerValue];
1618 if (windowNumber == ancestorNumber)
1620 WineWindow* otherWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowNumber];
1621 if ([otherWindow isKindOfClass:[WineWindow class]] && [otherWindow screen] &&
1622 [otherWindow level] <= ancestorLevel && otherWindow == [otherWindow ancestorWineWindow])
1624 [ancestor postBroughtForwardEvent];
1628 if (!process && ![windowBroughtForward isKeyWindow] && !windowBroughtForward.disabled && !windowBroughtForward.noForeground)
1629 [self windowGotFocus:windowBroughtForward];
1632 // Since mouse button events deliver absolute cursor position, the
1633 // accumulating delta from move events is invalidated. Make sure
1634 // next mouse move event starts over from an absolute baseline.
1635 // Also, it's at least possible that the title bar widgets (e.g. close
1636 // button, etc.) could enter an internal event loop on a mouse down that
1637 // wouldn't exit until a mouse up. In that case, we'd miss any mouse
1638 // dragged events and, after that, any notion of the cursor position
1639 // computed from accumulating deltas would be wrong.
1640 forceNextMouseMoveAbsolute = TRUE;
1643 - (void) handleScrollWheel:(NSEvent*)theEvent
1647 if (mouseCaptureWindow)
1648 window = mouseCaptureWindow;
1650 window = (WineWindow*)[theEvent window];
1652 if ([window isKindOfClass:[WineWindow class]])
1654 CGEventRef cgevent = [theEvent CGEvent];
1655 CGPoint pt = CGEventGetLocation(cgevent);
1658 if (self.clippingCursor)
1659 [clipCursorHandler clipCursorLocation:&pt];
1661 if (mouseCaptureWindow)
1665 // Only process the event if it was in the window's content area.
1666 NSPoint nspoint = [self flippedMouseLocation:NSPointFromCGPoint(pt)];
1667 NSRect contentRect = [window contentRectForFrameRect:[window frame]];
1668 process = NSMouseInRect(nspoint, contentRect, NO);
1673 macdrv_event* event;
1675 BOOL continuous = FALSE;
1677 pt = cgpoint_win_from_mac(pt);
1679 event = macdrv_create_event(MOUSE_SCROLL, window);
1680 event->mouse_scroll.x = floor(pt.x);
1681 event->mouse_scroll.y = floor(pt.y);
1682 event->mouse_scroll.time_ms = [self ticksForEventTime:[theEvent timestamp]];
1684 if (CGEventGetIntegerValueField(cgevent, kCGScrollWheelEventIsContinuous))
1688 /* Continuous scroll wheel events come from high-precision scrolling
1689 hardware like Apple's Magic Mouse, Mighty Mouse, and trackpads.
1690 For these, we can get more precise data from the CGEvent API. */
1691 /* Axis 1 is vertical, axis 2 is horizontal. */
1692 x = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis2);
1693 y = CGEventGetDoubleValueField(cgevent, kCGScrollWheelEventPointDeltaAxis1);
1697 double pixelsPerLine = 10;
1698 CGEventSourceRef source;
1700 /* The non-continuous values are in units of "lines", not pixels. */
1701 if ((source = CGEventCreateSourceFromEvent(cgevent)))
1703 pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
1707 x = pixelsPerLine * [theEvent deltaX];
1708 y = pixelsPerLine * [theEvent deltaY];
1711 /* Mac: negative is right or down, positive is left or up.
1712 Win32: negative is left or down, positive is right or up.
1713 So, negate the X scroll value to translate. */
1716 /* The x,y values so far are in pixels. Win32 expects to receive some
1717 fraction of WHEEL_DELTA == 120. By my estimation, that's roughly
1718 6 times the pixel value. */
1722 if (use_precise_scrolling)
1724 event->mouse_scroll.x_scroll = x;
1725 event->mouse_scroll.y_scroll = y;
1729 /* For non-continuous "clicky" wheels, if there was any motion, make
1730 sure there was at least WHEEL_DELTA motion. This is so, at slow
1731 speeds where the system's acceleration curve is actually reducing the
1732 scroll distance, the user is sure to get some action out of each click.
1733 For example, this is important for rotating though weapons in a
1734 first-person shooter. */
1735 if (0 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 120)
1736 event->mouse_scroll.x_scroll = 120;
1737 else if (-120 < event->mouse_scroll.x_scroll && event->mouse_scroll.x_scroll < 0)
1738 event->mouse_scroll.x_scroll = -120;
1740 if (0 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 120)
1741 event->mouse_scroll.y_scroll = 120;
1742 else if (-120 < event->mouse_scroll.y_scroll && event->mouse_scroll.y_scroll < 0)
1743 event->mouse_scroll.y_scroll = -120;
1748 /* If it's been a while since the last scroll event or if the scrolling has
1749 reversed direction, reset the accumulated scroll value. */
1750 if ([theEvent timestamp] - lastScrollTime > 1)
1751 accumScrollX = accumScrollY = 0;
1754 /* The accumulated scroll value is in the opposite direction/sign of the last
1755 scroll. That's because it's the "debt" resulting from over-scrolling in
1756 that direction. We accumulate by adding in the scroll amount and then, if
1757 it has the same sign as the scroll value, we subtract any whole or partial
1758 WHEEL_DELTAs, leaving it 0 or the opposite sign. So, the user switched
1759 scroll direction if the accumulated debt and the new scroll value have the
1761 if ((accumScrollX < 0 && x < 0) || (accumScrollX > 0 && x > 0))
1763 if ((accumScrollY < 0 && y < 0) || (accumScrollY > 0 && y > 0))
1766 lastScrollTime = [theEvent timestamp];
1771 if (accumScrollX > 0 && x > 0)
1772 event->mouse_scroll.x_scroll = 120 * ceil(accumScrollX / 120);
1773 if (accumScrollX < 0 && x < 0)
1774 event->mouse_scroll.x_scroll = 120 * -ceil(-accumScrollX / 120);
1775 if (accumScrollY > 0 && y > 0)
1776 event->mouse_scroll.y_scroll = 120 * ceil(accumScrollY / 120);
1777 if (accumScrollY < 0 && y < 0)
1778 event->mouse_scroll.y_scroll = 120 * -ceil(-accumScrollY / 120);
1780 accumScrollX -= event->mouse_scroll.x_scroll;
1781 accumScrollY -= event->mouse_scroll.y_scroll;
1784 if (event->mouse_scroll.x_scroll || event->mouse_scroll.y_scroll)
1785 [window.queue postEvent:event];
1787 macdrv_release_event(event);
1789 // Since scroll wheel events deliver absolute cursor position, the
1790 // accumulating delta from move events is invalidated. Make sure next
1791 // mouse move event starts over from an absolute baseline.
1792 forceNextMouseMoveAbsolute = TRUE;
1797 // Returns TRUE if the event was handled and caller should do nothing more
1798 // with it. Returns FALSE if the caller should process it as normal and
1799 // then call -didSendEvent:.
1800 - (BOOL) handleEvent:(NSEvent*)anEvent
1803 NSEventType type = [anEvent type];
1805 if (type == NSEventTypeFlagsChanged)
1806 self.lastFlagsChanged = anEvent;
1807 else if (type == NSEventTypeMouseMoved || type == NSEventTypeLeftMouseDragged ||
1808 type == NSEventTypeRightMouseDragged || type == NSEventTypeOtherMouseDragged)
1810 [self handleMouseMove:anEvent];
1811 ret = mouseCaptureWindow && ![windowsBeingDragged count];
1813 else if (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp ||
1814 type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp ||
1815 type == NSEventTypeOtherMouseDown || type == NSEventTypeOtherMouseUp)
1817 [self handleMouseButton:anEvent];
1818 ret = mouseCaptureWindow && ![windowsBeingDragged count];
1820 else if (type == NSEventTypeScrollWheel)
1822 [self handleScrollWheel:anEvent];
1823 ret = mouseCaptureWindow != nil;
1825 else if (type == NSEventTypeKeyDown)
1827 // -[NSApplication sendEvent:] seems to consume presses of the Help
1828 // key (Insert key on PC keyboards), so we have to bypass it and
1829 // send the event directly to the window.
1830 if (anEvent.keyCode == kVK_Help)
1832 [anEvent.window sendEvent:anEvent];
1836 else if (type == NSEventTypeKeyUp)
1838 uint16_t keyCode = [anEvent keyCode];
1839 if ([self isKeyPressed:keyCode])
1841 WineWindow* window = (WineWindow*)[anEvent window];
1842 [self noteKey:keyCode pressed:FALSE];
1843 if ([window isKindOfClass:[WineWindow class]])
1844 [window postKeyEvent:anEvent];
1847 else if (!useDragNotifications && type == NSEventTypeAppKitDefined)
1849 WineWindow *window = (WineWindow *)[anEvent window];
1850 short subtype = [anEvent subtype];
1852 // These subtypes are not documented but they appear to mean
1853 // "a window is being dragged" and "a window is no longer being
1854 // dragged", respectively.
1855 if ((subtype == 20 || subtype == 21) && [window isKindOfClass:[WineWindow class]])
1856 [self handleWindowDrag:window begin:(subtype == 20)];
1862 - (void) didSendEvent:(NSEvent*)anEvent
1864 NSEventType type = [anEvent type];
1866 if (type == NSEventTypeKeyDown && ![anEvent isARepeat] && [anEvent keyCode] == kVK_Tab)
1868 NSUInteger modifiers = [anEvent modifierFlags];
1869 if ((modifiers & NSEventModifierFlagCommand) &&
1870 !(modifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)))
1872 // Command-Tab and Command-Shift-Tab would normally be intercepted
1873 // by the system to switch applications. If we're seeing it, it's
1874 // presumably because we've captured the displays, preventing
1875 // normal application switching. Do it manually.
1876 [self handleCommandTab];
1881 - (void) setupObservations
1883 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
1884 NSNotificationCenter* wsnc = [[NSWorkspace sharedWorkspace] notificationCenter];
1885 NSDistributedNotificationCenter* dnc = [NSDistributedNotificationCenter defaultCenter];
1887 [nc addObserverForName:NSWindowDidBecomeKeyNotification
1890 usingBlock:^(NSNotification *note){
1891 NSWindow* window = [note object];
1892 [keyWindows removeObjectIdenticalTo:window];
1893 [keyWindows insertObject:window atIndex:0];
1896 [nc addObserverForName:NSWindowWillCloseNotification
1898 queue:[NSOperationQueue mainQueue]
1899 usingBlock:^(NSNotification *note){
1900 NSWindow* window = [note object];
1901 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFakingClose])
1903 [keyWindows removeObjectIdenticalTo:window];
1904 if (window == lastTargetWindow)
1905 lastTargetWindow = nil;
1906 if (window == self.mouseCaptureWindow)
1907 self.mouseCaptureWindow = nil;
1908 if ([window isKindOfClass:[WineWindow class]] && [(WineWindow*)window isFullscreen])
1910 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
1911 [self updateFullscreenWindows];
1914 [windowsBeingDragged removeObject:window];
1917 if (useDragNotifications) {
1918 [nc addObserverForName:NSWindowWillStartDraggingNotification
1920 queue:[NSOperationQueue mainQueue]
1921 usingBlock:^(NSNotification *note){
1922 NSWindow* window = [note object];
1923 if ([window isKindOfClass:[WineWindow class]])
1924 [self handleWindowDrag:(WineWindow *)window begin:YES];
1927 [nc addObserverForName:NSWindowDidEndDraggingNotification
1929 queue:[NSOperationQueue mainQueue]
1930 usingBlock:^(NSNotification *note){
1931 NSWindow* window = [note object];
1932 if ([window isKindOfClass:[WineWindow class]])
1933 [self handleWindowDrag:(WineWindow *)window begin:NO];
1937 [nc addObserver:self
1938 selector:@selector(keyboardSelectionDidChange)
1939 name:NSTextInputContextKeyboardSelectionDidChangeNotification
1942 /* The above notification isn't sent unless the NSTextInputContext
1943 class has initialized itself. Poke it. */
1944 [NSTextInputContext self];
1946 [wsnc addObserver:self
1947 selector:@selector(activeSpaceDidChange)
1948 name:NSWorkspaceActiveSpaceDidChangeNotification
1951 [nc addObserver:self
1952 selector:@selector(releaseMouseCapture)
1953 name:NSMenuDidBeginTrackingNotification
1956 [dnc addObserver:self
1957 selector:@selector(releaseMouseCapture)
1958 name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
1960 suspensionBehavior:NSNotificationSuspensionBehaviorDrop];
1962 [dnc addObserver:self
1963 selector:@selector(enabledKeyboardInputSourcesChanged)
1964 name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged
1967 if ([NSApplication instancesRespondToSelector:@selector(yieldActivationToApplication:)])
1969 /* App activation cooperation, starting in macOS 14 Sonoma. */
1970 [dnc addObserver:self
1971 selector:@selector(otherWineAppWillActivate:)
1972 name:WineAppWillActivateNotification
1974 suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
1978 - (void) otherWineAppWillActivate:(NSNotification *)note
1980 NSProcessInfo *ourProcess;
1982 NSString *ourConfigDir, *otherConfigDir, *ourPrefix, *otherPrefix;
1983 NSRunningApplication *otherApp;
1985 /* No point in yielding if we're not the foreground app. */
1986 if (![NSApp isActive]) return;
1988 /* Ignore requests from ourself, dead processes, and other prefixes. */
1989 ourProcess = [NSProcessInfo processInfo];
1990 otherPID = [note.userInfo[WineActivatingAppPIDKey] integerValue];
1991 if (otherPID == ourProcess.processIdentifier) return;
1993 otherApp = [NSRunningApplication runningApplicationWithProcessIdentifier:otherPID];
1994 if (!otherApp) return;
1996 ourConfigDir = ourProcess.environment[@"WINECONFIGDIR"];
1997 otherConfigDir = note.userInfo[WineActivatingAppConfigDirKey];
1998 if (ourConfigDir.length && otherConfigDir.length &&
1999 ![ourConfigDir isEqualToString:otherConfigDir])
2004 ourPrefix = ourProcess.environment[@"WINEPREFIX"];
2005 otherPrefix = note.userInfo[WineActivatingAppPrefixKey];
2006 if (ourPrefix.length && otherPrefix.length &&
2007 ![ourPrefix isEqualToString:otherPrefix])
2012 /* There's a race condition here. The requesting app sends out
2013 WineAppWillActivateNotification and then activates itself, but since
2014 distributed notifications are asynchronous, we may not have yielded
2015 in time. So we call activateFromApplication: on the other app here,
2016 which will work around that race if it happened. If we didn't hit the
2017 race, the activateFromApplication: call will be a no-op. */
2019 /* We only add this observer if NSApplication responds to the yield
2020 methods, so they're safe to call without checking here. */
2021 [NSApp yieldActivationToApplication:otherApp];
2022 [otherApp activateFromApplication:[NSRunningApplication currentApplication]
2026 - (void) tryToActivateIgnoringOtherApps:(BOOL)ignore
2028 NSProcessInfo *processInfo;
2029 NSString *configDir, *prefix;
2030 NSDictionary *userInfo;
2032 if ([NSApp isActive]) return; /* Nothing to do. */
2035 ![NSApplication instancesRespondToSelector:@selector(yieldActivationToApplication:)])
2037 /* Either we don't need to force activation, or the OS is old enough
2038 that this is our only option. */
2039 [NSApp activateIgnoringOtherApps:ignore];
2043 /* Ask other Wine apps to yield activation to us. */
2044 processInfo = [NSProcessInfo processInfo];
2045 configDir = processInfo.environment[@"WINECONFIGDIR"];
2046 prefix = processInfo.environment[@"WINEPREFIX"];
2048 WineActivatingAppPIDKey: @(processInfo.processIdentifier),
2049 WineActivatingAppPrefixKey: prefix ? prefix : @"",
2050 WineActivatingAppConfigDirKey: configDir ? configDir : @""
2053 [[NSDistributedNotificationCenter defaultCenter]
2054 postNotificationName:WineAppWillActivateNotification
2057 deliverImmediately:YES];
2059 /* This is racy. See the note in otherWineAppWillActivate:. */
2063 - (BOOL) inputSourceIsInputMethod
2065 if (!inputSourceIsInputMethodValid)
2067 TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
2070 CFStringRef type = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceType);
2071 inputSourceIsInputMethod = !CFEqual(type, kTISTypeKeyboardLayout);
2072 CFRelease(inputSource);
2075 inputSourceIsInputMethod = FALSE;
2076 inputSourceIsInputMethodValid = TRUE;
2079 return inputSourceIsInputMethod;
2082 - (void) releaseMouseCapture
2084 // This might be invoked on a background thread by the distributed
2085 // notification center. Shunt it to the main thread.
2086 if (![NSThread isMainThread])
2088 dispatch_async(dispatch_get_main_queue(), ^{ [self releaseMouseCapture]; });
2092 if (mouseCaptureWindow)
2094 macdrv_event* event;
2096 event = macdrv_create_event(RELEASE_CAPTURE, mouseCaptureWindow);
2097 [mouseCaptureWindow.queue postEvent:event];
2098 macdrv_release_event(event);
2102 - (void) unminimizeWindowIfNoneVisible
2104 if (![self frontWineWindow])
2106 for (WineWindow* window in [NSApp windows])
2108 if ([window isKindOfClass:[WineWindow class]] && [window isMiniaturized])
2110 [window deminiaturize:self];
2117 - (void) setRetinaMode:(int)mode
2121 [clipCursorHandler setRetinaMode:mode];
2123 for (WineWindow* window in [NSApp windows])
2125 if ([window isKindOfClass:[WineWindow class]])
2126 [window setRetinaMode:mode];
2132 * ---------- NSApplicationDelegate methods ----------
2134 - (void)applicationDidBecomeActive:(NSNotification *)notification
2136 NSNumber* displayID;
2137 NSDictionary* modesToRealize = [latentDisplayModes autorelease];
2139 latentDisplayModes = [[NSMutableDictionary alloc] init];
2140 for (displayID in modesToRealize)
2142 CGDisplayModeRef mode = (CGDisplayModeRef)[modesToRealize objectForKey:displayID];
2143 [self setMode:mode forDisplay:[displayID unsignedIntValue]];
2146 [self updateFullscreenWindows];
2147 [self adjustWindowLevels:YES];
2150 [self unminimizeWindowIfNoneVisible];
2153 // If a Wine process terminates abruptly while it has the display captured
2154 // and switched to a different resolution, Mac OS X will uncapture the
2155 // displays and switch their resolutions back. However, the other Wine
2156 // processes won't have their notion of the desktop rect changed back.
2157 // This can lead them to refuse to draw or acknowledge clicks in certain
2158 // portions of their windows.
2160 // To solve this, we synthesize a displays-changed event whenever we're
2161 // activated. This will provoke a re-synchronization of Wine's notion of
2162 // the desktop rect with the actual state.
2163 [self sendDisplaysChanged:TRUE];
2165 // The cursor probably moved while we were inactive. Accumulated mouse
2166 // movement deltas are invalidated. Make sure the next mouse move event
2167 // starts over from an absolute baseline.
2168 forceNextMouseMoveAbsolute = TRUE;
2171 - (void)applicationDidChangeScreenParameters:(NSNotification *)notification
2173 primaryScreenHeightValid = FALSE;
2174 [self sendDisplaysChanged:FALSE];
2175 [self adjustWindowLevels];
2177 // When the display configuration changes, the cursor position may jump.
2178 // Accumulated mouse movement deltas are invalidated. Make sure the next
2179 // mouse move event starts over from an absolute baseline.
2180 forceNextMouseMoveAbsolute = TRUE;
2183 - (void)applicationDidResignActive:(NSNotification *)notification
2185 macdrv_event* event;
2186 WineEventQueue* queue;
2188 [self invalidateGotFocusEvents];
2190 event = macdrv_create_event(APP_DEACTIVATED, nil);
2192 [eventQueuesLock lock];
2193 for (queue in eventQueues)
2194 [queue postEvent:event];
2195 [eventQueuesLock unlock];
2197 macdrv_release_event(event);
2199 [self releaseMouseCapture];
2202 - (void) applicationDidUnhide:(NSNotification*)aNotification
2204 [self adjustWindowLevels];
2207 - (BOOL) applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag
2209 // Note that "flag" is often wrong. WineWindows are NSPanels and NSPanels
2210 // don't count as "visible windows" for this purpose.
2211 [self unminimizeWindowIfNoneVisible];
2215 - (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *)sender
2217 NSApplicationTerminateReply ret = NSTerminateNow;
2218 NSAppleEventManager* m = [NSAppleEventManager sharedAppleEventManager];
2219 NSAppleEventDescriptor* desc = [m currentAppleEvent];
2220 macdrv_event* event;
2221 WineEventQueue* queue;
2223 event = macdrv_create_event(APP_QUIT_REQUESTED, nil);
2225 switch ([[desc attributeDescriptorForKeyword:kAEQuitReason] int32Value])
2228 case kAEReallyLogOut:
2229 event->app_quit_requested.reason = QUIT_REASON_LOGOUT;
2231 case kAEShowRestartDialog:
2232 event->app_quit_requested.reason = QUIT_REASON_RESTART;
2234 case kAEShowShutdownDialog:
2235 event->app_quit_requested.reason = QUIT_REASON_SHUTDOWN;
2238 event->app_quit_requested.reason = QUIT_REASON_NONE;
2242 [eventQueuesLock lock];
2244 if ([eventQueues count])
2246 for (queue in eventQueues)
2247 [queue postEvent:event];
2248 ret = NSTerminateLater;
2251 [eventQueuesLock unlock];
2253 macdrv_release_event(event);
2258 - (void)applicationWillBecomeActive:(NSNotification *)notification
2260 macdrv_event* event = macdrv_create_event(APP_ACTIVATED, nil);
2263 [eventQueuesLock lock];
2264 for (WineEventQueue* queue in eventQueues)
2265 [queue postEvent:event];
2266 [eventQueuesLock unlock];
2268 macdrv_release_event(event);
2271 - (void)applicationWillResignActive:(NSNotification *)notification
2273 [self adjustWindowLevels:NO];
2276 /***********************************************************************
2279 * Run-loop-source perform callback. Pull request blocks from the
2280 * array of queued requests and invoke them.
2282 static void PerformRequest(void *info)
2284 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2285 WineApplicationController* controller = [WineApplicationController sharedController];
2289 __block dispatch_block_t block;
2291 dispatch_sync(controller->requestsManipQueue, ^{
2292 if ([controller->requests count])
2294 block = (dispatch_block_t)[[controller->requests objectAtIndex:0] retain];
2295 [controller->requests removeObjectAtIndex:0];
2308 pool = [[NSAutoreleasePool alloc] init];
2314 /***********************************************************************
2317 * Run a block on the main thread asynchronously.
2319 void OnMainThreadAsync(dispatch_block_t block)
2321 WineApplicationController* controller = [WineApplicationController sharedController];
2323 block = [block copy];
2324 dispatch_sync(controller->requestsManipQueue, ^{
2325 [controller->requests addObject:block];
2328 CFRunLoopSourceSignal(controller->requestSource);
2329 CFRunLoopWakeUp(CFRunLoopGetMain());
2334 /***********************************************************************
2337 void LogError(const char* func, NSString* format, ...)
2340 va_start(args, format);
2341 LogErrorv(func, format, args);
2345 /***********************************************************************
2348 void LogErrorv(const char* func, NSString* format, va_list args)
2350 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
2352 NSString* message = [[NSString alloc] initWithFormat:format arguments:args];
2353 fprintf(stderr, "err:%s:%s", func, [message UTF8String]);
2359 /***********************************************************************
2360 * macdrv_window_rejected_focus
2362 * Pass focus to the next window that hasn't already rejected this same
2363 * WINDOW_GOT_FOCUS event.
2365 void macdrv_window_rejected_focus(const macdrv_event *event)
2368 [[WineApplicationController sharedController] windowRejectedFocusEvent:event];
2372 /***********************************************************************
2373 * macdrv_get_input_source_info
2375 * Returns the keyboard layout uchr data, keyboard type and input source.
2377 void macdrv_get_input_source_info(CFDataRef* uchr, CGEventSourceKeyboardType* keyboard_type, int* is_iso, TISInputSourceRef* input_source)
2380 TISInputSourceRef inputSourceLayout;
2382 inputSourceLayout = TISCopyCurrentKeyboardLayoutInputSource();
2383 if (inputSourceLayout)
2385 CFDataRef data = TISGetInputSourceProperty(inputSourceLayout,
2386 kTISPropertyUnicodeKeyLayoutData);
2387 *uchr = CFDataCreateCopy(NULL, data);
2388 CFRelease(inputSourceLayout);
2390 *keyboard_type = [WineApplicationController sharedController].keyboardType;
2391 *is_iso = (KBGetLayoutType(*keyboard_type) == kKeyboardISO);
2393 *input_source = TISCopyCurrentKeyboardInputSource();
2398 /***********************************************************************
2401 * Play the beep sound configured by the user in System Preferences.
2403 void macdrv_beep(void)
2405 OnMainThreadAsync(^{
2410 /***********************************************************************
2411 * macdrv_set_display_mode
2413 int macdrv_set_display_mode(const struct macdrv_display* display,
2414 CGDisplayModeRef display_mode)
2419 ret = [[WineApplicationController sharedController] setMode:display_mode forDisplay:display->displayID];
2425 /***********************************************************************
2430 * If name is non-NULL, it is a selector for a class method on NSCursor
2431 * identifying the cursor to set. In that case, frames is ignored. If
2432 * name is NULL, then frames is used.
2434 * frames is an array of dictionaries. Each dictionary is a frame of
2435 * an animated cursor. Under the key "image" is a CGImage for the
2436 * frame. Under the key "duration" is a CFNumber time interval, in
2437 * seconds, for how long that frame is presented before proceeding to
2438 * the next frame. Under the key "hotSpot" is a CFDictionary encoding a
2439 * CGPoint, to be decoded using CGPointMakeWithDictionaryRepresentation().
2440 * This is the hot spot, measured in pixels down and to the right of the
2441 * top-left corner of the image.
2443 * If the array has exactly 1 element, the cursor is static, not
2444 * animated. If frames is NULL or has 0 elements, the cursor is hidden.
2446 void macdrv_set_cursor(CFStringRef name, CFArrayRef frames)
2450 sel = NSSelectorFromString((NSString*)name);
2453 OnMainThreadAsync(^{
2454 WineApplicationController* controller = [WineApplicationController sharedController];
2455 [controller setCursorWithFrames:nil];
2456 controller.cursor = [NSCursor performSelector:sel];
2457 [controller unhideCursor];
2462 NSArray* nsframes = (NSArray*)frames;
2463 if ([nsframes count])
2465 OnMainThreadAsync(^{
2466 [[WineApplicationController sharedController] setCursorWithFrames:nsframes];
2471 OnMainThreadAsync(^{
2472 WineApplicationController* controller = [WineApplicationController sharedController];
2473 [controller setCursorWithFrames:nil];
2474 [controller hideCursor];
2480 /***********************************************************************
2481 * macdrv_get_cursor_position
2483 * Obtains the current cursor position. Returns zero on failure,
2484 * non-zero on success.
2486 int macdrv_get_cursor_position(CGPoint *pos)
2489 NSPoint location = [NSEvent mouseLocation];
2490 location = [[WineApplicationController sharedController] flippedMouseLocation:location];
2491 *pos = cgpoint_win_from_mac(NSPointToCGPoint(location));
2497 /***********************************************************************
2498 * macdrv_set_cursor_position
2500 * Sets the cursor position without generating events. Returns zero on
2501 * failure, non-zero on success.
2503 int macdrv_set_cursor_position(CGPoint pos)
2508 ret = [[WineApplicationController sharedController] setCursorPosition:cgpoint_mac_from_win(pos)];
2514 /***********************************************************************
2515 * macdrv_clip_cursor
2517 * Sets the cursor cursor clipping rectangle. If the rectangle is equal
2518 * to or larger than the whole desktop region, the cursor is unclipped.
2519 * Returns zero on failure, non-zero on success.
2521 int macdrv_clip_cursor(CGRect r)
2526 WineApplicationController* controller = [WineApplicationController sharedController];
2527 BOOL clipping = FALSE;
2530 if (!CGRectIsInfinite(rect))
2531 rect = cgrect_mac_from_win(rect);
2533 if (!CGRectIsInfinite(rect))
2535 NSRect nsrect = NSRectFromCGRect(rect);
2538 /* Convert the rectangle from top-down coords to bottom-up. */
2539 [controller flipRect:&nsrect];
2542 for (screen in [NSScreen screens])
2544 if (!NSContainsRect(nsrect, [screen frame]))
2553 ret = [controller startClippingCursor:rect];
2555 ret = [controller stopClippingCursor];
2561 /***********************************************************************
2562 * macdrv_set_application_icon
2564 * Set the application icon. The images array contains CGImages. If
2565 * there are more than one, then they represent different sizes or
2566 * color depths from the icon resource. If images is NULL or empty,
2567 * restores the default application image.
2569 void macdrv_set_application_icon(CFArrayRef images)
2571 NSArray* imageArray = (NSArray*)images;
2573 OnMainThreadAsync(^{
2574 [[WineApplicationController sharedController] setApplicationIconFromCGImageArray:imageArray];
2578 /***********************************************************************
2581 void macdrv_quit_reply(int reply)
2584 [NSApp replyToApplicationShouldTerminate:reply];
2588 /***********************************************************************
2589 * macdrv_using_input_method
2591 int macdrv_using_input_method(void)
2596 ret = [[WineApplicationController sharedController] inputSourceIsInputMethod];
2602 /***********************************************************************
2603 * macdrv_set_mouse_capture_window
2605 void macdrv_set_mouse_capture_window(macdrv_window window)
2607 WineWindow* w = (WineWindow*)window;
2609 [w.queue discardEventsMatchingMask:event_mask_for_type(RELEASE_CAPTURE) forWindow:w];
2612 [[WineApplicationController sharedController] setMouseCaptureWindow:w];
2616 const CFStringRef macdrv_input_source_input_key = CFSTR("input");
2617 const CFStringRef macdrv_input_source_type_key = CFSTR("type");
2618 const CFStringRef macdrv_input_source_lang_key = CFSTR("lang");
2620 /***********************************************************************
2621 * macdrv_create_input_source_list
2623 CFArrayRef macdrv_create_input_source_list(void)
2625 CFMutableArrayRef ret = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
2628 CFArrayRef input_list;
2629 CFDictionaryRef filter_dict;
2630 const void *filter_keys[2] = { kTISPropertyInputSourceCategory, kTISPropertyInputSourceIsSelectCapable };
2631 const void *filter_values[2] = { kTISCategoryKeyboardInputSource, kCFBooleanTrue };
2634 filter_dict = CFDictionaryCreate(NULL, filter_keys, filter_values, sizeof(filter_keys)/sizeof(filter_keys[0]),
2635 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2636 input_list = TISCreateInputSourceList(filter_dict, false);
2638 for (i = 0; i < CFArrayGetCount(input_list); i++)
2640 TISInputSourceRef input = (TISInputSourceRef)CFArrayGetValueAtIndex(input_list, i);
2641 CFArrayRef source_langs = TISGetInputSourceProperty(input, kTISPropertyInputSourceLanguages);
2642 CFDictionaryRef entry;
2643 const void *input_keys[3] = { macdrv_input_source_input_key,
2644 macdrv_input_source_type_key,
2645 macdrv_input_source_lang_key };
2646 const void *input_values[3];
2648 input_values[0] = input;
2649 input_values[1] = TISGetInputSourceProperty(input, kTISPropertyInputSourceType);
2650 input_values[2] = CFArrayGetValueAtIndex(source_langs, 0);
2652 entry = CFDictionaryCreate(NULL, input_keys, input_values, sizeof(input_keys) / sizeof(input_keys[0]),
2653 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2655 CFArrayAppendValue(ret, entry);
2658 CFRelease(input_list);
2659 CFRelease(filter_dict);
2665 int macdrv_select_input_source(TISInputSourceRef input_source)
2667 __block int ret = FALSE;
2670 ret = (TISSelectInputSource(input_source) == noErr);
2676 void macdrv_set_cocoa_retina_mode(int new_mode)
2679 [[WineApplicationController sharedController] setRetinaMode:new_mode];
2683 int macdrv_is_any_wine_window_visible(void)
2685 __block int ret = FALSE;
2688 ret = [[WineApplicationController sharedController] isAnyWineWindowVisible];