Better window detection for when a window regains focus.
This commit is contained in:
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')
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user