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.
-- v6: winemac.drv: Hide app's dock icon when it wouldn't have a taskbar item on Windows.
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 are owned or 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 | 75 +++++++++++++++++++++++++++++++++ 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 | 8 ++++ 7 files changed, 113 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 f6a9cd145ea..e7c98eb5ebe 100644 --- a/dlls/winemac.drv/cocoa_app.m +++ b/dlls/winemac.drv/cocoa_app.m @@ -18,6 +18,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include "macdrv_cocoa.h" #import <Carbon/Carbon.h>
#import "cocoa_app.h" @@ -1300,6 +1301,78 @@ - (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. anyVisible + will be set to reflect whether any window other than the given one is + visible. */ + - (BOOL) shouldHaveDockIconAfterWindowOrdersOut:(NSWindow *)window + anyWindowIsVisible:(BOOL *)anyVisible + { + BOOL foundVisibleWindow = NO; + + if (!eager_dock_icon_hiding) + return YES; + + for (NSWindow *w in [NSApp windows]) + { + if (w != window && (w.isVisible || w.isMiniaturized)) + { + foundVisibleWindow = YES; + + if ([w isKindOfClass:[WineWindow class]] && !((WineWindow *)w).needsDockIcon) + continue; + + return YES; + } + } + + if (anyVisible) + *anyVisible = foundVisibleWindow; + + 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 + { + BOOL anyVisibleWindows; + static int isMontereyOrLater = -1; + + if (isMontereyOrLater == -1) + { + isMontereyOrLater = 0; + if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)]) + { + NSOperatingSystemVersion requiredVersion = { 12, 0, 0 }; + isMontereyOrLater = [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:requiredVersion]; + } + } + + if (!eager_dock_icon_hiding) + return; + + if (window && + ((!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 + 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. */ + if (isMontereyOrLater || !anyVisibleWindows) + NSApp.activationPolicy = NSApplicationActivationPolicyAccessory; + } + } + - (void) windowWillOrderOut:(WineWindow*)window { if ([windowsBeingDragged containsObject:window]) @@ -1310,6 +1383,8 @@ - (void) windowWillOrderOut:(WineWindow*)window [window.queue postEvent:event]; macdrv_release_event(event); } + + [self maybeHideDockIconDueToWindowOrderingOut:window]; }
- (BOOL) isAnyWineWindowVisible 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..5e745534da4 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 0c010d6e097..a6f576ae47c 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) { @@ -521,6 +522,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 a04ff5fd4c9..bdf2e960d5a 100644 --- a/dlls/winemac.drv/window.c +++ b/dlls/winemac.drv/window.c @@ -59,6 +59,14 @@ static void get_cocoa_window_features(struct macdrv_win_data *data,
if (ex_style & WS_EX_NOACTIVATE) wf->prevents_app_activation = TRUE;
+ /* The dock_icon flag is only relevant when the window is visible, so we + don't need to worry about any other styles or the window rect. */ + if (NtUserGetWindowRelative(data->hwnd, GW_OWNER)) + wf->dock_icon = FALSE; + else + 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;
v5: actually push owned windows fix
v6: fix some missed cases where the icon might hide despite the registry setting being off