This is a change from the default behavior in macdrv for as long as it's existed, as far as I can tell. Previously, gaining a dock icon was a one-way path. However, that doesn't jive with the way taskbar entries work on Windows; if an app has no windows, it doesn't appear in the taskbar. This patch attempts to remedy cases where an app winds up with superfluous dock icons for exe's that no longer have windows (looking at you, basically every launcher and Steam).
The major concern I can see with this is that if an app closes all of its windows but does not exit, there will be no indication that it is still running. Two thoughts on that:
1. That *should* be an anomalous case, such as the app hanging on exit. 2. Effectively the same behavior would happen on Windows.
I would love to hear any other thoughts about this change. I'm open (though I would not prefer it) to defaulting the registry key to false if that would alleviate any concerns.
From: Tim Clem tclem@codeweavers.com
Hide the icon when an app has no visible windows, or when all of its visible windows would have no taskbar entry (i.e., they have WS_EX_TOOLWINDOW or WS_EX_NOACTIVATE, but not WS_EX_APPWINDOW). The dock icon returns if those conditions are no longer satisfied.
This behavior is behind a Mac Driver registry key, EagerDockIconHiding (defaulting to true), to allow toggling it on a per-app or global basis. --- dlls/winemac.drv/cocoa_app.h | 1 + dlls/winemac.drv/cocoa_app.m | 43 +++++++++++++++++++++++++++++++++ dlls/winemac.drv/cocoa_window.h | 2 ++ dlls/winemac.drv/cocoa_window.m | 23 ++++++++++++++++-- dlls/winemac.drv/macdrv_cocoa.h | 2 ++ dlls/winemac.drv/macdrv_main.c | 4 +++ dlls/winemac.drv/window.c | 5 ++++ 7 files changed, 78 insertions(+), 2 deletions(-)
diff --git a/dlls/winemac.drv/cocoa_app.h b/dlls/winemac.drv/cocoa_app.h index b2b8187e0b4..4db5b511c60 100644 --- a/dlls/winemac.drv/cocoa_app.h +++ b/dlls/winemac.drv/cocoa_app.h @@ -157,6 +157,7 @@ - (void) noteKey:(uint16_t)keyCode pressed:(BOOL)pressed;
- (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged; - (void) windowWillOrderOut:(WineWindow*)window; + - (void) maybeHideDockIconDueToWindowOrderingOut:(NSWindow *)window;
- (void) flipRect:(NSRect*)rect; - (NSPoint) flippedMouseLocation:(NSPoint)point; diff --git a/dlls/winemac.drv/cocoa_app.m b/dlls/winemac.drv/cocoa_app.m index de3a23a5d2d..545885cb540 100644 --- a/dlls/winemac.drv/cocoa_app.m +++ b/dlls/winemac.drv/cocoa_app.m @@ -1300,6 +1300,45 @@ - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged [windowsBeingDragged removeObject:window]; }
+ /* Checks if, discounting the given window, there are any visible windows + that should make the app have a dock icon. window may be nil. */ + - (BOOL) shouldHaveDockIconAfterWindowOrdersOut:(NSWindow *)window + { + if (!eager_dock_icon_hiding) + return YES; + + for (NSWindow *w in [NSApp windows]) + { + if (w != window && (w.isVisible || w.isMiniaturized)) + { + if ([w isKindOfClass:[WineWindow class]] && !((WineWindow *)w).needsDockIcon) + continue; + + return YES; + } + } + + return NO; + } + + /* If there are no visible windows that should make the app have a dock icon + (other than the provided one), hides the dock icon. window may be nil. */ + - (void) maybeHideDockIconDueToWindowOrderingOut:(NSWindow *)window + { + if ((!window.isVisible && !window.isMiniaturized) || + ([window isKindOfClass:[WineWindow class]] && !((WineWindow *)window).needsDockIcon)) + { + /* Nothing to do; that window couldn't have changed anything. */ + return; + } + + if ([NSApp activationPolicy] == NSApplicationActivationPolicyRegular && + ![self shouldHaveDockIconAfterWindowOrdersOut:window]) + { + NSApp.activationPolicy = NSApplicationActivationPolicyAccessory; + } + } + - (void) windowWillOrderOut:(WineWindow*)window { if ([windowsBeingDragged containsObject:window]) @@ -1310,6 +1349,8 @@ - (void) windowWillOrderOut:(WineWindow*)window [window.queue postEvent:event]; macdrv_release_event(event); } + + [self maybeHideDockIconDueToWindowOrderingOut:window]; }
- (BOOL) isAnyWineWindowVisible @@ -1914,6 +1955,8 @@ - (void) setupObservations }); } [windowsBeingDragged removeObject:window]; + + [self maybeHideDockIconDueToWindowOrderingOut:window]; }];
if (useDragNotifications) { diff --git a/dlls/winemac.drv/cocoa_window.h b/dlls/winemac.drv/cocoa_window.h index 9539e4ebdd7..f5eb75e0bef 100644 --- a/dlls/winemac.drv/cocoa_window.h +++ b/dlls/winemac.drv/cocoa_window.h @@ -29,6 +29,7 @@ @interface WineWindow : NSPanel <NSWindowDelegate> BOOL disabled; BOOL noForeground; BOOL preventsAppActivation; + BOOL needsDockIcon; BOOL floating; BOOL resizable; BOOL maximized; @@ -93,6 +94,7 @@ @interface WineWindow : NSPanel <NSWindowDelegate> @property (readonly, nonatomic) BOOL disabled; @property (readonly, nonatomic) BOOL noForeground; @property (readonly, nonatomic) BOOL preventsAppActivation; +@property (readonly, nonatomic) BOOL needsDockIcon; @property (readonly, nonatomic) BOOL floating; @property (readonly, getter=isFullscreen, nonatomic) BOOL fullscreen; @property (readonly, getter=isFakingClose, nonatomic) BOOL fakingClose; diff --git a/dlls/winemac.drv/cocoa_window.m b/dlls/winemac.drv/cocoa_window.m index 1655ea98ef7..15ee4720630 100644 --- a/dlls/winemac.drv/cocoa_window.m +++ b/dlls/winemac.drv/cocoa_window.m @@ -1008,6 +1008,7 @@ @implementation WineWindow @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue; @synthesize usePerPixelAlpha; @synthesize himc, commandDone; + @synthesize needsDockIcon;
+ (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf windowFrame:(NSRect)window_frame @@ -1047,6 +1048,7 @@ + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)w window->savedContentMaxSize = NSMakeSize(FLT_MAX, FLT_MAX); window->resizable = wf->resizable; window->_lastDisplayTime = [[NSDate distantPast] timeIntervalSinceReferenceDate]; + window->needsDockIcon = wf->dock_icon;
[window registerForDraggedTypes:@[(NSString*)kUTTypeData, (NSString*)kUTTypeContent]];
@@ -1182,6 +1184,7 @@ - (void) setWindowFeatures:(const struct macdrv_window_features*)wf NSWindowStyleMaskNonactivatingPanel; NSUInteger currentStyle = [self styleMask]; NSUInteger newStyle = style_mask_for_features(wf) | (currentStyle & ~usedStyles); + int neededDockIcon;
self.preventsAppActivation = wf->prevents_app_activation;
@@ -1226,6 +1229,17 @@ - (void) setWindowFeatures:(const struct macdrv_window_features*)wf resizable = wf->resizable; [self adjustFeaturesForState]; [self setHasShadow:wf->shadow]; + + /* We may need to hide or show our dock icon in response to changes. */ + neededDockIcon = needsDockIcon; + needsDockIcon = wf->dock_icon; /* Need to update this before calling maybeHideDockIcon */ + if ([self isOrderedIn]) + { + if (neededDockIcon && !needsDockIcon) + [[WineApplicationController sharedController] maybeHideDockIconDueToWindowOrderingOut:nil]; + else if(!neededDockIcon && needsDockIcon) + [[WineApplicationController sharedController] transformProcessToForeground:!self.preventsAppActivation]; + } }
// Indicates if the window would be visible if the app were not hidden. @@ -1738,7 +1752,9 @@ - (void) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next activate:(BOOL)a WineWindow* parent; WineWindow* child;
- [controller transformProcessToForeground:!self.preventsAppActivation]; + if (!eager_dock_icon_hiding || self.needsDockIcon) + [controller transformProcessToForeground:!self.preventsAppActivation]; + if ([NSApp isHidden]) [NSApp unhide:nil]; wasVisible = [self isVisible]; @@ -2109,7 +2125,10 @@ - (void) makeFocused:(BOOL)activate if (activate) { WineApplicationController *controller = [WineApplicationController sharedController]; - [controller transformProcessToForeground:YES]; + + if (!eager_dock_icon_hiding || self.needsDockIcon) + [controller transformProcessToForeground:YES]; + [controller tryToActivateIgnoringOtherApps:YES]; }
diff --git a/dlls/winemac.drv/macdrv_cocoa.h b/dlls/winemac.drv/macdrv_cocoa.h index 3a977f68955..294b0d9389b 100644 --- a/dlls/winemac.drv/macdrv_cocoa.h +++ b/dlls/winemac.drv/macdrv_cocoa.h @@ -158,6 +158,7 @@ extern int retina_enabled; /* Whether Retina mode is enabled via registry setting. */ extern int retina_on; /* Whether Retina mode is currently active (enabled and display is in default mode). */ extern int enable_app_nap; +extern int eager_dock_icon_hiding;
static inline CGRect cgrect_mac_from_win(CGRect rect) { @@ -526,6 +527,7 @@ extern int macdrv_register_hot_key(macdrv_event_queue q, unsigned int vkey, unsi unsigned int utility:1; unsigned int shadow:1; unsigned int prevents_app_activation:1; + unsigned int dock_icon:1; };
struct macdrv_window_state { diff --git a/dlls/winemac.drv/macdrv_main.c b/dlls/winemac.drv/macdrv_main.c index 21b148ff558..acfad65ea81 100644 --- a/dlls/winemac.drv/macdrv_main.c +++ b/dlls/winemac.drv/macdrv_main.c @@ -60,6 +60,7 @@ int use_precise_scrolling = TRUE; int gl_surface_mode = GL_SURFACE_IN_FRONT_OPAQUE; int retina_enabled = FALSE; int enable_app_nap = FALSE; +int eager_dock_icon_hiding = TRUE;
CFDictionaryRef localized_strings;
@@ -378,6 +379,9 @@ static void setup_options(void) if (!get_config_key(hkey, appkey, "EnableAppNap", buffer, sizeof(buffer))) enable_app_nap = IS_OPTION_TRUE(buffer[0]);
+ if (!get_config_key(hkey, appkey, "EagerDockIconHiding", buffer, sizeof(buffer))) + eager_dock_icon_hiding = IS_OPTION_TRUE(buffer[0]); + /* Don't use appkey. The DPI and monitor sizes should be consistent for all processes in the prefix. */ if (!get_config_key(hkey, NULL, "RetinaMode", buffer, sizeof(buffer))) diff --git a/dlls/winemac.drv/window.c b/dlls/winemac.drv/window.c index 67f45d301df..62d164b6e09 100644 --- a/dlls/winemac.drv/window.c +++ b/dlls/winemac.drv/window.c @@ -59,6 +59,11 @@ static void get_cocoa_window_features(struct macdrv_win_data *data,
if (ex_style & WS_EX_NOACTIVATE) wf->prevents_app_activation = TRUE;
+ /* This flag is only relevant when the window is visible, so we don't need + to worry about any other styles or the window rect. */ + wf->dock_icon = (ex_style & WS_EX_APPWINDOW) || + (ex_style & (WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE) == 0); + if (disable_window_decorations) return; if (IsRectEmpty(window_rect)) return; if (EqualRect(window_rect, client_rect)) return;
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=143537
Your paranoid android.
=== debian11b (64 bit WoW report) ===
user32: input.c:3864: Test succeeded inside todo block: button_down_hwnd_todo 1: got MSG_TEST_WIN hwnd 00000000007D00E8, msg WM_LBUTTONDOWN, wparam 0x1, lparam 0x320032
I am not very knowledgeable in this area, so I don't think I can give any useful comments on the code, but I have some questions:
- Why call `maybeHideDockIconDueToWindowOrderingOut` in those two places specifically? And why isn't `doOrderOut` called when the window is closed? - Is this supposed to work with CrossOver as is? The situation with Steam seems even weirder than before. For instance the first dock icon is the default icon, which stays until you close all Steam windows (news, friends list, main window) and then open another, after which it will have the Steam icon instead of the default icon.
On Thu Feb 29 17:23:38 2024 +0000, Sven Baars wrote:
I am not very knowledgeable in this area, so I don't think I can give any useful comments on the code, but I have some questions:
- Why call `maybeHideDockIconDueToWindowOrderingOut` in those two places
specifically? And why isn't `doOrderOut` called when the window is closed?
- Is this supposed to work with CrossOver as is? The situation with
Steam seems even weirder than before. For instance the first dock icon is the default icon, which stays until you close all Steam windows (news, friends list, main window) and then open another, after which it will have the Steam icon instead of the default icon.
`doOrderOut` is called when a window is closed, but the reverse isn't always true - there are times when a window orders out, but not in response to it being closed (just being hidden, for example). So that check needs to happen in two places to catch both cases.
As for the CrossOver situation, we can discuss that internally. There's another piece that needs to come in because of the proxy apps we make. I am concerned about the default app icon showing up in the dock though. Let me see if I can reproduce that.
On Thu Feb 29 17:23:38 2024 +0000, Tim Clem wrote:
`doOrderOut` is called when a window is closed, but the reverse isn't always true - there are times when a window orders out, but not in response to it being closed (just being hidden, for example). So that check needs to happen in two places to catch both cases. As for the CrossOver situation, we can discuss that internally. There's another piece that needs to come in because of the proxy apps we make. I am concerned about the default app icon showing up in the dock though. Let me see if I can reproduce that.
But if `doOrderOut` is called when the window is closed, do you also need to call `maybeHideDockIconDueToWindowOrderingOut` in `NSWindowWillCloseNotification`?
I observed the behavior with the default icon on Catalina if that helps.
On Thu Feb 29 17:45:42 2024 +0000, Sven Baars wrote:
But if `doOrderOut` is called when the window is closed, do you also need to call `maybeHideDockIconDueToWindowOrderingOut` in `NSWindowWillCloseNotification`? I observed the behavior with the default icon on Catalina if that helps.
Huh, yes, I think you're right. I added that early on because I thought it fixed one case, but it doesn't seem like it does. I'll do some more testing without it and on Catalina.