mirror of
https://github.com/baketnk/frame-yap.git
synced 2026-10-06 00:00:05 +02:00
Gamescope rewrites GAMESCOPE_FOCUSED_WINDOW (same window) after an IME commit. The focus guard treated any PropertyNotify on the root focus properties as a focus change, so every paced delivery stopped after its first chunk. Re-read the property and fail only when it names a different window; real X focus moves still fail via FocusOut, and every check still re-validates both properties and X input focus. Diagnosed from delivery-debug.log on Frame: sent offset=0 bytes=24 focus invalid: GAMESCOPE_FOCUSED_WINDOW changed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
75 lines
3.5 KiB
C++
75 lines
3.5 KiB
C++
#include "focus_guard.hpp"
|
|
#include <xcb/xcb.h>
|
|
#include <cassert>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <memory>
|
|
|
|
using namespace frameyap;
|
|
namespace {
|
|
struct Free { void operator()(void* p) const { std::free(p); } };
|
|
template<class T> using Reply = std::unique_ptr<T, Free>;
|
|
xcb_atom_t atom(xcb_connection_t* c, const char* name) {
|
|
auto cookie = xcb_intern_atom(c, 0, std::strlen(name), name);
|
|
Reply<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(c, cookie, nullptr));
|
|
assert(reply && reply->atom);
|
|
return reply->atom;
|
|
}
|
|
void focus(xcb_connection_t* c, xcb_window_t root, xcb_window_t window,
|
|
xcb_atom_t active, xcb_atom_t gamescope) {
|
|
xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, active, XCB_ATOM_WINDOW, 32, 1, &window);
|
|
xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, gamescope, XCB_ATOM_CARDINAL, 32, 1, &window);
|
|
xcb_set_input_focus(c, XCB_INPUT_FOCUS_POINTER_ROOT, window, XCB_CURRENT_TIME);
|
|
auto cookie = xcb_get_input_focus(c);
|
|
Reply<xcb_get_input_focus_reply_t> reply(xcb_get_input_focus_reply(c, cookie, nullptr));
|
|
assert(reply && reply->focus == window);
|
|
}
|
|
}
|
|
int main() {
|
|
int index = 0;
|
|
xcb_connection_t* c = xcb_connect(nullptr, &index);
|
|
assert(c && !xcb_connection_has_error(c));
|
|
auto screens = xcb_setup_roots_iterator(xcb_get_setup(c));
|
|
for (int i = 0; i < index; ++i) xcb_screen_next(&screens);
|
|
assert(screens.rem);
|
|
const auto root = screens.data->root;
|
|
const auto active = atom(c, "_NET_ACTIVE_WINDOW"), gamescope = atom(c, "GAMESCOPE_FOCUSED_WINDOW");
|
|
const auto a = xcb_generate_id(c), b = xcb_generate_id(c);
|
|
xcb_create_window(c, XCB_COPY_FROM_PARENT, a, root, 0, 0, 160, 100, 0,
|
|
XCB_WINDOW_CLASS_INPUT_OUTPUT, screens.data->root_visual, 0, nullptr);
|
|
xcb_create_window(c, XCB_COPY_FROM_PARENT, b, root, 180, 0, 160, 100, 0,
|
|
XCB_WINDOW_CLASS_INPUT_OUTPUT, screens.data->root_visual, 0, nullptr);
|
|
xcb_map_window(c, a); xcb_map_window(c, b);
|
|
focus(c, root, a, active, gamescope);
|
|
FocusGuard stable;
|
|
assert(stable.arm() && stable.valid());
|
|
focus(c, root, b, active, gamescope);
|
|
focus(c, root, a, active, gamescope);
|
|
assert(!stable.valid()); // loss/regain of the same ID cannot authorize delivery
|
|
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<xcb_get_input_focus_reply_t> reply(xcb_get_input_focus_reply(c, cookie, nullptr));
|
|
assert(reply);
|
|
assert(!new_capture.valid()); // compositor/X disagreement fails closed
|
|
focus(c, root, a, active, gamescope);
|
|
FocusGuard destroyed;
|
|
assert(destroyed.arm());
|
|
xcb_destroy_window(c, a);
|
|
cookie = xcb_get_input_focus(c);
|
|
reply.reset(xcb_get_input_focus_reply(c, cookie, nullptr));
|
|
assert(reply);
|
|
assert(!destroyed.valid());
|
|
xcb_disconnect(c);
|
|
std::cout << "focus guard XCB transitions passed\n";
|
|
}
|