test: tier-2 harness coverage, bell, IME grab/popup, request round-trips, libdecor boot
This commit is contained in:
parent
3a194104a3
commit
f58418b70d
3 changed files with 177 additions and 9 deletions
|
|
@ -1535,6 +1535,31 @@ def test_standalone_headless_text_input_focus_follows_tab(tmp_path):
|
|||
_assert_alive(s)
|
||||
|
||||
|
||||
def test_standalone_headless_ime_popup_and_grab(tmp_path):
|
||||
"""An in-session IME that grabs the keyboard and opens a candidate
|
||||
popup drives the compositor's IME handlers end to end: grab, popup
|
||||
open, commit relay to the focused field, then mid-session teardown
|
||||
(the field exits after logging its commit, the IME leaves on the
|
||||
resulting deactivate, dropping popup and method while the session
|
||||
lives on)."""
|
||||
ti_out = tmp_path / "ti.out"
|
||||
im_out = tmp_path / "im.out"
|
||||
with _run_standalone(
|
||||
{},
|
||||
"%s --input-method --im-grab --im-popup --im-commit 'kana' "
|
||||
"--im-exit-on-deactivate --im-out '%s'" % (WL, im_out),
|
||||
"%s --text-input --text-input-out '%s' '#ff0000'" % (WL, ti_out),
|
||||
wait="any") as s:
|
||||
_assert_alive(s)
|
||||
s.wait_for(r"\[im\] IME grabbed keyboard", timeout=15.0)
|
||||
s.wait_for(r"\[im\] IME popup opened", timeout=15.0)
|
||||
assert _wait_out(ti_out), "field never got the IME commit string"
|
||||
assert ti_out.read_text() == "commit:kana\n", \
|
||||
f"IME -> field payload mismatch: {ti_out.read_text()!r}"
|
||||
s.wait_for(r"\[im\] input method gone", timeout=15.0)
|
||||
_assert_alive(s, "compositor died on IME teardown")
|
||||
|
||||
|
||||
def test_standalone_headless_content_single_pixel_globals(globals_session,
|
||||
tmp_path):
|
||||
"""The standalone compositor advertises wp_content_type_manager_v1 and
|
||||
|
|
@ -1596,6 +1621,16 @@ def test_standalone_headless_gamma_headless_fails_clean(tmp_path):
|
|||
_assert_no_crash(s.read_log())
|
||||
|
||||
|
||||
def test_standalone_headless_system_bell_rings():
|
||||
"""A client ringing xdg_system_bell_v1 reaches the compositor's bell
|
||||
handler, which logs the ring at info level and stays up."""
|
||||
with _run_standalone(
|
||||
{}, "%s --app-id keep '#0000ff' 320x240" % WL,
|
||||
"%s --bell" % WL, wait="any") as s:
|
||||
s.wait_for(r"\[bell\] ring", timeout=15.0)
|
||||
_assert_alive(s, "compositor died on bell ring")
|
||||
|
||||
|
||||
def test_standalone_headless_dialog_globals(globals_session, tmp_path):
|
||||
"""The standalone compositor advertises xdg_wm_dialog_v1 so clients
|
||||
can mark toplevels modal."""
|
||||
|
|
@ -1678,6 +1713,18 @@ def test_standalone_headless_xdg_decoration_client_side(tmp_path):
|
|||
assert "mode=1" in text, f"tab not pinned client-side: {text!r}"
|
||||
|
||||
|
||||
def test_nested_libdecor_frame_path(monkeypatch):
|
||||
"""A forced-libdecor nested session boots and maps through libdecor's
|
||||
host frame: the libdecor path is taken (not just compiled) and the
|
||||
tab maps under it."""
|
||||
_require_host_wayland()
|
||||
monkeypatch.setenv("KICK_LIBDECOR", "1")
|
||||
with _run_nested("%s --app-id keep '#0000ff' 320x240" % WL,
|
||||
wait="app0_mapped") as s:
|
||||
s.wait_for(r"host window: libdecor-managed decorations", timeout=15.0)
|
||||
_assert_alive(s, "libdecor session died")
|
||||
|
||||
|
||||
def test_standalone_headless_tearing_async_applied(tmp_path):
|
||||
"""A tab requesting async presentation drives the commit path: once
|
||||
its surface maps with the hint, commits log the async transition."""
|
||||
|
|
@ -2870,13 +2917,17 @@ def _nested_session(*tabs, extra_args=(), repeat_off=True):
|
|||
for _ in range(3):
|
||||
log = _fresh_log()
|
||||
dump_dir = _mk_dump_dir()
|
||||
# KICK_LIBDECOR passes through only when set: an empty value would
|
||||
# force the libdecor path (any non-"0" value does), so default runs
|
||||
# must not see the variable at all.
|
||||
cmd = ["env", "KICK_LOG=2", "KICK_SYNTH_INPUT=1",
|
||||
"KICK_DUMP_DIR=" + dump_dir,
|
||||
"KICK_REPEAT_DELAY_MS=" + (KICK_REPEAT_TEST_DELAY if repeat_off
|
||||
else KICK_REPEAT_DEFAULT)]
|
||||
if os.environ.get("KICK_LIBDECOR"):
|
||||
cmd.append("KICK_LIBDECOR=" + os.environ["KICK_LIBDECOR"])
|
||||
proc = subprocess.Popen(
|
||||
["env", "KICK_LOG=2", "KICK_SYNTH_INPUT=1",
|
||||
"KICK_DUMP_DIR=" + dump_dir,
|
||||
"KICK_REPEAT_DELAY_MS=" + (KICK_REPEAT_TEST_DELAY if repeat_off
|
||||
else KICK_REPEAT_DEFAULT),
|
||||
"stdbuf", "-oL",
|
||||
BIN, *extra_args] + list(tabs),
|
||||
cmd + ["stdbuf", "-oL", BIN, *extra_args] + list(tabs),
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
||||
preexec_fn=_preexec_death(os.getpid()),
|
||||
|
|
@ -4633,7 +4684,10 @@ def test_standalone_headless_foreign_activate(tmp_path):
|
|||
|
||||
def test_standalone_headless_foreign_maximize(tmp_path):
|
||||
"""A taskbar maximize request reaches the compositor's foreign-maximize
|
||||
handler and the window keeps maximized state on its taskbar handle."""
|
||||
handler and the window keeps maximized state on its taskbar handle.
|
||||
The handler is a no visible-op (tabs are always maximized), so the
|
||||
test waits for its debug line: without that round trip the session
|
||||
teardown wins the race and the request is never dispatched."""
|
||||
out = tmp_path / "foreign.out"
|
||||
with _run_standalone(
|
||||
{},
|
||||
|
|
@ -4646,6 +4700,7 @@ def test_standalone_headless_foreign_maximize(tmp_path):
|
|||
text = out.read_text().strip()
|
||||
assert "maximized=1" in text, \
|
||||
f"taskbar maximize not reflected: {text!r}"
|
||||
s.wait_for(r"\[foreign\] maximize request", timeout=15.0)
|
||||
_assert_alive(s, "compositor died on foreign maximize")
|
||||
|
||||
|
||||
|
|
@ -5268,6 +5323,10 @@ def test_standalone_client_maximize_request_stays_maximized(tmp_path):
|
|||
out = tmp_path / "req.out"
|
||||
with _run_standalone({}, "%s --req-out '%s' '#ff0000'" % (WL, out), wait="app0_mapped") as s:
|
||||
s.tap(M) # set_maximized request
|
||||
# Round trip on the handler itself, not just any configure line:
|
||||
# the initial map configure already logs maximized=, so waiting
|
||||
# for the file alone passes while the request is still in flight.
|
||||
s.wait_for(r"\[xdg\] maximize request", timeout=15.0)
|
||||
# Wait for the M configure roundtrip instead of a fixed sleep, so
|
||||
# the refusal below is judged only after M has definitely landed.
|
||||
assert _wait_out(out, "maximized=", timeout=10.0), \
|
||||
|
|
@ -5299,6 +5358,12 @@ def test_standalone_client_minimize_move_resize_ignored():
|
|||
s.tap_ack(N) # set_minimized request
|
||||
s.tap_ack(V) # interactive move request
|
||||
s.tap_ack(R) # interactive resize request
|
||||
# Round trip: the keys must reach the client and its requests must
|
||||
# be dispatched, not just consumed from the feed -- otherwise the
|
||||
# test passes while the handlers never run.
|
||||
s.wait_for(r"\[xdg\] minimize request ignored", timeout=15.0)
|
||||
s.wait_for(r"\[xdg\] move request ignored", timeout=15.0)
|
||||
s.wait_for(r"\[xdg\] resize request ignored", timeout=15.0)
|
||||
assert s.active() == 0, "tab lost active after ignored requests"
|
||||
_assert_alive(s, "compositor died on minimize/move/resize")
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#include "single-pixel-buffer-v1-client.h"
|
||||
#include "viewporter-client.h"
|
||||
#include "wlr-gamma-control-unstable-v1-client.h"
|
||||
#include "xdg-system-bell-v1-client.h"
|
||||
#include "xdg-dialog-v1-client.h"
|
||||
#include "pointer-gestures-unstable-v1-client.h"
|
||||
#include "tearing-control-v1-client.h"
|
||||
|
|
@ -228,6 +229,7 @@ static struct wp_single_pixel_buffer_manager_v1 *single_pixel_mgr;
|
|||
static struct wp_viewporter *viewporter;
|
||||
static struct wp_viewport *viewport;
|
||||
static struct zwlr_gamma_control_manager_v1 *gamma_mgr;
|
||||
static struct xdg_system_bell_v1 *bell_mgr;
|
||||
static struct xdg_toplevel_tag_manager_v1 *tag_mgr;
|
||||
static struct wp_color_representation_manager_v1 *color_repr_mgr;
|
||||
static void color_repr_bind_listener(void);
|
||||
|
|
@ -1063,6 +1065,9 @@ static void registry_global(void *data, struct wl_registry *reg, uint32_t name,
|
|||
gamma_mgr = wl_registry_bind(reg, name,
|
||||
&zwlr_gamma_control_manager_v1_interface,
|
||||
version < 1 ? version : 1);
|
||||
} else if (strcmp(interface, xdg_system_bell_v1_interface.name) == 0) {
|
||||
bell_mgr = wl_registry_bind(reg, name,
|
||||
&xdg_system_bell_v1_interface, version < 1 ? version : 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2663,6 +2668,30 @@ static void gamma_run(void) {
|
|||
}
|
||||
}
|
||||
|
||||
/* --- System bell (xdg-system-bell-v1) ---
|
||||
--bell: ring the compositor's bell, then exit. The smoke test waits
|
||||
for the compositor's [bell] ring line. */
|
||||
static bool bell_mode;
|
||||
|
||||
static void bell_run(void) {
|
||||
display = wl_display_connect(NULL);
|
||||
if (!display) {
|
||||
fprintf(stderr, "wltest: bell: 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 (!bell_mgr) {
|
||||
fprintf(stderr, "wltest: bell: manager missing\n");
|
||||
exit(1);
|
||||
}
|
||||
xdg_system_bell_v1_ring(bell_mgr, NULL);
|
||||
wl_display_flush(display);
|
||||
/* One round trip so a protocol error would surface before exiting. */
|
||||
wl_display_roundtrip(display);
|
||||
}
|
||||
|
||||
/* --- Output power management (zwlr_output_power_management_v1) ---
|
||||
--power <on|off>: ask the compositor to blank (off) or unblank (on) the
|
||||
single output, then exit. The smoke test pairs this with --screencopy to
|
||||
|
|
@ -3265,6 +3294,10 @@ static void ti_enable_field(void) {
|
|||
static bool input_method_mode;
|
||||
static const char *im_commit_text;
|
||||
static const char *im_out;
|
||||
static bool im_grab;
|
||||
static bool im_popup;
|
||||
static bool im_exit_on_deactivate;
|
||||
static bool im_got_activate;
|
||||
static struct zwp_input_method_v2 *im_obj;
|
||||
static bool im_done;
|
||||
static uint32_t im_serial;
|
||||
|
|
@ -3282,6 +3315,7 @@ static void im_note(const char *s) {
|
|||
static void im_activate(void *data, struct zwp_input_method_v2 *im) {
|
||||
(void)data; (void)im;
|
||||
im_note("active");
|
||||
im_got_activate = true;
|
||||
/* The commit must carry the latest done serial (which counts past done
|
||||
events), so the actual commit_string + commit goes out from the done
|
||||
handler below -- on every done, so each newly focused field gets its
|
||||
|
|
@ -3291,6 +3325,12 @@ static void im_activate(void *data, struct zwp_input_method_v2 *im) {
|
|||
static void im_deactivate(void *data, struct zwp_input_method_v2 *im) {
|
||||
(void)data; (void)im;
|
||||
im_note("inactive");
|
||||
/* Event-driven exit for the popup/grab teardown test: the focused
|
||||
field is gone (it exits after logging its commit), so leaving now
|
||||
drops the popup and the method mid-session. Gated on having been
|
||||
active so a pre-focus deactivate never exits early. */
|
||||
if (im_exit_on_deactivate && im_got_activate)
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static void im_surrounding(void *data, struct zwp_input_method_v2 *im,
|
||||
|
|
@ -3355,6 +3395,42 @@ static void input_method_run(void) {
|
|||
im_obj = zwp_input_method_manager_v2_get_input_method(input_method_mgr,
|
||||
seat);
|
||||
zwp_input_method_v2_add_listener(im_obj, &im_listener, NULL);
|
||||
if (im_grab) {
|
||||
/* Take the keyboard grab: the compositor routes grabbed keys to
|
||||
the IME (and logs it). Grab events need no listener here. */
|
||||
zwp_input_method_v2_grab_keyboard(im_obj);
|
||||
}
|
||||
if (im_popup) {
|
||||
/* Candidate popup: a bare surface with a committed shm buffer
|
||||
taking the popup role. */
|
||||
if (!compositor || !shm) {
|
||||
fprintf(stderr, "wltest: input-method: compositor/shm missing\n");
|
||||
exit(1);
|
||||
}
|
||||
struct wl_surface *psurf = wl_compositor_create_surface(compositor);
|
||||
int fd;
|
||||
struct wl_shm_pool *pool = make_pool(64 * 64 * 4, &fd);
|
||||
if (!pool) {
|
||||
fprintf(stderr, "wltest: input-method: shm pool failed\n");
|
||||
exit(1);
|
||||
}
|
||||
uint8_t *px = mmap(NULL, 64 * 64 * 4, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, fd, 0);
|
||||
if (px == MAP_FAILED) {
|
||||
fprintf(stderr, "wltest: input-method: mmap failed\n");
|
||||
exit(1);
|
||||
}
|
||||
memset(px, 0xff, 64 * 64 * 4);
|
||||
munmap(px, 64 * 64 * 4);
|
||||
close(fd);
|
||||
struct wl_buffer *pbuf = wl_shm_pool_create_buffer(pool, 0, 64, 64,
|
||||
64 * 4, WL_SHM_FORMAT_XRGB8888);
|
||||
wl_shm_pool_destroy(pool);
|
||||
wl_surface_attach(psurf, pbuf, 0, 0);
|
||||
wl_surface_damage(psurf, 0, 0, 64, 64);
|
||||
zwp_input_method_v2_get_input_popup_surface(im_obj, psurf);
|
||||
wl_surface_commit(psurf);
|
||||
}
|
||||
wl_display_roundtrip(display);
|
||||
for (;;) {
|
||||
if (wl_display_dispatch(display) < 0)
|
||||
|
|
@ -3378,11 +3454,12 @@ static void print_usage(const char *argv0) {
|
|||
"[--foreign [--foreign-out <file>] [--foreign-close <app_id>]] "
|
||||
"[--idle <ms> [--idle-out <file>] [--idle-inhibit]] "
|
||||
"[--text-input [--text-input-out <file>]] "
|
||||
"[--input-method [--im-commit <text>] [--im-out <file>]] "
|
||||
"[--input-method [--im-commit <text>] [--im-out <file>] "
|
||||
"[--im-grab] [--im-popup] [--im-exit-on-deactivate]] "
|
||||
"[--content-type <none|photo|video|game>] [--single-pixel] "
|
||||
"[--tag-set <tag> [--tag-desc <desc>]] "
|
||||
"[--color-probe [--color-out <file>]] "
|
||||
"[--power <on|off>] [--gamma [--gamma-out <file>]] "
|
||||
"[--power <on|off>] [--gamma [--gamma-out <file>]] [--bell] "
|
||||
"[--dialog-modal] [--extra-modal] [--extra-delay <ms>] "
|
||||
"[--tearing <vsync|async>] [--fractional-out <file>] "
|
||||
"[--icon <#RRGGBB> [SIZE]] "
|
||||
|
|
@ -3902,6 +3979,9 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
gamma_out = argv[i + 1];
|
||||
i += 2;
|
||||
} else if (strcmp(argv[i], "--bell") == 0) {
|
||||
bell_mode = true;
|
||||
i += 1;
|
||||
} else if (strcmp(argv[i], "--virtual-key") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
fprintf(stderr, "wltest: --virtual-key needs code:state,...\n");
|
||||
|
|
@ -4004,6 +4084,15 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
im_out = argv[i + 1];
|
||||
i += 2;
|
||||
} else if (strcmp(argv[i], "--im-grab") == 0) {
|
||||
im_grab = true;
|
||||
i += 1;
|
||||
} else if (strcmp(argv[i], "--im-popup") == 0) {
|
||||
im_popup = true;
|
||||
i += 1;
|
||||
} else if (strcmp(argv[i], "--im-exit-on-deactivate") == 0) {
|
||||
im_exit_on_deactivate = true;
|
||||
i += 1;
|
||||
} else if (strcmp(argv[i], "--content-type") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
fprintf(stderr, "wltest: --content-type needs a value "
|
||||
|
|
@ -4093,6 +4182,12 @@ int main(int argc, char **argv) {
|
|||
gamma_run();
|
||||
return 0;
|
||||
}
|
||||
/* Bell mode: no color/size argument. Ring the compositor's bell,
|
||||
then exit. */
|
||||
if (bell_mode) {
|
||||
bell_run();
|
||||
return 0;
|
||||
}
|
||||
/* Output-management mode: no color/size argument. List the head
|
||||
and optionally apply a custom size, then exit. */
|
||||
if (output_mgmt_mode) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue