mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-07-06 20:19:24 +00:00
Use UIA to target Win taskbar minimize workaround
This commit is contained in:
@@ -31,7 +31,7 @@
|
|||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<AdditionalLibraryDirectories>$(ArchiveLibDir);$(DepsRoot)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
<AdditionalLibraryDirectories>$(ArchiveLibDir);$(DepsRoot)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||||
<AdditionalDependencies>$(DepLibs);$(ZoiteChatLib)common.lib;wbemuuid.lib;dwmapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
<AdditionalDependencies>$(DepLibs);$(ZoiteChatLib)common.lib;wbemuuid.lib;dwmapi.lib;ole32.lib;oleaut32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
|
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
#ifdef G_OS_WIN32
|
#ifdef G_OS_WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
|
#include <uiautomationclient.h>
|
||||||
#include <gdk/gdkwin32.h>
|
#include <gdk/gdkwin32.h>
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -5004,6 +5005,234 @@ mg_tabwindow_de_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef G_OS_WIN32
|
#ifdef G_OS_WIN32
|
||||||
|
/* GTK3's win32 backend breaks the shell's minimize-on-taskbar-button-click:
|
||||||
|
* when the taskbar button of the active window is clicked, the window only
|
||||||
|
* loses activation but stays on screen
|
||||||
|
* (https://gitlab.gnome.org/GNOME/gtk/-/issues/3749).
|
||||||
|
*
|
||||||
|
* Activation state alone cannot identify that gesture: clicking empty
|
||||||
|
* taskbar space, the Start button, the tray or another application's
|
||||||
|
* button also deactivates this window while the pointer is over the
|
||||||
|
* taskbar, and in each case (including the failed minimize) activation
|
||||||
|
* ends up outside this window. So when activation is lost with the
|
||||||
|
* pointer over the taskbar, hit-test the click point with UI Automation:
|
||||||
|
* only a taskbar button whose accessible name refers to this window's
|
||||||
|
* title is treated as a minimize request, and the minimize is then
|
||||||
|
* performed manually after a short delay (which also lets a working
|
||||||
|
* shell minimize win, should GTK ever be fixed).
|
||||||
|
*/
|
||||||
|
#define MG_WIN32_TASKBAR_MINIMIZE_DELAY_MS 200
|
||||||
|
#define MG_WIN32_UIA_BUTTON_CONTROL_TYPE 50000 /* UIA_ButtonControlTypeId */
|
||||||
|
#define MG_WIN32_UIA_MAX_ANCESTORS 8
|
||||||
|
|
||||||
|
/* CLSID_CUIAutomation / IID_IUIAutomation, defined locally so no
|
||||||
|
* uuid import library is needed */
|
||||||
|
static const CLSID mg_win32_clsid_cuiautomation =
|
||||||
|
{ 0xff48dba4, 0x60ef, 0x4201, { 0xaa, 0x87, 0x54, 0x10, 0x3e, 0xef, 0x59, 0x4e } };
|
||||||
|
static const IID mg_win32_iid_iuiautomation =
|
||||||
|
{ 0x30cbe57d, 0xd9d0, 0x452a, { 0xab, 0x13, 0x7a, 0xc5, 0xac, 0x48, 0x25, 0xee } };
|
||||||
|
|
||||||
|
static guint mg_win32_taskbar_minimize_source_id = 0;
|
||||||
|
static HWND mg_win32_taskbar_minimize_hwnd = NULL;
|
||||||
|
static POINT mg_win32_taskbar_minimize_point;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The main thread is COM-initialized for the process lifetime (see main ()
|
||||||
|
* in src/common/zoitechat.c), so the instance can be created lazily and
|
||||||
|
* kept until exit. */
|
||||||
|
static IUIAutomation *
|
||||||
|
mg_win32_get_uia (void)
|
||||||
|
{
|
||||||
|
static IUIAutomation *uia = NULL;
|
||||||
|
static gboolean failed = FALSE;
|
||||||
|
|
||||||
|
if (uia == NULL && !failed)
|
||||||
|
{
|
||||||
|
if (FAILED (CoCreateInstance (&mg_win32_clsid_cuiautomation, NULL,
|
||||||
|
CLSCTX_INPROC_SERVER,
|
||||||
|
&mg_win32_iid_iuiautomation,
|
||||||
|
(void **)&uia)))
|
||||||
|
{
|
||||||
|
uia = NULL;
|
||||||
|
failed = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uia;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A taskbar button's accessible name refers to a window title if one
|
||||||
|
* string equals the other or extends it at a separator: the button of a
|
||||||
|
* single window carries the title, on Win11 with a suffix
|
||||||
|
* ("<title> - 1 running window"), and either string may briefly trail
|
||||||
|
* the other while the shell catches up with a title change (e.g. the
|
||||||
|
* " (52)" user count appearing). A button grouping several windows is
|
||||||
|
* named after the application instead and deliberately does not match:
|
||||||
|
* clicking a group opens its thumbnails rather than minimizing. */
|
||||||
|
static gboolean
|
||||||
|
mg_win32_button_name_matches_title (const wchar_t *name, const wchar_t *title)
|
||||||
|
{
|
||||||
|
size_t name_len, title_len, common;
|
||||||
|
wchar_t next;
|
||||||
|
|
||||||
|
if (!name || !title)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
name_len = wcslen (name);
|
||||||
|
title_len = wcslen (title);
|
||||||
|
common = MIN (name_len, title_len);
|
||||||
|
|
||||||
|
if (common == 0 || wcsncmp (name, title, common) != 0)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (name_len == title_len)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
next = (name_len > title_len) ? name[common] : title[common];
|
||||||
|
return next == L' ' || next == L':' || next == L'-';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Was the click at pt on this window's own taskbar button? Hit-test the
|
||||||
|
* point and search the element and a few ancestors for a button whose
|
||||||
|
* name refers to this window. Empty taskbar space has no button ancestor,
|
||||||
|
* and other buttons (Start, tray icons, other applications' windows)
|
||||||
|
* fail the name check. */
|
||||||
|
static gboolean
|
||||||
|
mg_win32_taskbar_click_on_own_button (IUIAutomation *uia, HWND hwnd, POINT pt)
|
||||||
|
{
|
||||||
|
IUIAutomationTreeWalker *walker = NULL;
|
||||||
|
IUIAutomationElement *element = NULL;
|
||||||
|
wchar_t title[512];
|
||||||
|
gboolean match = FALSE;
|
||||||
|
int depth;
|
||||||
|
|
||||||
|
if (GetWindowTextW (hwnd, title, G_N_ELEMENTS (title)) == 0)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (FAILED (uia->lpVtbl->ElementFromPoint (uia, pt, &element))
|
||||||
|
|| element == NULL)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (FAILED (uia->lpVtbl->get_ControlViewWalker (uia, &walker)))
|
||||||
|
walker = NULL;
|
||||||
|
|
||||||
|
for (depth = 0; element != NULL && depth < MG_WIN32_UIA_MAX_ANCESTORS; depth++)
|
||||||
|
{
|
||||||
|
IUIAutomationElement *parent = NULL;
|
||||||
|
CONTROLTYPEID control_type = 0;
|
||||||
|
|
||||||
|
if (SUCCEEDED (element->lpVtbl->get_CurrentControlType (element, &control_type))
|
||||||
|
&& control_type == MG_WIN32_UIA_BUTTON_CONTROL_TYPE)
|
||||||
|
{
|
||||||
|
BSTR name = NULL;
|
||||||
|
|
||||||
|
if (SUCCEEDED (element->lpVtbl->get_CurrentName (element, &name))
|
||||||
|
&& name != NULL)
|
||||||
|
{
|
||||||
|
match = mg_win32_button_name_matches_title (name, title);
|
||||||
|
SysFreeString (name);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (walker == NULL
|
||||||
|
|| FAILED (walker->lpVtbl->GetParentElement (walker, element, &parent)))
|
||||||
|
parent = NULL;
|
||||||
|
|
||||||
|
element->lpVtbl->Release (element);
|
||||||
|
element = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element)
|
||||||
|
element->lpVtbl->Release (element);
|
||||||
|
if (walker)
|
||||||
|
walker->lpVtbl->Release (walker);
|
||||||
|
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
mg_win32_taskbar_minimize_cb (gpointer userdata)
|
||||||
|
{
|
||||||
|
HWND hwnd = mg_win32_taskbar_minimize_hwnd;
|
||||||
|
POINT click_pos = mg_win32_taskbar_minimize_point;
|
||||||
|
IUIAutomation *uia;
|
||||||
|
|
||||||
|
mg_win32_taskbar_minimize_source_id = 0;
|
||||||
|
mg_win32_taskbar_minimize_hwnd = NULL;
|
||||||
|
|
||||||
|
/* Already minimized or gone: the shell's own minimize worked */
|
||||||
|
if (!hwnd || !IsWindow (hwnd) || !IsWindowVisible (hwnd) || IsIconic (hwnd))
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
|
||||||
|
/* Button still held: a taskbar-button drag, not a click */
|
||||||
|
if (GetAsyncKeyState (VK_LBUTTON) & 0x8000)
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
|
||||||
|
uia = mg_win32_get_uia ();
|
||||||
|
if (uia != NULL)
|
||||||
|
{
|
||||||
|
if (!mg_win32_taskbar_click_on_own_button (uia, hwnd, click_pos))
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* No UI Automation available: fall back to the activation
|
||||||
|
* signature - a failed shell minimize can leave this window
|
||||||
|
* as the foreground window. */
|
||||||
|
HWND foreground = GetForegroundWindow ();
|
||||||
|
|
||||||
|
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_point = cursor_pos;
|
||||||
|
mg_win32_taskbar_minimize_source_id =
|
||||||
|
g_timeout_add (MG_WIN32_TASKBAR_MINIMIZE_DELAY_MS,
|
||||||
|
mg_win32_taskbar_minimize_cb, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
static GdkFilterReturn
|
static GdkFilterReturn
|
||||||
mg_win32_filter (GdkXEvent *xevent, GdkEvent *event, gpointer data)
|
mg_win32_filter (GdkXEvent *xevent, GdkEvent *event, gpointer data)
|
||||||
{
|
{
|
||||||
@@ -5012,6 +5241,13 @@ mg_win32_filter (GdkXEvent *xevent, GdkEvent *event, gpointer data)
|
|||||||
if (!msg)
|
if (!msg)
|
||||||
return GDK_FILTER_CONTINUE;
|
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)
|
if (msg->message == WM_TIMECHANGE)
|
||||||
{
|
{
|
||||||
_tzset();
|
_tzset();
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ zoitechat_gtk_ldflags = []
|
|||||||
if host_machine.system() == 'windows'
|
if host_machine.system() == 'windows'
|
||||||
zoitechat_gtk_sources += 'notifications/notification-windows.c'
|
zoitechat_gtk_sources += 'notifications/notification-windows.c'
|
||||||
zoitechat_gtk_deps += cc.find_library('dwmapi', required: true)
|
zoitechat_gtk_deps += cc.find_library('dwmapi', required: true)
|
||||||
|
zoitechat_gtk_deps += cc.find_library('ole32', required: true) # taskbar minimize (UI Automation)
|
||||||
|
zoitechat_gtk_deps += cc.find_library('oleaut32', required: true) # taskbar minimize (UI Automation)
|
||||||
zoitechat_theme_deps += cc.find_library('dwmapi', required: true)
|
zoitechat_theme_deps += cc.find_library('dwmapi', required: true)
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user