From 94c5e361fbb229b0cd6853e2102a7d95f1f358b3 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Mon, 12 Oct 2026 22:12:02 +0300 Subject: better public interface and doxygen --- tests/wayland/test_clipboard.c | 47 +++++++++++++++ tests/wayland/test_keyboard.c | 128 +++++++++++++++++++++++++++++++++++++++++ tests/wayland/test_tablet.c | 37 ++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 tests/wayland/test_clipboard.c create mode 100644 tests/wayland/test_keyboard.c (limited to 'tests/wayland') diff --git a/tests/wayland/test_clipboard.c b/tests/wayland/test_clipboard.c new file mode 100644 index 0000000..553e84d --- /dev/null +++ b/tests/wayland/test_clipboard.c @@ -0,0 +1,47 @@ +#include "../../src/wayland/clipboard.c" +#include + +struct received { int count; size_t len; char text[32]; }; + +static void receive(const char *text, size_t len, bool uri_list, + struct wl_surface *surface, void *ud) +{ + (void)surface; + struct received *r = ud; + assert(!uri_list && len < sizeof(r->text)); + memcpy(r->text, text, len); + r->text[len] = '\0'; + r->len = len; + r->count++; +} + +int main(void) +{ + struct received received = {0}; + struct clipboard c = {.loop = yasl_loop_create(), .receive = receive, .ud = &received}; + assert(c.loop); + const struct { const char *bytes; size_t len; bool valid; } cases[] = { + {"é字😀", sizeof("é字😀") - 1, true}, + {"", 0, true}, + {"\xff", 1, false}, + {"\xf0\x9f\x98", 3, false}, /* Incomplete non-BMP character. */ + }; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + received = (struct received){0}; + int fds[2]; + assert(pipe(fds) == 0); + assert(start_transfer(&c, fds[0], false, false)); + assert(write(fds[1], cases[i].bytes, cases[i].len) == (ssize_t)cases[i].len); + close(fds[1]); + for (int steps = 0; c.transfers && steps < 10; steps++) + assert(yasl_loop_step(c.loop, 0) >= 0); + assert(!c.transfers); + assert(received.count == (int)cases[i].valid); + if (cases[i].valid) { + assert(received.len == cases[i].len); + assert(memcmp(received.text, cases[i].bytes, received.len) == 0); + } + } + yasl_loop_destroy(c.loop); + return 0; +} diff --git a/tests/wayland/test_keyboard.c b/tests/wayland/test_keyboard.c new file mode 100644 index 0000000..dbe0a94 --- /dev/null +++ b/tests/wayland/test_keyboard.c @@ -0,0 +1,128 @@ +#include "../../src/wayland/keyboard.c" +#include + +static struct yasl_input_event next(struct wayland_surface *w, enum yasl_input_kind kind) +{ + struct yasl_input_event ev; + assert(yasl_surface_input_poll(&w->base, &ev)); + assert(ev.kind == kind && ev.surface == &w->base); + return ev; +} + +static void empty(struct wayland_surface *w) +{ + struct yasl_input_event ev; + assert(!yasl_surface_input_poll(&w->base, &ev)); +} + +static void layout(struct seat *s, const char *name, const char *variant) +{ + keyboard_text_reset(s); + if (s->xkb_state) xkb_state_unref(s->xkb_state); + if (s->xkb_keymap) xkb_keymap_unref(s->xkb_keymap); + struct xkb_rule_names names = {.layout = name, .variant = variant}; + s->xkb_keymap = xkb_keymap_new_from_names(s->conn->xkb_ctx, &names, 0); + assert(s->xkb_keymap); + s->xkb_state = xkb_state_new(s->xkb_keymap); + assert(s->xkb_state); + s->input_mods_ready = false; + s->mod_alt = xkb_keymap_mod_get_index(s->xkb_keymap, XKB_MOD_NAME_ALT); +} + +static void modifiers(struct seat *s, const char *name) +{ + xkb_mod_mask_t mask = name ? 1u << xkb_keymap_mod_get_index(s->xkb_keymap, name) : 0; + xkb_state_update_mask(s->xkb_state, mask, 0, 0, 0, 0, 0); +} + +static void key(struct seat *s, uint32_t code, bool down) +{ + keyboard_listener.key(s, NULL, 1, 0, code, down ? + WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED); +} + +int main(void) +{ + struct wayland_display d = {.base.ops = &yasl_wayland_backend}; + d.xkb_ctx = xkb_context_new(0); + assert(d.xkb_ctx); + struct wayland_surface w = {.base.display = &d.base, .conn = &d}; + wayland_surface_set_callbacks(&w, NULL, NULL); + struct seat s = {.conn = &d, .keyboard_focus = &w, .repeat_fd = -1, + .compose_attempted = true}; + const char compose[] = " : \"é\" U00E9\n"; + struct xkb_compose_table *table = xkb_compose_table_new_from_buffer(d.xkb_ctx, + compose, sizeof(compose) - 1, "C.UTF-8", XKB_COMPOSE_FORMAT_TEXT_V1, 0); + assert(table); + s.compose = xkb_compose_state_new(table, 0); + assert(s.compose); + xkb_compose_table_unref(table); + layout(&s, "us", NULL); + key(&s, KEY_A, true); + struct yasl_input_event ev = next(&w, YASL_INPUT_KEY); + assert(ev.key.key == YASL_KEY_A && ev.key.native_code == KEY_A); + assert(ev.key.codepoint == 'a' && ev.key.shifted == 'A' && !ev.key.mods); + assert(strcmp(next(&w, YASL_INPUT_TEXT).text.text, "a") == 0); + modifiers(&s, XKB_MOD_NAME_SHIFT); + key(&s, KEY_A, false); /* Modifier changes cannot change physical identity. */ + ev = next(&w, YASL_INPUT_KEY); + assert(ev.key.key == YASL_KEY_A && ev.key.action == YASL_KEY_RELEASE); + assert(ev.key.mods == YASL_MOD_SHIFT); + empty(&w); + key(&s, KEY_A, true); + next(&w, YASL_INPUT_KEY); + assert(strcmp(next(&w, YASL_INPUT_TEXT).text.text, "A") == 0); + modifiers(&s, XKB_MOD_NAME_CAPS); + key(&s, KEY_A, true); + assert(next(&w, YASL_INPUT_KEY).key.mods == YASL_MOD_CAPS); + assert(strcmp(next(&w, YASL_INPUT_TEXT).text.text, "A") == 0); + const char *shortcuts[] = {XKB_MOD_NAME_CTRL, XKB_MOD_NAME_ALT, XKB_MOD_NAME_LOGO}; + for (size_t i = 0; i < sizeof(shortcuts)/sizeof(shortcuts[0]); i++) { + modifiers(&s, shortcuts[i]); + key(&s, KEY_A, true); + next(&w, YASL_INPUT_KEY); + empty(&w); /* Shortcut characters never leak into committed text. */ + } + layout(&s, "fr", NULL); + key(&s, KEY_A, true); + ev = next(&w, YASL_INPUT_KEY); + assert(ev.key.key == YASL_KEY_A && ev.key.codepoint == 'q'); + assert(strcmp(next(&w, YASL_INPUT_TEXT).text.text, "q") == 0); + layout(&s, "de", NULL); + modifiers(&s, "Mod5"); /* AltGr is text input, not an Alt shortcut. */ + key(&s, KEY_E, true); + assert(next(&w, YASL_INPUT_KEY).key.key == YASL_KEY_E); + assert(strcmp(next(&w, YASL_INPUT_TEXT).text.text, "€") == 0); + layout(&s, "us", "intl"); + key(&s, KEY_APOSTROPHE, true); + next(&w, YASL_INPUT_KEY); + empty(&w); + key(&s, KEY_E, true); + next(&w, YASL_INPUT_KEY); + assert(strcmp(next(&w, YASL_INPUT_TEXT).text.text, "é") == 0); + assert(keyboard_dispatch_key(&s, &w, KEY_E, YASL_KEY_REPEAT)); + assert(next(&w, YASL_INPUT_KEY).key.action == YASL_KEY_REPEAT); + assert(strcmp(next(&w, YASL_INPUT_TEXT).text.text, "e") == 0); + key(&s, KEY_APOSTROPHE, true); + next(&w, YASL_INPUT_KEY); + keyboard_reset_keys(&s, &w); + key(&s, KEY_E, true); + next(&w, YASL_INPUT_KEY); + assert(strcmp(next(&w, YASL_INPUT_TEXT).text.text, "e") == 0); + key(&s, KEY_APOSTROPHE, true); + next(&w, YASL_INPUT_KEY); + s.ime_focus = &w; + text_input_listener.commit_string(&s, NULL, "字"); + text_input_listener.done(&s, NULL, 0); + assert(strcmp(next(&w, YASL_INPUT_TEXT).text.text, "字") == 0); + key(&s, KEY_E, true); + next(&w, YASL_INPUT_KEY); + assert(strcmp(next(&w, YASL_INPUT_TEXT).text.text, "e") == 0); + empty(&w); + xkb_compose_state_unref(s.compose); + xkb_state_unref(s.xkb_state); + xkb_keymap_unref(s.xkb_keymap); + xkb_context_unref(d.xkb_ctx); + input_queue_clear(&w.input); + return 0; +} diff --git a/tests/wayland/test_tablet.c b/tests/wayland/test_tablet.c index 449c446..f8982b0 100644 --- a/tests/wayland/test_tablet.c +++ b/tests/wayland/test_tablet.c @@ -50,6 +50,7 @@ int main(void) tool_frame(&tool, NULL, 0); ev = next(&surface); assert(ev.kind == YASL_PTR_SCROLL && ev.device == YASL_PTR_DEV_ERASER); + assert(ev.scroll.x == 0 && ev.scroll.y == 1); tablet_tool_proximity_out(&tool, NULL); ev = next(&surface); assert(ev.kind == YASL_PTR_LEAVE && ev.device == YASL_PTR_DEV_ERASER && !ev.pen.pressure); @@ -57,6 +58,42 @@ int main(void) assert(!(yasl_display_caps(&display.base) & YASL_CAP_TABLET)); display.tablet_mgr = (void *)&display; /* Capability depends on protocol availability. */ assert(yasl_display_caps(&display.base) & YASL_CAP_TABLET); + + seat.pointer_focus = &surface; + /* A compositor may describe the same detent in three ways. value120 wins, + * and both axes belong to one event, without whole-line truncation. */ + pointer_axis(&seat, NULL, 0, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_int(10)); + pointer_axis_discrete(&seat, NULL, WL_POINTER_AXIS_VERTICAL_SCROLL, 1); + pointer_axis_value120(&seat, NULL, WL_POINTER_AXIS_VERTICAL_SCROLL, 30); + pointer_axis_value120(&seat, NULL, WL_POINTER_AXIS_HORIZONTAL_SCROLL, -60); + pointer_frame(&seat, NULL); + ev = next(&surface); + assert(ev.kind == YASL_PTR_SCROLL && ev.device == YASL_PTR_DEV_MOUSE); + assert(ev.scroll.x == -0.5 && ev.scroll.y == 0.25); + struct yasl_input_event unused; + assert(!yasl_surface_input_poll(&surface.base, &unused)); + pointer_frame(&seat, NULL); + assert(!yasl_surface_input_poll(&surface.base, &unused)); + + /* Stops preserve offsets from the same frame. Repeated tiny movements + * must be visible immediately rather than held until a whole detent. */ + for (int i = 0; i < 3; i++) { + pointer_axis_source(&seat, NULL, WL_POINTER_AXIS_SOURCE_FINGER); + pointer_axis(&seat, NULL, 0, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_double(1.25)); + pointer_axis(&seat, NULL, 0, WL_POINTER_AXIS_HORIZONTAL_SCROLL, wl_fixed_from_double(-2.5)); + pointer_axis_stop(&seat, NULL, 0, WL_POINTER_AXIS_VERTICAL_SCROLL); + pointer_frame(&seat, NULL); + ev = next(&surface); + assert(ev.scroll.x == -0.25 && ev.scroll.y == 0.125); + } + pointer_axis_discrete(&seat, NULL, WL_POINTER_AXIS_HORIZONTAL_SCROLL, 2); + pointer_frame(&seat, NULL); + ev = next(&surface); + assert(ev.scroll.x == 2 && ev.scroll.y == 0); + pointer_axis_value120(&seat, NULL, WL_POINTER_AXIS_VERTICAL_SCROLL, 60); + seat_pointer_reset(&seat); + pointer_frame(&seat, NULL); + assert(!yasl_surface_input_poll(&surface.base, &unused)); input_queue_clear(&surface.input); return 0; } -- cgit v1.2.3