From: Tim Clem tclem@codeweavers.com
Working around an OS bug: rapidly hiding and showing the dock icon can result in multiple icons for the same app, and those icons will ignore NSApp.applicationIconImage. Making sure transitions happen no less than 1 second apart from one another seems to fix it. --- dlls/winemac.drv/cocoa_app.h | 4 +++ dlls/winemac.drv/cocoa_app.m | 66 +++++++++++++++++++++++++++++++----- 2 files changed, 62 insertions(+), 8 deletions(-)
diff --git a/dlls/winemac.drv/cocoa_app.h b/dlls/winemac.drv/cocoa_app.h index 4db5b511c60..d51dca2f2e3 100644 --- a/dlls/winemac.drv/cocoa_app.h +++ b/dlls/winemac.drv/cocoa_app.h @@ -125,6 +125,10 @@ @interface WineApplicationController : NSObject <NSApplicationDelegate>
NSImage* applicationIcon;
+ NSTimeInterval lastDockIconVisibilityChangeTime; + BOOL hasPendingDockIconVisibilityChange; + BOOL pendingDockIconShow; + BOOL beenActive;
NSMutableSet* windowsBeingDragged; diff --git a/dlls/winemac.drv/cocoa_app.m b/dlls/winemac.drv/cocoa_app.m index ee0add686a5..a86c58f4a30 100644 --- a/dlls/winemac.drv/cocoa_app.m +++ b/dlls/winemac.drv/cocoa_app.m @@ -304,11 +304,63 @@ - (void) setDefaultMenus [NSApp setWindowsMenu:submenu]; }
+ - (void) doPendingDockIconChange + { + if (!hasPendingDockIconVisibilityChange) return; + + hasPendingDockIconVisibilityChange = NO; + lastDockIconVisibilityChangeTime = [[NSProcessInfo processInfo] systemUptime]; + + if (pendingDockIconShow) + { + if (NSApp.activationPolicy != NSApplicationActivationPolicyRegular) + { + NSApp.activationPolicy = NSApplicationActivationPolicyRegular; + + /* This seems to have no effect until we actually have a dock + icon, so we can't set it earlier. */ + NSApp.applicationIconImage = self.applicationIcon; + + /* We can't take over the menu bar until we have a dock icon, so + there's no point in doing this if we don't. */ + [self setDefaultMenus]; + } + } + else if(NSApp.activationPolicy != NSApplicationActivationPolicyAccessory) + { + NSApp.activationPolicy = NSApplicationActivationPolicyAccessory; + } + } + + - (void) showDockIcon:(BOOL)show + { + static const NSTimeInterval minTimeBetweenChanges = 1.0; + NSTimeInterval timeSinceLastChange; + + pendingDockIconShow = show; + + /* Nothing to do if there's already a scheduled change. */ + if (hasPendingDockIconVisibilityChange) + return; + + hasPendingDockIconVisibilityChange = YES; + timeSinceLastChange = [[NSProcessInfo processInfo] systemUptime] - lastDockIconVisibilityChangeTime; + if (lastDockIconVisibilityChangeTime == 0 || timeSinceLastChange >= minTimeBetweenChanges) + [self doPendingDockIconChange]; + else + { + NSTimeInterval timeToWaitForNextChange = minTimeBetweenChanges - timeSinceLastChange; + [self performSelector:@selector(doPendingDockIconChange) withObject:nil afterDelay:timeToWaitForNextChange]; + } + } + - (void) transformProcessToForeground:(BOOL)activateIfTransformed { + /* activationPolicy may not be exactly accurate if there's a pending + hide/show of the dock icon, but it's not a big deal here. */ if ([NSApp activationPolicy] != NSApplicationActivationPolicyRegular) { - [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + [self showDockIcon:YES];
if (activateIfTransformed) [self tryToActivateIgnoringOtherApps:YES]; @@ -320,10 +372,6 @@ - (void) transformProcessToForeground:(BOOL)activateIfTransformed reason:@"Running Windows program"] retain]; // intentional leak } #endif - - [self setDefaultMenus]; - - [NSApp setApplicationIconImage:self.applicationIcon]; } }
@@ -1358,16 +1406,18 @@ - (void) maybeHideDockIconDueToWindowOrderingOut:(NSWindow *)window return; }
- if ([NSApp activationPolicy] == NSApplicationActivationPolicyRegular && - ![self shouldHaveDockIconAfterWindowOrdersOut:window + if (![self shouldHaveDockIconAfterWindowOrdersOut:window anyWindowIsVisible:&anyVisibleWindows]) { /* Before macOS 12 Monterey, hiding the dock icon while there are visible windows makes those windows disappear until they are programmatically ordered back in. So we don't do that transition (which should be rather uncommon) on older OSes. */ + /* We may already not have a dock icon, but -showDockIcon: queues + transitions, so we should call it regardless of the current value + of NSApp.activationPolicy. */ if (isMontereyOrLater || !anyVisibleWindows) - NSApp.activationPolicy = NSApplicationActivationPolicyAccessory; + [self showDockIcon:NO]; } }