shortcuts-inhibit: honor zwp_keyboard_shortcuts_inhibit_v1
Let a focused fullscreen client (game/VM) grab the compositor's keyboard shortcuts. New inhibitors for the focused surface activate immediately; a lazy sync at grab-chord time (de)activates across focus changes so all focus paths are covered without extra listeners. While honored, the tab/window grab chords are swallowed in process_key_event (quit and VT switch stay live so a stuck client can't trap the session). Add a --inhibit wltest mode (window + inhibitor, reports active) and two smoke tests asserting the global is advertised and the inhibitor becomes active on the focused surface.
This commit is contained in:
parent
c64a10e8d9
commit
45df9d4818
4 changed files with 196 additions and 1 deletions
|
|
@ -3388,3 +3388,49 @@ def test_standalone_headless_tearing_and_fractional_globals(tmp_path):
|
|||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
def test_standalone_headless_shortcuts_inhibit_globals(tmp_path):
|
||||
"""The compositor advertises zwp_keyboard_shortcuts_inhibit_manager_v1
|
||||
so a fullscreen game/VM can grab the compositor's shortcuts."""
|
||||
out = tmp_path / "globals.txt"
|
||||
s = _spawn_standalone({}, "%s --globals-out '%s' '#112233'" % (WL, out))
|
||||
try:
|
||||
deadline = time.monotonic() + 15.0
|
||||
while time.monotonic() < deadline:
|
||||
if out.exists():
|
||||
break
|
||||
time.sleep(0.1)
|
||||
assert out.exists(), "wltest --globals-out never wrote its file"
|
||||
names = {line.split()[0] for line in out.read_text().splitlines()}
|
||||
assert "zwp_keyboard_shortcuts_inhibit_manager_v1" in names, \
|
||||
f"missing inhibit manager; advertised: {sorted(names)}"
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
def test_standalone_headless_shortcuts_inhibit_active(tmp_path):
|
||||
"""A client that inhibits shortcuts on its focused window is honored:
|
||||
the compositor replies with the active event (it only activates while
|
||||
the inhibitor's surface has keyboard focus)."""
|
||||
out = tmp_path / "inhibit.out"
|
||||
s = _spawn_standalone(
|
||||
{}, "%s --inhibit --inhibit-out '%s'" % (WL, out))
|
||||
try:
|
||||
s.wait_any_active()
|
||||
deadline = time.monotonic() + 10.0
|
||||
got_active = False
|
||||
while time.monotonic() < deadline:
|
||||
if out.exists():
|
||||
text = out.read_text().strip()
|
||||
if "active" in text:
|
||||
got_active = True
|
||||
break
|
||||
time.sleep(0.1)
|
||||
assert got_active, \
|
||||
f"inhibitor never became active: " \
|
||||
f"{(out.read_text().strip() if out.exists() else '(missing)')!r}"
|
||||
assert s.proc.poll() is None, \
|
||||
f"compositor exited with inhibitor; log tail:\n{s.read_log()[-400:]}"
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include "idle-inhibit-unstable-v1-client.h"
|
||||
#include "ext-session-lock-v1-client.h"
|
||||
#include "wlr-screencopy-unstable-v1-client.h"
|
||||
#include "keyboard-shortcuts-inhibit-unstable-v1-client.h"
|
||||
|
||||
/* Minimal deterministic xdg toplevel test client: shows a solid-color shm
|
||||
buffer and runs until killed. Used by test.sh to give the smoke test two
|
||||
|
|
@ -118,6 +119,7 @@ static struct ext_idle_notifier_v1 *idle_mgr;
|
|||
static struct zwp_idle_inhibit_manager_v1 *inhib_mgr;
|
||||
static struct ext_session_lock_manager_v1 *lock_mgr;
|
||||
static struct zwlr_screencopy_manager_v1 *sc_mgr;
|
||||
static struct zwp_keyboard_shortcuts_inhibit_manager_v1 *kb_inhibit_mgr;
|
||||
static struct wl_output *wl_output;
|
||||
|
||||
struct foreign_state {
|
||||
|
|
@ -728,6 +730,11 @@ static void registry_global(void *data, struct wl_registry *reg, uint32_t name,
|
|||
sc_mgr = wl_registry_bind(reg, name,
|
||||
&zwlr_screencopy_manager_v1_interface,
|
||||
version < 3 ? version : 3);
|
||||
} else if (strcmp(interface,
|
||||
zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name) == 0) {
|
||||
kb_inhibit_mgr = wl_registry_bind(reg, name,
|
||||
&zwp_keyboard_shortcuts_inhibit_manager_v1_interface,
|
||||
version < 1 ? version : 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1323,6 +1330,71 @@ static void screencopy_run(void) {
|
|||
}
|
||||
}
|
||||
|
||||
/* --- Keyboard shortcuts inhibit (zwp_keyboard_shortcuts_inhibit_v1) ---
|
||||
--inhibit [--inhibit-out <file>]: act as a fullscreen game that grabs the
|
||||
compositor's shortcuts. Creates a normal window (so it takes keyboard
|
||||
focus), then inhibits shortcuts on its surface+seat. Writes "active" to
|
||||
--inhibit-out when the compositor honors it (proving kick activates the
|
||||
inhibitor for the focused surface), "inactive" if focus moves away. */
|
||||
static bool inhibit_mode;
|
||||
static const char *inhibit_out;
|
||||
static struct zwp_keyboard_shortcuts_inhibitor_v1 *kb_inhibitor;
|
||||
|
||||
static void kb_inhibit_note(const char *s) {
|
||||
if (inhibit_out) write_outfile(inhibit_out, s, strlen(s));
|
||||
}
|
||||
|
||||
static void kb_inhibitor_active(void *data,
|
||||
struct zwp_keyboard_shortcuts_inhibitor_v1 *inhib) {
|
||||
(void)data; (void)inhib;
|
||||
kb_inhibit_note("active");
|
||||
}
|
||||
|
||||
static void kb_inhibitor_inactive(void *data,
|
||||
struct zwp_keyboard_shortcuts_inhibitor_v1 *inhib) {
|
||||
(void)data; (void)inhib;
|
||||
kb_inhibit_note("inactive");
|
||||
}
|
||||
|
||||
static const struct zwp_keyboard_shortcuts_inhibitor_v1_listener kb_inhibitor_listener = {
|
||||
.active = kb_inhibitor_active,
|
||||
.inactive = kb_inhibitor_inactive,
|
||||
};
|
||||
|
||||
static void inhibit_run(void) {
|
||||
display = wl_display_connect(NULL);
|
||||
if (!display) {
|
||||
fprintf(stderr, "wltest: inhibit: cannot connect\n");
|
||||
exit(1);
|
||||
}
|
||||
struct wl_registry *reg = wl_display_get_registry(display);
|
||||
wl_registry_add_listener(reg, ®istry_listener, NULL);
|
||||
wl_display_roundtrip(display);
|
||||
if (!kb_inhibit_mgr || !seat) {
|
||||
fprintf(stderr, "wltest: inhibit: manager or seat not advertised\n");
|
||||
exit(1);
|
||||
}
|
||||
/* Need a mapped window to hold keyboard focus: draw a small buffer so
|
||||
the compositor focuses it, then inhibit on its surface+seat. */
|
||||
surface = wl_compositor_create_surface(compositor);
|
||||
xdg_surface = xdg_wm_base_get_xdg_surface(wm_base, surface);
|
||||
toplevel = xdg_surface_get_toplevel(xdg_surface);
|
||||
xdg_toplevel_set_title(toplevel, "wltest-inhibit");
|
||||
xdg_wm_base_add_listener(wm_base, &wm_base_listener, NULL);
|
||||
xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
|
||||
xdg_toplevel_add_listener(toplevel, &toplevel_listener, NULL);
|
||||
wl_surface_commit(surface);
|
||||
wl_display_roundtrip(display);
|
||||
kb_inhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(
|
||||
kb_inhibit_mgr, surface, seat);
|
||||
zwp_keyboard_shortcuts_inhibitor_v1_add_listener(kb_inhibitor,
|
||||
&kb_inhibitor_listener, NULL);
|
||||
for (;;) {
|
||||
if (wl_display_dispatch(display) < 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Pull the current offer and act on it. In --primary-demand mode write the
|
||||
payload to the file and exit; in --primary-demand-expect mode keep waiting
|
||||
until the payload matches the expected text (the host may already hold its
|
||||
|
|
@ -1618,10 +1690,26 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
screencopy_out = argv[i + 1];
|
||||
i += 2;
|
||||
} else if (strcmp(argv[i], "--inhibit") == 0) {
|
||||
inhibit_mode = true;
|
||||
i += 1;
|
||||
} else if (strcmp(argv[i], "--inhibit-out") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
fprintf(stderr, "wltest: --inhibit-out needs a path\n");
|
||||
return 1;
|
||||
}
|
||||
inhibit_out = argv[i + 1];
|
||||
i += 2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Shortcuts-inhibit mode: act as a game window that grabs the
|
||||
compositor's shortcuts. Creates its own focused window. */
|
||||
if (inhibit_mode) {
|
||||
inhibit_run();
|
||||
return 0;
|
||||
}
|
||||
/* Screen-capture mode: no color/size argument. Capture the compositor's
|
||||
rendered output into a shm buffer and report the frame geometry. */
|
||||
if (screencopy_mode) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue