summaryrefslogtreecommitdiff
path: root/tests/wayland
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-12 22:12:02 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-12 22:12:02 +0300
commit94c5e361fbb229b0cd6853e2102a7d95f1f358b3 (patch)
tree3c0f11af9dcb84dac8067a31a6979acd6dd56fbf /tests/wayland
parent08dc9047098d6d96791024ac7d8f5837633673ef (diff)
better public interface and doxygen
Diffstat (limited to 'tests/wayland')
-rw-r--r--tests/wayland/test_clipboard.c47
-rw-r--r--tests/wayland/test_keyboard.c128
-rw-r--r--tests/wayland/test_tablet.c37
3 files changed, 212 insertions, 0 deletions
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 <assert.h>
+
+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 <assert.h>
+
+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[] = "<dead_acute> <e> : \"é\" 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;
}