diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index fa4973c2..b8402d32 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -5004,6 +5004,94 @@ mg_tabwindow_de_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) } #ifdef G_OS_WIN32 +/* GTK3's win32 backend does not minimize the window when its taskbar + * button is clicked while the window is active; the shell's minimize + * fails and, as the upstream report puts it, "the window only becomes + * unfocused but remains in the foreground" + * (https://gitlab.gnome.org/GNOME/gtk/-/issues/3749). + * + * We use exactly that signature to tell the two taskbar gestures apart: + * after a taskbar click deactivates the window, look at the foreground + * window. If this window is still the foreground window (the shell tried + * to minimize it and failed), the click was on its own button, so + * minimize it manually. If activation moved to the taskbar, the Start + * menu, another application or one of our other windows, the user was + * not clicking this window's button, so leave it alone. + */ +#define MG_WIN32_TASKBAR_MINIMIZE_DELAY_MS 200 + +static guint mg_win32_taskbar_minimize_source_id = 0; +static HWND mg_win32_taskbar_minimize_hwnd = NULL; + +static gboolean +mg_win32_hwnd_is_taskbar (HWND hwnd) +{ + wchar_t class_name[32]; + HWND root; + + if (!hwnd) + return FALSE; + + root = GetAncestor (hwnd, GA_ROOT); + if (!root) + root = hwnd; + + if (GetClassNameW (root, class_name, G_N_ELEMENTS (class_name)) == 0) + return FALSE; + + return lstrcmpW (class_name, L"Shell_TrayWnd") == 0 + || lstrcmpW (class_name, L"Shell_SecondaryTrayWnd") == 0; +} + +static gboolean +mg_win32_taskbar_minimize_cb (gpointer userdata) +{ + HWND hwnd = mg_win32_taskbar_minimize_hwnd; + HWND foreground; + + mg_win32_taskbar_minimize_source_id = 0; + mg_win32_taskbar_minimize_hwnd = NULL; + + if (!hwnd || !IsWindow (hwnd) || !IsWindowVisible (hwnd) || IsIconic (hwnd)) + return G_SOURCE_REMOVE; + + foreground = GetForegroundWindow (); + + /* Activation moved to another window (another application, the + * taskbar itself for an empty-space/Start/tray click, or one of our + * other windows), so the taskbar click was not on this window's own + * button - leave it alone. Only the failed shell-minimize leaves this + * window still foreground (or leaves nothing foreground at all). */ + if (foreground != NULL && foreground != hwnd) + return G_SOURCE_REMOVE; + + PostMessage (hwnd, WM_SYSCOMMAND, SC_MINIMIZE, 0); + return G_SOURCE_REMOVE; +} + +static void +mg_win32_taskbar_click_check (HWND hwnd) +{ + POINT cursor_pos; + + if (!IsWindowVisible (hwnd) || IsIconic (hwnd)) + return; + + if (!GetCursorPos (&cursor_pos)) + return; + + if (!mg_win32_hwnd_is_taskbar (WindowFromPoint (cursor_pos))) + return; + + if (mg_win32_taskbar_minimize_source_id) + g_source_remove (mg_win32_taskbar_minimize_source_id); + + mg_win32_taskbar_minimize_hwnd = hwnd; + mg_win32_taskbar_minimize_source_id = + g_timeout_add (MG_WIN32_TASKBAR_MINIMIZE_DELAY_MS, + mg_win32_taskbar_minimize_cb, NULL); +} + static GdkFilterReturn mg_win32_filter (GdkXEvent *xevent, GdkEvent *event, gpointer data) { @@ -5012,6 +5100,13 @@ mg_win32_filter (GdkXEvent *xevent, GdkEvent *event, gpointer data) if (!msg) return GDK_FILTER_CONTINUE; + if (msg->message == WM_ACTIVATE) + { + if (LOWORD (msg->wParam) == WA_INACTIVE) + mg_win32_taskbar_click_check (msg->hwnd); + return GDK_FILTER_CONTINUE; + } + if (msg->message == WM_TIMECHANGE) { _tzset();