Bug fixes for reclaiming focus.

This commit is contained in:
Storm Dragon committed 2026-10-15 13:39:39 -04:00
1 parent ea509509e4
commit 52a3fddc77
6 files changed
+30 -43

No files matched your search

@@ -1,6 +1,6 @@
# Maintainer: Storm Dragon <storm_dragon@stormux.org>
pkgname=wine-access-git
pkgver=r43.ge3de71e
pkgver=r44.gea50950
pkgrel=1
pkgdesc='Wine accessibility helper with MSAA, NVDA Controller, and Tolk compatibility for screen readers'
arch=('x86_64')
+1
View File
@@ -3,4 +3,5 @@
// Recover a virtual desktop which regained X11 focus with no Win32 foreground.
// Leave existing focus, other desktops and ambiguous application sets alone.
// Returns whether activation was queued; the application restores control focus.
bool restore_virtual_desktop_focus();
+8 -17
View File
@@ -112,26 +112,17 @@ bool wine_x11_restore_focus(HWND window) {
const uint64_t deadline = GetTickCount64() + 20;
auto tree = reply<xcb_query_tree_reply_t>(xcb_query_tree(connection.value, xid).sequence, deadline);
if (!tree || tree->parent != desktop) return false;
// Native focus informs Wine's application thread of activation. A helper's
// SetForegroundWindow can be denied by the Win32 foreground-lock policy.
constexpr char protocolsName[] = "WM_PROTOCOLS";
constexpr char focusName[] = "WM_TAKE_FOCUS";
auto protocols = reply<xcb_intern_atom_reply_t>(xcb_intern_atom(connection.value, 1,
sizeof(protocolsName) - 1, protocolsName).sequence, deadline);
auto takeFocus = reply<xcb_intern_atom_reply_t>(xcb_intern_atom(connection.value, 1,
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(window))
return false;
xcb_client_message_event_t event{};
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.window = xid;
event.type = protocols->atom;
event.data.data32[0] = takeFocus->atom;
event.data.data32[1] = XCB_CURRENT_TIME;
xcb_send_event(connection.value, 0, xid, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char *>(&event));
// WM_TAKE_FOCUS can be ignored while Wine awaits WM_STATE, which can remain
// unset for virtual-desktop children. Queue activation on the owner's thread:
// a helper-thread SetForegroundWindow can be denied by Wine's input policy.
// Async dispatch avoids waiting on an unresponsive application's window proc.
if (!SetWindowPos(window, HWND_TOP, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_ASYNCWINDOWPOS) ||
!wine_x11_desktop_focused(window))
return false;
xcb_set_input_focus(connection.value, XCB_INPUT_FOCUS_PARENT, xid, XCB_CURRENT_TIME);
auto focus = reply<xcb_get_input_focus_reply_t>(xcb_get_input_focus(connection.value).sequence, deadline);
return focus && focus->focus == xid;
+2 -1
View File
@@ -11,5 +11,6 @@ bool review_x11_geometry(HWND window, RECT &bounds, POINT &origin);
// 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.
// Queue Win32 activation on the verified child's thread and set native focus.
// A true result means dispatched; control focus arrives asynchronously.
bool wine_x11_restore_focus(HWND window);
+13 -4
View File
@@ -203,10 +203,19 @@ 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. 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.
with no Win32 foreground, as seen after AudioQuake exits. Recovery must restore
both Win32 activation/control focus and native keyboard focus, even when X11
already selects that child. The fixture deliberately leaves `WM_STATE` unset,
matching Wine's real virtual-desktop children. Adding a synthetic window-manager
acknowledgement masks the failure: native `WM_TAKE_FOCUS` alone can otherwise be
ignored while Wine awaits that state change. The runner propagates the fixture's
actual exit status.
Recovery queues `SetWindowPos` activation on the application's own window
thread and sets native X11 focus without changing its size or position. It
does not wait on the application's window procedure. A dispatched request
alone is not acceptance: the fixture then checks Win32 foreground and the
restored button focus after the application processes activation.
After installing the updated helper and restarting the reader, open AudioQuake,
play and close a game, then Tab and Shift+Tab through the launcher. Check speech
+5 -20
View File
@@ -57,21 +57,6 @@ int main(int argc, char **argv) {
if (!atomReply) return 2;
const auto activeAtom = atomReply->atom;
std::free(atomReply);
constexpr char stateName[] = "WM_STATE";
auto *stateReply = xcb_intern_atom_reply(connection,
xcb_intern_atom(connection, 0, sizeof(stateName) - 1, stateName), nullptr);
if (!stateReply) return 2;
const auto stateAtom = stateReply->atom;
std::free(stateReply);
auto acknowledge = [&](HWND window) {
const auto xid = static_cast<xcb_window_t>(reinterpret_cast<uintptr_t>(
GetPropW(window, L"__wine_x11_whole_window")));
const uint32_t state[] = {1, XCB_WINDOW_NONE}; // ICCCM NormalState.
xcb_change_property(connection, XCB_PROP_MODE_REPLACE, xid,
stateAtom, stateAtom, 32, 2, state);
xcb_flush(connection);
pump();
};
const auto linuxWindow = xcb_generate_id(connection);
xcb_create_window(connection, XCB_COPY_FROM_PARENT, linuxWindow, screen->root,
0, 0, 20, 20, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, 0, nullptr);
@@ -97,7 +82,7 @@ int main(int argc, char **argv) {
ShowWindow(launcher, SW_HIDE);
pump();
ShowWindow(launcher, SW_SHOWNOACTIVATE);
acknowledge(launcher);
pump();
focus(linuxWindow, linuxWindow);
ok &= check(!GetForegroundWindow(), "Linux focus clears Wine foreground");
ok &= check(!restore_virtual_desktop_focus() && !GetForegroundWindow(), "Linux focus is never taken");
@@ -111,7 +96,7 @@ int main(int argc, char **argv) {
ok &= check(!restore_virtual_desktop_focus(), "disabled application is not activated");
EnableWindow(launcher, TRUE);
ShowWindow(other, SW_SHOWNOACTIVATE);
acknowledge(other);
pump();
focus(linuxWindow, linuxWindow);
focus(desktop, desktop);
ok &= check(!restore_virtual_desktop_focus() && !GetForegroundWindow(), "ambiguous applications are not chosen");
@@ -126,7 +111,7 @@ int main(int argc, char **argv) {
ShowWindow(launcher, SW_HIDE);
pump();
ShowWindow(launcher, SW_SHOWNOACTIVATE);
acknowledge(launcher);
pump();
focus(linuxWindow, linuxWindow);
focus(desktop, desktop);
ok &= check(restore_virtual_desktop_focus(), "later workspace return recovers again");
@@ -134,14 +119,14 @@ int main(int argc, char **argv) {
ShowWindow(launcher, SW_HIDE);
pump();
ShowWindow(launcher, SW_SHOWNOACTIVATE);
acknowledge(launcher);
pump();
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);
pump();
focus(launcherXid, desktop);
ok &= check(!restore_virtual_desktop_focus(), "focused child does not bypass ambiguous applications");
ShowWindow(other, SW_HIDE);