Module: wine Branch: master Commit: fd67d678503ec2e57cbefef0bdfda48938153d3b URL: https://gitlab.winehq.org/wine/wine/-/commit/fd67d678503ec2e57cbefef0bdfda48...
Author: Tim Clem tclem@codeweavers.com Date: Tue Aug 15 11:38:43 2023 -0700
winemac.drv: Support cooperative app activation in macOS 14 Sonoma.
Starting in Sonoma, apps can no longer force themselves to the foreground with -activateIgnoringOtherApps:. winemac currently does that in a few places - when an app creates its first window, and in the implementation of APIs like SetFocus.
There's nothing we can do to work around the new behavior in the general case. This patch makes Wine apps running in the same prefix yield to one another, so that windows from multiple EXEs can at least behave as intended.
---
dlls/winemac.drv/cocoa_app.h | 1 + dlls/winemac.drv/cocoa_app.m | 121 +++++++++++++++++++++++++++++++++++++++- dlls/winemac.drv/cocoa_window.m | 7 ++- 3 files changed, 125 insertions(+), 4 deletions(-)
diff --git a/dlls/winemac.drv/cocoa_app.h b/dlls/winemac.drv/cocoa_app.h index 52c91c0621f..c4c8bbd16f2 100644 --- a/dlls/winemac.drv/cocoa_app.h +++ b/dlls/winemac.drv/cocoa_app.h @@ -142,6 +142,7 @@ enum { + (WineApplicationController*) sharedController;
- (void) transformProcessToForeground:(BOOL)activateIfTransformed; + - (void) tryToActivateIgnoringOtherApps:(BOOL)ignore;
- (BOOL) registerEventQueue:(WineEventQueue*)queue; - (void) unregisterEventQueue:(WineEventQueue*)queue; diff --git a/dlls/winemac.drv/cocoa_app.m b/dlls/winemac.drv/cocoa_app.m index 6a16d1ba832..5a6a3e01005 100644 --- a/dlls/winemac.drv/cocoa_app.m +++ b/dlls/winemac.drv/cocoa_app.m @@ -34,6 +34,12 @@ static NSString* const WineAppWaitQueryResponseMode = @"WineAppWaitQueryResponse static NSString* const NSWindowWillStartDraggingNotification = @"NSWindowWillStartDraggingNotification"; static NSString* const NSWindowDidEndDraggingNotification = @"NSWindowDidEndDraggingNotification";
+// Internal distributed notification to handle cooperative app activation in Sonoma. +static NSString* const WineAppWillActivateNotification = @"WineAppWillActivateNotification"; +static NSString* const WineActivatingAppPIDKey = @"ActivatingAppPID"; +static NSString* const WineActivatingAppPrefixKey = @"ActivatingAppPrefix"; +static NSString* const WineActivatingAppConfigDirKey = @"ActivatingAppConfigDir"; +
int macdrv_err_on;
@@ -47,6 +53,24 @@ int macdrv_err_on; #endif
+#if !defined(MAC_OS_VERSION_14_0) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_14_0 +@interface NSApplication (CooperativeActivationSelectorsForOldSDKs) + + - (void)activate; + - (void)yieldActivationToApplication:(NSRunningApplication *)application; + - (void)yieldActivationToApplicationWithBundleIdentifier:(NSString *)bundleIdentifier; + +@end + +@interface NSRunningApplication (CooperativeActivationSelectorsForOldSDKs) + + - (BOOL)activateFromApplication:(NSRunningApplication *)application + options:(NSApplicationActivationOptions)options; + +@end +#endif + + /*********************************************************************** * WineLocalizedString * @@ -227,7 +251,7 @@ static NSString* WineLocalizedString(unsigned int stringID) [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
if (activateIfTransformed) - [NSApp activateIgnoringOtherApps:YES]; + [self tryToActivateIgnoringOtherApps:YES];
#if defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9 if (!enable_app_nap && [NSProcessInfo instancesRespondToSelector:@selector(beginActivityWithOptions:reason:)]) @@ -1939,8 +1963,103 @@ static NSString* WineLocalizedString(unsigned int stringID) selector:@selector(enabledKeyboardInputSourcesChanged) name:(NSString*)kTISNotifyEnabledKeyboardInputSourcesChanged object:nil]; + + if ([NSApplication instancesRespondToSelector:@selector(yieldActivationToApplication:)]) + { + /* App activation cooperation, starting in macOS 14 Sonoma. */ + [dnc addObserver:self + selector:@selector(otherWineAppWillActivate:) + name:WineAppWillActivateNotification + object:nil + suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately]; + } + } + + - (void) otherWineAppWillActivate:(NSNotification *)note + { + NSProcessInfo *ourProcess; + pid_t otherPID; + NSString *ourConfigDir, *otherConfigDir, *ourPrefix, *otherPrefix; + NSRunningApplication *otherApp; + + /* No point in yielding if we're not the foreground app. */ + if (![NSApp isActive]) return; + + /* Ignore requests from ourself, dead processes, and other prefixes. */ + ourProcess = [NSProcessInfo processInfo]; + otherPID = [note.userInfo[WineActivatingAppPIDKey] integerValue]; + if (otherPID == ourProcess.processIdentifier) return; + + otherApp = [NSRunningApplication runningApplicationWithProcessIdentifier:otherPID]; + if (!otherApp) return; + + ourConfigDir = ourProcess.environment[@"WINECONFIGDIR"]; + otherConfigDir = note.userInfo[WineActivatingAppConfigDirKey]; + if (ourConfigDir.length && otherConfigDir.length && + ![ourConfigDir isEqualToString:otherConfigDir]) + { + return; + } + + ourPrefix = ourProcess.environment[@"WINEPREFIX"]; + otherPrefix = note.userInfo[WineActivatingAppPrefixKey]; + if (ourPrefix.length && otherPrefix.length && + ![ourPrefix isEqualToString:otherPrefix]) + { + return; + } + + /* There's a race condition here. The requesting app sends out + WineAppWillActivateNotification and then activates itself, but since + distributed notifications are asynchronous, we may not have yielded + in time. So we call activateFromApplication: on the other app here, + which will work around that race if it happened. If we didn't hit the + race, the activateFromApplication: call will be a no-op. */ + + /* We only add this observer if NSApplication responds to the yield + methods, so they're safe to call without checking here. */ + [NSApp yieldActivationToApplication:otherApp]; + [otherApp activateFromApplication:[NSRunningApplication currentApplication] + options:0]; }
+ - (void) tryToActivateIgnoringOtherApps:(BOOL)ignore + { + NSProcessInfo *processInfo; + NSString *configDir, *prefix; + NSDictionary *userInfo; + + if ([NSApp isActive]) return; /* Nothing to do. */ + + if (!ignore || + ![NSApplication instancesRespondToSelector:@selector(yieldActivationToApplication:)]) + { + /* Either we don't need to force activation, or the OS is old enough + that this is our only option. */ + [NSApp activateIgnoringOtherApps:ignore]; + return; + } + + /* Ask other Wine apps to yield activation to us. */ + processInfo = [NSProcessInfo processInfo]; + configDir = processInfo.environment[@"WINECONFIGDIR"]; + prefix = processInfo.environment[@"WINEPREFIX"]; + userInfo = @{ + WineActivatingAppPIDKey: @(processInfo.processIdentifier), + WineActivatingAppPrefixKey: prefix ? prefix : @"", + WineActivatingAppConfigDirKey: configDir ? configDir : @"" + }; + + [[NSDistributedNotificationCenter defaultCenter] + postNotificationName:WineAppWillActivateNotification + object:nil + userInfo:userInfo + deliverImmediately:YES]; + + /* This is racy. See the note in otherWineAppWillActivate:. */ + [NSApp activate]; + } + - (BOOL) inputSourceIsInputMethod { if (!inputSourceIsInputMethodValid) diff --git a/dlls/winemac.drv/cocoa_window.m b/dlls/winemac.drv/cocoa_window.m index ac4cc214239..cc9a9e13c2b 100644 --- a/dlls/winemac.drv/cocoa_window.m +++ b/dlls/winemac.drv/cocoa_window.m @@ -1720,7 +1720,7 @@ static CVReturn WineDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTi wasVisible = [self isVisible];
if (activate) - [NSApp activateIgnoringOtherApps:YES]; + [controller tryToActivateIgnoringOtherApps:YES];
NSDisableScreenUpdates();
@@ -2084,8 +2084,9 @@ static CVReturn WineDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTi { if (activate) { - [[WineApplicationController sharedController] transformProcessToForeground:YES]; - [NSApp activateIgnoringOtherApps:YES]; + WineApplicationController *controller = [WineApplicationController sharedController]; + [controller transformProcessToForeground:YES]; + [controller tryToActivateIgnoringOtherApps:YES]; }
causing_becomeKeyWindow = self;