Harden pagedown-repeat test: TABBED_SYNTH_INPUT mode + stall-tolerant polling

TABBED_SYNTH_INPUT=1 makes the stdin event feed the only source of
key/modifier input: host_kbd_key and host_kbd_modifiers drop real host
events (kbd != NULL) while enter/leave/repeat_info still drive the
clipboard bridge.  A stray physical key (e.g. Alt) no longer cancels a
synthesized repeat or clobbers the injected modifier state mid-test.

The repeat test now polls for base+3 tab transitions with a 5s deadline
instead of a fixed 1.3s sleep, so a present/frame stall no longer causes
spurious failures.
This commit is contained in:
Andrew Hamilton 2026-08-11 21:51:52 +10:00
parent 198c329df7
commit dc6f8f97c2
2 changed files with 32 additions and 6 deletions

View file

@ -2025,10 +2025,21 @@ static void dump_host_back(void) {
dump_seq++; dump_seq++;
} }
/* TABBED_SYNTH_INPUT=1: ignore the real host keyboard entirely and accept
input only from the stdin event feed (the test/driver channel). The host
keyboard listener stays attached (its enter/leave events still drive the
clipboard bridge), but key and modifier events from the real host are
dropped so a stray physical press can't cancel a synthesized repeat or
clobber the injected modifier state. */
static bool synth_input_only;
static void host_kbd_key(void *data, struct wl_keyboard *kbd, uint32_t serial, static void host_kbd_key(void *data, struct wl_keyboard *kbd, uint32_t serial,
uint32_t time, uint32_t key, uint32_t state) uint32_t time, uint32_t key, uint32_t state)
{ {
(void)data; (void)kbd; (void)data;
if (synth_input_only && kbd)
return;
(void)kbd;
bridge.kbd_serial = serial; bridge.kbd_serial = serial;
tabbed_log(TABBED_LOG_DEBUG, tabbed_log(TABBED_LOG_DEBUG,
"[kbd] key=%u state=%u ctrl=%d repeat_key=%u\n", key, state, "[kbd] key=%u state=%u ctrl=%d repeat_key=%u\n", key, state,
@ -2090,7 +2101,10 @@ static void host_kbd_modifiers(void *data, struct wl_keyboard *kbd, uint32_t ser
uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_depressed, uint32_t mods_latched,
uint32_t mods_locked, uint32_t group) uint32_t mods_locked, uint32_t group)
{ {
(void)data; (void)kbd; (void)serial; (void)data; (void)serial;
if (synth_input_only && kbd)
return;
(void)kbd;
host.host_mods_depressed = mods_depressed; host.host_mods_depressed = mods_depressed;
if (host.repeat_key && !(mods_depressed & (1u << 2))) if (host.repeat_key && !(mods_depressed & (1u << 2)))
kbd_repeat_cancel(); kbd_repeat_cancel();
@ -2829,6 +2843,13 @@ int main(int argc, char *argv[]) {
level without touching non-async-signal-safe functions. */ level without touching non-async-signal-safe functions. */
tabbed_log_level_init(); tabbed_log_level_init();
/* TABBED_SYNTH_INPUT: keep real host keyboard events out of the test
channel so the stdin feed is the only source of key/modifier input. */
{
const char *v = getenv("TABBED_SYNTH_INPUT");
synth_input_only = v && v[0] && v[0] != '0';
}
struct sigaction crash_sa = { struct sigaction crash_sa = {
.sa_sigaction = crash_handler, .sa_sigaction = crash_handler,
.sa_flags = SA_SIGINFO | SA_NODEFER, .sa_flags = SA_SIGINFO | SA_NODEFER,

View file

@ -253,7 +253,7 @@ def session():
pass pass
proc = subprocess.Popen( proc = subprocess.Popen(
["env", "TABBED_LOG=2", "stdbuf", "-oL", ["env", "TABBED_LOG=2", "TABBED_SYNTH_INPUT=1", "stdbuf", "-oL",
BIN, f"{WL} '{RED}'", f"{WL} '{BLUE}'"], BIN, f"{WL} '{RED}'", f"{WL} '{BLUE}'"],
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=open(LOG, "ab"), stderr=subprocess.STDOUT, stdout=open(LOG, "ab"), stderr=subprocess.STDOUT,
@ -399,12 +399,17 @@ def test_cycle_wraps(session):
def test_hold_pagedown_repeats(session): def test_hold_pagedown_repeats(session):
start = session.wait_any_active() start = session.wait_any_active()
assert start in (0, 1) assert start in (0, 1)
# Press and hold Ctrl+PageDown for ~1.3s so the compositor's own # Press and hold Ctrl+PageDown and wait for the compositor's own repeat
# repeat timer (600ms delay, then 25Hz) kicks in. # timer (600ms delay, then 25Hz) to cycle several tabs. Wait for the
# transitions instead of sleeping a fixed 1.3s: a present/frame stall in
# the compositor (e.g. a swapchain allocation) can push the repeats past
# a fixed window and cause a spurious failure.
session.mods(CTRL) session.mods(CTRL)
session.key(PAGE_DOWN, KEY_PRESSED) session.key(PAGE_DOWN, KEY_PRESSED)
base = session.active_count() base = session.active_count()
time.sleep(1.3) deadline = time.monotonic() + 5.0
while time.monotonic() < deadline and session.active_count() - base < 3:
time.sleep(0.05)
transitions = session.active_count() - base transitions = session.active_count() - base
# Clean release: let go of the key and the modifier. # Clean release: let go of the key and the modifier.
session.key(PAGE_DOWN, KEY_RELEASED) session.key(PAGE_DOWN, KEY_RELEASED)