diff --git a/src/focus_guard.cpp b/src/focus_guard.cpp index e863c21..c8bef14 100644 --- a/src/focus_guard.cpp +++ b/src/focus_guard.cpp @@ -98,8 +98,18 @@ struct FocusGuard::Impl { if (bad) reason = "X error"; if (type == XCB_PROPERTY_NOTIFY) { const auto* e = reinterpret_cast(event); - if (e->window == root && e->atom == active_atom) { bad = true; reason = "_NET_ACTIVE_WINDOW changed"; } - if (e->window == root && e->atom == gamescope_atom) { bad = true; reason = "GAMESCOPE_FOCUSED_WINDOW changed"; } + // Gamescope rewrites its focus properties with the same window + // after IME commits; only a different value is a focus change. + // A real X focus move still reports FocusOut on the target. + xcb_window_t value = XCB_WINDOW_NONE; + if (e->window == root && e->atom == active_atom && + (!property_window(active_atom, XCB_ATOM_WINDOW, value) || value != target)) { + bad = true; reason = "_NET_ACTIVE_WINDOW changed"; + } + if (e->window == root && e->atom == gamescope_atom && + (!property_window(gamescope_atom, XCB_ATOM_CARDINAL, value) || value != target)) { + bad = true; reason = "GAMESCOPE_FOCUSED_WINDOW changed"; + } } else if (type == XCB_FOCUS_OUT) { const auto* e = reinterpret_cast(event); if (e->event == target) { bad = true; reason = "FocusOut on target"; } diff --git a/tests/focus_guard_xcb_test.cpp b/tests/focus_guard_xcb_test.cpp index 60b6207..302ba9d 100644 --- a/tests/focus_guard_xcb_test.cpp +++ b/tests/focus_guard_xcb_test.cpp @@ -50,6 +50,12 @@ int main() { assert(!stable.arm()); FocusGuard new_capture; assert(new_capture.arm() && new_capture.valid()); + // Gamescope rewrites both properties with the same window after IME commits. + xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, active, XCB_ATOM_WINDOW, 32, 1, &a); + xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, gamescope, XCB_ATOM_CARDINAL, 32, 1, &a); + auto sync = xcb_get_input_focus(c); + std::free(xcb_get_input_focus_reply(c, sync, nullptr)); + assert(new_capture.valid()); // same-value rewrite is not a focus change xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, active, XCB_ATOM_WINDOW, 32, 1, &b); auto cookie = xcb_get_input_focus(c); Reply reply(xcb_get_input_focus_reply(c, cookie, nullptr));