Better window detection for when a window regains focus.

This commit is contained in:
Storm Dragon committed 2026-10-15 13:25:47 -04:00
1 parent e3de71e594
commit ea509509e4
6 files changed
+67 -14

No files matched your search

@@ -1,6 +1,6 @@
# Maintainer: Storm Dragon <storm_dragon@stormux.org>
pkgname=wine-access-git
pkgver=r41.gb16c696
pkgver=r43.ge3de71e
pkgrel=1
pkgdesc='Wine accessibility helper with MSAA, NVDA Controller, and Tolk compatibility for screen readers'
arch=('x86_64')
+2 -2
View File
@@ -19,12 +19,12 @@ BOOL CALLBACK find_candidate(HWND window, LPARAM data) {
}
bool restore_virtual_desktop_focus() {
if (GetForegroundWindow() || !wine_x11_desktop_focused()) return false;
if (GetForegroundWindow()) return false;
Candidate candidate;
EnumWindows(find_candidate, reinterpret_cast<LPARAM>(&candidate));
// Recheck focus after enumeration so a Linux/application transition cannot
// turn a recovery into activation of a background Wine application.
if (candidate.count != 1 || GetForegroundWindow() || !wine_x11_desktop_focused())
if (candidate.count != 1 || GetForegroundWindow() || !wine_x11_desktop_focused(candidate.window))
return false;
return wine_x11_restore_focus(candidate.window);
}
+25 -8
View File
@@ -61,7 +61,7 @@ bool review_x11_geometry(HWND window, RECT &bounds, POINT &origin) {
return true;
}
bool wine_x11_desktop_focused() {
bool wine_x11_desktop_focused(HWND window) {
USEROBJECTFLAGS flags{};
if (!GetUserObjectInformationW(GetThreadDesktop(GetCurrentThreadId()), UOI_FLAGS,
&flags, sizeof(flags), nullptr) || !(flags.dwFlags & 0x80000000))
@@ -71,7 +71,24 @@ bool wine_x11_desktop_focused() {
return false;
const uint64_t deadline = GetTickCount64() + 20;
auto focus = reply<xcb_get_input_focus_reply_t>(xcb_get_input_focus(connection.value).sequence, deadline);
if (!focus || focus->focus != xid) return false;
if (!focus) return false;
if (focus->focus != xid) {
if (!window) return false;
const auto child = reinterpret_cast<uintptr_t>(GetPropW(window, L"__wine_x11_whole_window"));
if (!child || child > UINT32_MAX) return false;
auto tree = reply<xcb_query_tree_reply_t>(xcb_query_tree(connection.value, child).sequence, deadline);
if (!tree || tree->parent != xid) return false;
// A focused native client can outlive Wine's remembered Win32 foreground.
// Accept only this application's hierarchy, never a sibling Wine child.
auto focused = focus->focus;
for (unsigned depth = 0; focused != child && depth < 64; ++depth) {
if (!focused || focused == XCB_INPUT_FOCUS_POINTER_ROOT || focused == xid) return false;
tree = reply<xcb_query_tree_reply_t>(xcb_query_tree(connection.value, focused).sequence, deadline);
if (!tree || tree->parent == focused) return false;
focused = tree->parent;
}
if (focused != child) return false;
}
auto size = reply<xcb_get_geometry_reply_t>(xcb_get_geometry(connection.value, xid).sequence, deadline);
if (!size) return false;
constexpr char name[] = "_NET_ACTIVE_WINDOW";
@@ -79,16 +96,16 @@ bool wine_x11_desktop_focused() {
if (!atom || !atom->atom) return false;
auto active = reply<xcb_get_property_reply_t>(xcb_get_property(connection.value, 0, size->root,
atom->atom, XCB_ATOM_WINDOW, 0, 1).sequence, deadline);
uint32_t window = 0;
uint32_t activeWindow = 0;
if (!active || active->type != XCB_ATOM_WINDOW || active->format != 32 ||
xcb_get_property_value_length(active.get()) != sizeof(window))
xcb_get_property_value_length(active.get()) != sizeof(activeWindow))
return false;
std::memcpy(&window, xcb_get_property_value(active.get()), sizeof(window));
return window == xid;
std::memcpy(&activeWindow, xcb_get_property_value(active.get()), sizeof(activeWindow));
return activeWindow == xid;
}
bool wine_x11_restore_focus(HWND window) {
if (!wine_x11_desktop_focused()) return false;
if (!wine_x11_desktop_focused(window)) return false;
const auto xid = reinterpret_cast<uintptr_t>(GetPropW(window, L"__wine_x11_whole_window"));
const auto desktop = reinterpret_cast<uintptr_t>(GetPropW(GetDesktopWindow(), L"__wine_x11_whole_window"));
if (!xid || xid > UINT32_MAX) return false;
@@ -105,7 +122,7 @@ bool wine_x11_restore_focus(HWND window) {
sizeof(focusName) - 1, focusName).sequence, deadline);
if (!protocols || !protocols->atom || !takeFocus || !takeFocus->atom) return false;
if (GetForegroundWindow() || !IsWindowVisible(window) || !IsWindowEnabled(window) ||
IsIconic(window) || !wine_x11_desktop_focused())
IsIconic(window) || !wine_x11_desktop_focused(window))
return false;
xcb_client_message_event_t event{};
event.response_type = XCB_CLIENT_MESSAGE;
+3 -2
View File
@@ -7,8 +7,9 @@
bool review_x11_geometry(HWND window, RECT &bounds, POINT &origin);
// True only when both the window manager and X11 focus select this prefix's
// virtual desktop itself, rather than another application or desktop child.
bool wine_x11_desktop_focused();
// virtual desktop, with keyboard focus on the desktop itself or, when supplied,
// the verified application's X11 child hierarchy.
bool wine_x11_desktop_focused(HWND window = nullptr);
// Deliver native focus to the application's actual child of this desktop.
bool wine_x11_restore_focus(HWND window);
+4 -1
View File
@@ -202,7 +202,10 @@ Windows launcher fixture. It reproduces a visible launcher returned without
activation, then checks recovery of its native foreground and button focus.
It also checks Linux focus, mismatched window-manager/keyboard focus, disabled
windows, multiple visible applications, hidden windows, existing foreground,
and repeated recovery. The fixture acknowledges ICCCM window visibility on
and repeated recovery. It also reproduces X11 focus on the launcher child
with no Win32 foreground, as seen after AudioQuake exits: recovery must send
Wine's activation notification even when X11 focus already selects that child.
The fixture acknowledges ICCCM window visibility on
the private display; the runner propagates the fixture's actual exit status.
After installing the updated helper and restarting the reader, open AudioQuake,
+32
View File
@@ -130,6 +130,38 @@ int main(int argc, char **argv) {
focus(linuxWindow, linuxWindow);
focus(desktop, desktop);
ok &= check(restore_virtual_desktop_focus(), "later workspace return recovers again");
pump();
ShowWindow(launcher, SW_HIDE);
pump();
ShowWindow(launcher, SW_SHOWNOACTIVATE);
acknowledge(launcher);
focus(linuxWindow, linuxWindow);
const auto launcherXid = static_cast<xcb_window_t>(reinterpret_cast<uintptr_t>(
GetPropW(launcher, L"__wine_x11_whole_window")));
focus(launcherXid, linuxWindow);
ok &= check(!restore_virtual_desktop_focus(), "focused child requires window manager desktop ownership");
ShowWindow(other, SW_SHOWNOACTIVATE);
acknowledge(other);
focus(launcherXid, desktop);
ok &= check(!restore_virtual_desktop_focus(), "focused child does not bypass ambiguous applications");
ShowWindow(other, SW_HIDE);
const auto siblingXid = xcb_generate_id(connection);
xcb_create_window(connection, XCB_COPY_FROM_PARENT, siblingXid, desktop,
0, 0, 20, 20, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, 0, nullptr);
xcb_map_window(connection, siblingXid);
focus(siblingXid, desktop);
ok &= check(!restore_virtual_desktop_focus(), "unrelated desktop child is never redirected to launcher");
xcb_destroy_window(connection, siblingXid);
focus(launcherXid, desktop);
EnableWindow(launcher, FALSE);
ok &= check(!restore_virtual_desktop_focus(), "focused child does not bypass disabled application guard");
EnableWindow(launcher, TRUE);
ok &= check(!GetForegroundWindow(), "reproduce focused X11 child without Win32 foreground");
ok &= check(restore_virtual_desktop_focus(), "focused launcher child recovers foreground");
pump();
GetGUIThreadInfo(GetWindowThreadProcessId(launcher, nullptr), &gui);
ok &= check(GetForegroundWindow() == launcher && gui.hwndFocus == button,
"focused launcher child restores its control focus");
DestroyWindow(other);
PostMessageW(launcher, WM_CLOSE, 0, 0);
WaitForSingleObject(process.hProcess, 5000);