From: "Gabriel Ivăncescu" Subject: [PATCH 6/6] winex11.drv: Implement WM virtual desktops via shell cloaking. Message-Id: <62dd5fc26dada5c5241cdedf111a90c5d79f3d30.1604678337.git.gabrielopcode@gmail.com> Date: Fri, 6 Nov 2020 18:03:35 +0200 In-Reply-To: References: When a window is on a virtual desktop, the WM iconifies it without marking it "hidden". We use that to mark it as "shell cloaked" instead of minimizing the window, to match what Windows does when switching virtual desktops. This fixes applications which do things when minimized, since they are *not* minimized on Windows when switching desktops, unlike on Wine currently. When on a different desktop, GetWindowCompositionAttribute always reports DWM_CLOAKED_SHELL. The shell cloaking the window (when on a different desktop) is tracked independently of the manual cloaking done by an app. It always cloaks it when on a different desktop, even if the app asks the window to be uncloaked. However, when going back to the original desktop, the window will be uncloaked (because the app uncloaked it while off-desktop). So even if DWM_CLOAKED_SHELL is reported, it's possible that the app is still manually cloaked underneath, with no way to know until you switch back to the desktop containing it and calling the API again. Windows on a different desktop (i.e. cloaked by the shell) can't be set to the foreground. It simply fails. If some item on the desktop is selected and has focus, then switching desktops keeps it selected and no window gets activated on a desktop switch, because the desktop items are common between desktops. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=43691 Signed-off-by: Gabriel Ivăncescu --- dlls/winex11.drv/event.c | 47 +++++++++++++++++++++++++++++++--- dlls/winex11.drv/window.c | 13 ++++++++-- dlls/winex11.drv/x11drv.h | 4 +++ dlls/winex11.drv/x11drv_main.c | 1 + server/protocol.def | 1 + server/window.c | 16 +++++++++--- 6 files changed, 74 insertions(+), 8 deletions(-) diff --git a/dlls/winex11.drv/event.c b/dlls/winex11.drv/event.c index d21d2a7..e27412c 100644 --- a/dlls/winex11.drv/event.c +++ b/dlls/winex11.drv/event.c @@ -549,15 +549,20 @@ DWORD EVENT_x11_time_to_win32_time(Time time) static inline BOOL can_activate_window( HWND hwnd ) { LONG style = GetWindowLongW( hwnd, GWL_STYLE ); + struct x11drv_win_data *data; + BOOL off_desktop; RECT rect; if (!(style & WS_VISIBLE)) return FALSE; if ((style & (WS_POPUP|WS_CHILD)) == WS_CHILD) return FALSE; - if (style & WS_MINIMIZE) return FALSE; + if (style & (WS_MINIMIZE | WS_DISABLED)) return FALSE; if (GetWindowLongW( hwnd, GWL_EXSTYLE ) & WS_EX_NOACTIVATE) return FALSE; if (hwnd == GetDesktopWindow()) return FALSE; if (GetWindowRect( hwnd, &rect ) && IsRectEmpty( &rect )) return FALSE; - return !(style & WS_DISABLED); + if (!(data = get_win_data( hwnd ))) return FALSE; + off_desktop = data->off_desktop; + release_win_data( data ); + return !off_desktop; } @@ -1096,7 +1101,7 @@ static BOOL X11DRV_ConfigureNotify( HWND hwnd, XEvent *xev ) if (!hwnd) return FALSE; if (!(data = get_win_data( hwnd ))) return FALSE; - if (!data->mapped || data->iconic) goto done; + if (!data->mapped || data->iconic || data->off_desktop) goto done; if (data->whole_window && !data->managed) goto done; /* ignore synthetic events on foreign windows */ if (event->send_event && !data->whole_window) goto done; @@ -1325,6 +1330,26 @@ static void handle_wm_state_notify( HWND hwnd, XPropertyEvent *event, BOOL updat } else if (!data->iconic && data->wm_state == IconicState) { + /* Check if the window is on another desktop rather than actually minimized */ + read_net_wm_states( event->display, data ); + if (!(data->net_wm_state & (1 << NET_WM_STATE_HIDDEN))) + { + if (!data->off_desktop) + { + struct WINCOMPATTRDATA attr; + BOOL cloak = TRUE; + + attr.attribute = WCA_CLOAK; + attr.pData = &cloak; + attr.dataSize = sizeof(cloak); + data->shell_cloak = TRUE; + SetWindowCompositionAttribute( hwnd, &attr ); + data->shell_cloak = FALSE; + data->off_desktop = TRUE; + } + goto done; + } + data->iconic = TRUE; if ((style & WS_MINIMIZEBOX) && !(style & WS_DISABLED)) { @@ -1335,6 +1360,22 @@ static void handle_wm_state_notify( HWND hwnd, XPropertyEvent *event, BOOL updat } TRACE( "not minimizing win %p/%lx style %08x\n", data->hwnd, data->whole_window, style ); } + else if (!data->iconic && data->wm_state == NormalState) + { + if (data->off_desktop) + { + struct WINCOMPATTRDATA attr; + BOOL cloak = FALSE; + + attr.attribute = WCA_CLOAK; + attr.pData = &cloak; + attr.dataSize = sizeof(cloak); + data->shell_cloak = TRUE; + SetWindowCompositionAttribute( hwnd, &attr ); + data->shell_cloak = FALSE; + data->off_desktop = FALSE; + } + } done: release_win_data( data ); } diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index 3e4f9eb..0d829e1 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -71,6 +71,7 @@ static const unsigned int net_wm_state_atoms[NB_NET_WM_STATES] = { XATOM__NET_WM_STATE_FULLSCREEN, XATOM__NET_WM_STATE_ABOVE, + XATOM__NET_WM_STATE_HIDDEN, XATOM__NET_WM_STATE_MAXIMIZED_VERT, XATOM__NET_WM_STATE_SKIP_PAGER, XATOM__NET_WM_STATE_SKIP_TASKBAR @@ -1263,7 +1264,7 @@ static void sync_window_position( struct x11drv_win_data *data, XWindowChanges changes; unsigned int mask = 0; - if (data->managed && data->iconic) return; + if (data->managed && (data->iconic || data->off_desktop)) return; /* resizing a managed maximized window is not allowed */ if (!(style & WS_MAXIMIZE) || !data->managed) @@ -2552,7 +2553,7 @@ UINT CDECL X11DRV_ShowWindow( HWND hwnd, INT cmd, RECT *rect, UINT swp ) } goto done; } - if (!data->managed || !data->mapped || data->iconic) goto done; + if (!data->managed || !data->mapped || data->iconic || data->off_desktop) goto done; /* only fetch the new rectangle if the ShowWindow was a result of a window manager event */ @@ -2598,6 +2599,14 @@ DWORD CDECL X11DRV_SetWindowCompositionAttribute( HWND hwnd, DWORD attribute, vo } ret = *(BOOL*)attr_data ? SET_WINDOW_CLOAKED_ON : 0; + if (data->shell_cloak) + { + ret |= SET_WINDOW_CLOAKED_SHELL; + data->shell_cloak = FALSE; + release_win_data( data ); + break; + } + /* If the owner is cloaked, manual uncloaking is not allowed */ if (!ret && (owner = GetWindow( hwnd, GW_OWNER ))) { diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h index d62fcfd..bf9000f 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -452,6 +452,7 @@ enum x11drv_atoms XATOM__NET_WM_STATE_ABOVE, XATOM__NET_WM_STATE_DEMANDS_ATTENTION, XATOM__NET_WM_STATE_FULLSCREEN, + XATOM__NET_WM_STATE_HIDDEN, XATOM__NET_WM_STATE_MAXIMIZED_HORZ, XATOM__NET_WM_STATE_MAXIMIZED_VERT, XATOM__NET_WM_STATE_SKIP_PAGER, @@ -547,6 +548,7 @@ enum x11drv_net_wm_state { NET_WM_STATE_FULLSCREEN, NET_WM_STATE_ABOVE, + NET_WM_STATE_HIDDEN, NET_WM_STATE_MAXIMIZED, NET_WM_STATE_SKIP_PAGER, NET_WM_STATE_SKIP_TASKBAR, @@ -575,6 +577,8 @@ struct x11drv_win_data BOOL shaped : 1; /* is window using a custom region shape? */ BOOL layered : 1; /* is window layered and with valid attributes? */ BOOL use_alpha : 1; /* does window use an alpha channel? */ + BOOL off_desktop : 1;/* is window on another WM desktop? */ + BOOL shell_cloak : 1;/* the shell is (un)cloaking the window */ int wm_state; /* current value of the WM_STATE property */ DWORD net_wm_state; /* bit mask of active x11drv_net_wm_state values */ Window embedder; /* window id of embedder */ diff --git a/dlls/winex11.drv/x11drv_main.c b/dlls/winex11.drv/x11drv_main.c index 9ec4c7a..639a291 100644 --- a/dlls/winex11.drv/x11drv_main.c +++ b/dlls/winex11.drv/x11drv_main.c @@ -165,6 +165,7 @@ static const char * const atom_names[NB_XATOMS - FIRST_XATOM] = "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_DEMANDS_ATTENTION", "_NET_WM_STATE_FULLSCREEN", + "_NET_WM_STATE_HIDDEN", "_NET_WM_STATE_MAXIMIZED_HORZ", "_NET_WM_STATE_MAXIMIZED_VERT", "_NET_WM_STATE_SKIP_PAGER", diff --git a/server/protocol.def b/server/protocol.def index 1bb4dcb..daed9e9 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -2318,6 +2318,7 @@ enum message_type VARARG(windows,user_handles); /* window handles that inherit it */ @END #define SET_WINDOW_CLOAKED_ON 0x01 +#define SET_WINDOW_CLOAKED_SHELL 0x02 /* Set the parent of a window */ diff --git a/server/window.c b/server/window.c index 356995a..4a73c0a 100644 --- a/server/window.c +++ b/server/window.c @@ -83,6 +83,7 @@ struct window unsigned int is_linked : 1; /* is it linked into the parent z-order list? */ unsigned int is_layered : 1; /* has layered info been set? */ unsigned int is_cloaked : 1; /* is the window cloaked by the app? */ + unsigned int is_cloaked_by_shell : 1; unsigned int color_key; /* color key for a layered window */ unsigned int alpha; /* alpha value for a layered window */ unsigned int layered_flags; /* flags for a layered window */ @@ -506,6 +507,7 @@ static struct window *create_window( struct window *parent, struct window *owner win->is_linked = 0; win->is_layered = 0; win->is_cloaked = 0; + win->is_cloaked_by_shell = 0; win->dpi_awareness = DPI_AWARENESS_PER_MONITOR_AWARE; win->dpi = 0; win->user_data = 0; @@ -609,7 +611,7 @@ int is_child_window( user_handle_t parent, user_handle_t child ) int is_valid_foreground_window( user_handle_t window ) { struct window *win = get_user_object( window, USER_WINDOW ); - return win && (win->style & (WS_POPUP|WS_CHILD)) != WS_CHILD; + return win && (win->style & (WS_POPUP|WS_CHILD)) != WS_CHILD && !win->is_cloaked_by_shell; } /* make a window active if possible */ @@ -787,7 +789,7 @@ static int get_window_children_from_point( struct window *parent, int x, int y, { int x_child = x, y_child = y; - if (is_desktop_window( parent ) && ptr->is_cloaked) continue; + if (is_desktop_window( parent ) && (ptr->is_cloaked || ptr->is_cloaked_by_shell)) continue; if (!is_point_in_window( ptr, &x_child, &y_child, parent->dpi )) continue; /* skip it */ /* if point is in client area, and window is not minimized or disabled, check children */ @@ -2191,7 +2193,8 @@ DECL_HANDLER(get_window_cloaked) cloaked |= DWM_CLOAKED_INHERITED; } - if (win->is_cloaked) cloaked |= DWM_CLOAKED_APP; + if (win->is_cloaked_by_shell) cloaked |= DWM_CLOAKED_SHELL; + else if (win->is_cloaked) cloaked |= DWM_CLOAKED_APP; else cloaked = 0; reply->cloaked = cloaked; @@ -2206,6 +2209,13 @@ DECL_HANDLER(set_window_cloaked) user_handle_t *data; if (!win) return; + if (req->cloaked & SET_WINDOW_CLOAKED_SHELL) + { + /* the shell has control over cloaking windows individually */ + win->is_cloaked_by_shell = req->cloaked & SET_WINDOW_CLOAKED_ON; + reply->count = 0; + return; + } if (is_desktop_window( win )) { set_error( STATUS_ACCESS_DENIED ); -- 2.21.0