Commit fd67d678 authored by Tim Clem's avatar Tim Clem Committed by Alexandre Julliard

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.
parent a1d429da
......@@ -142,6 +142,7 @@ enum {
+ (WineApplicationController*) sharedController;
- (void) transformProcessToForeground:(BOOL)activateIfTransformed;
- (void) tryToActivateIgnoringOtherApps:(BOOL)ignore;
- (BOOL) registerEventQueue:(WineEventQueue*)queue;
- (void) unregisterEventQueue:(WineEventQueue*)queue;
......
......@@ -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)
......
......@@ -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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment