summaryrefslogtreecommitdiff
path: root/tests/x11
diff options
context:
space:
mode:
Diffstat (limited to 'tests/x11')
-rw-r--r--tests/x11/test_input.c236
-rw-r--r--tests/x11/test_tablet_native.c10
2 files changed, 226 insertions, 20 deletions
diff --git a/tests/x11/test_input.c b/tests/x11/test_input.c
index 95943d1..c2eca37 100644
--- a/tests/x11/test_input.c
+++ b/tests/x11/test_input.c
@@ -1,5 +1,7 @@
/* An isolated real X server supplies the keymap; disabling this surface's XIC
* deliberately exercises text insertion when no input method is available. */
+#define _POSIX_C_SOURCE 200809L
+#include <locale.h>
#include "../../src/platform/x11/window.c"
static void check_selection_encoding(struct x11_display *d, struct x11_surface *s)
@@ -35,34 +37,72 @@ static void check_selection_encoding(struct x11_display *d, struct x11_surface *
XFree(data);
XDestroyWindow(d->native, requestor);
- /* Simulate a legacy owner returning STRING to the paste conversion window,
- * then an empty UTF-8 owner and malformed UTF-8. */
- const struct { Atom type; const char *bytes; size_t len; const char *expected; } cases[] = {
- {XA_STRING, "caf\xe9 \xa3", 6, "café £"},
- {d->utf8, "", 0, ""},
- {d->utf8, "\xff", 1, NULL},
+ /* Simulate legacy STRING, empty UTF-8, malformed UTF-8, and rejected INCR. */
+ const struct {
+ Atom type;
+ const void *bytes;
+ size_t len;
+ int format;
+ const char *expected;
+ enum yasl_transfer_status status;
+ } cases[] = {
+ {XA_STRING, "caf\xe9 \xa3", 6, 8, "café £", YASL_TRANSFER_OK},
+ {d->utf8, "", 0, 8, "", YASL_TRANSFER_OK},
+ {d->utf8, "\xff", 1, 8, NULL, YASL_TRANSFER_FAILED},
+ {d->incr, NULL, 1, 32, NULL, YASL_TRANSFER_FAILED},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
struct paste_request *pending = calloc(1, sizeof(*pending));
assert(pending);
+ pending->display = d;
pending->window = XCreateSimpleWindow(d->native, DefaultRootWindow(d->native),
0, 0, 1, 1, 0, 0, 0);
pending->target = s;
+ pending->id = (uint64_t)i + 1;
+ pending->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS,
+ paste_timeout, pending);
+ assert(pending->timeout);
pending->next = d->pastes;
d->pastes = pending;
- XChangeProperty(d->native, pending->window, property, cases[i].type, 8,
- PropModeReplace, (const unsigned char *)cases[i].bytes, (int)cases[i].len);
+ unsigned long incr_bytes = 4096;
+ const void *bytes = cases[i].bytes;
+ if (cases[i].type == d->incr) bytes = &incr_bytes;
+ XChangeProperty(d->native, pending->window, property, cases[i].type,
+ cases[i].format, PropModeReplace, bytes, (int)cases[i].len);
XSelectionEvent reply = {.requestor = pending->window, .property = property};
selection_notify(d, &reply);
struct yasl_event event;
- if (cases[i].expected) {
- assert(yasl_surface_poll_event(&s->base, &event));
- assert(event.type == YASL_EVENT_PASTE);
+ assert(yasl_surface_poll_event(&s->base, &event));
+ assert(event.type == YASL_EVENT_PASTE && event.paste.request_id == i + 1);
+ assert(event.paste.status == cases[i].status);
+ if (cases[i].status == YASL_TRANSFER_OK) {
assert(event.text.len == strlen(cases[i].expected));
assert(strcmp(event.text.data, cases[i].expected) == 0);
}
+ if (cases[i].type == d->incr) {
+ assert(d->pastes == pending && !pending->target);
+ assert(yasl_loop_del_timer(d->loop, pending->timeout));
+ pending->timeout = 0;
+ paste_timeout(pending, 0, PASTE_TIMEOUT_NS);
+ }
assert(!yasl_surface_poll_event(&s->base, &event));
}
+
+ struct paste_request *rejected = calloc(1, sizeof(*rejected));
+ assert(rejected);
+ rejected->window = XCreateSimpleWindow(d->native, DefaultRootWindow(d->native),
+ 0, 0, 1, 1, 0, 0, 0);
+ rejected->target = s;
+ rejected->selection = d->clipboard;
+ rejected->id = 99;
+ rejected->next = d->pastes;
+ d->pastes = rejected;
+ XSelectionEvent no_property = {.requestor = rejected->window, .property = None};
+ selection_notify(d, &no_property);
+ struct yasl_event event;
+ assert(yasl_surface_poll_event(&s->base, &event));
+ assert(event.type == YASL_EVENT_PASTE && event.paste.request_id == 99);
+ assert(event.paste.status == YASL_TRANSFER_FAILED);
}
/* Own the XSETTINGS selection with one Net/DoubleClickTime value. */
@@ -96,6 +136,173 @@ static void check_double_click(struct x11_display *d)
assert(yasl_display_double_click_ms(&d->base) == YASL_DOUBLE_CLICK_MS_DEFAULT);
}
+static struct yasl_event wait_paste(struct yasl_display *display, uint64_t id)
+{
+ struct yasl_event event;
+ for (int i = 0; i < 256 && yasl_display_poll_event(display, &event); i++)
+ if (event.type == YASL_EVENT_PASTE && event.paste.request_id == id)
+ return event;
+ assert(!"expected paste completion was not queued");
+ return (struct yasl_event){0};
+}
+
+static void check_request_completion(struct x11_display *d, struct x11_surface *s)
+{
+ assert(surface_input_queue(&s->base) == &s->input);
+ assert(copy_text(&d->base, YASL_CLIPBOARD, ""));
+ XSync(d->native, False);
+ assert(XGetSelectionOwner(d->native, d->clipboard) == d->selection_window);
+ assert(request_paste(&d->base, YASL_CLIPBOARD, &s->base, 101));
+ struct yasl_event event = wait_paste(&d->base, 101);
+ assert(event.paste.status == YASL_TRANSFER_OK && event.paste.len == 0);
+ assert(event.paste.data && !event.paste.data[0]);
+
+ XSetSelectionOwner(d->native, d->clipboard, None, CurrentTime);
+ XSync(d->native, False);
+ assert(request_paste(&d->base, YASL_CLIPBOARD, &s->base, 102));
+ XSync(d->native, False);
+ dispatch(&d->base);
+ event = wait_paste(&d->base, 102);
+ assert(event.paste.status == YASL_TRANSFER_UNAVAILABLE);
+
+ Window silent_owner = XCreateSimpleWindow(d->native, DefaultRootWindow(d->native),
+ 0, 0, 1, 1, 0, 0, 0);
+ XSetSelectionOwner(d->native, d->clipboard, silent_owner, CurrentTime);
+ XSync(d->native, False);
+ assert(request_paste(&d->base, YASL_CLIPBOARD, &s->base, 103));
+ struct paste_request *timed = d->pastes;
+ assert(timed && timed->id == 103 && timed->timeout);
+ assert(yasl_loop_del_timer(d->loop, timed->timeout));
+ timed->timeout = 0;
+ paste_timeout(timed, 0, PASTE_TIMEOUT_NS);
+ event = wait_paste(&d->base, 103);
+ assert(event.paste.status == YASL_TRANSFER_FAILED);
+
+ struct yasl_window_spec spec = {.title = "Cancelled paste", .size = {80, 60}};
+ struct yasl_surface *cancel_target = yasl_surface_create_toplevel(&d->base, &spec);
+ assert(cancel_target);
+ assert(request_paste(&d->base, YASL_CLIPBOARD, cancel_target, 104));
+ assert(d->pastes && d->pastes->target == (struct x11_surface *)cancel_target);
+ yasl_timer_id timeout = d->pastes->timeout;
+ yasl_surface_destroy(cancel_target);
+ assert(d->pastes && !d->pastes->target && d->pastes->id == 0);
+ assert(yasl_loop_del_timer(d->loop, timeout));
+ d->pastes->timeout = 0;
+ XSelectionEvent late_reply = {
+ .requestor = d->pastes->window,
+ .selection = d->pastes->selection,
+ .property = None,
+ };
+ selection_notify(d, &late_reply);
+ assert(!d->pastes);
+ XDestroyWindow(d->native, silent_owner);
+ XSetSelectionOwner(d->native, d->clipboard, None, CurrentTime);
+ XSync(d->native, False);
+}
+
+static struct yasl_event next_composition(struct yasl_display *display)
+{
+ struct yasl_event event;
+ for (int i = 0; i < 256 && yasl_display_poll_event(display, &event); i++)
+ if (event.type == YASL_EVENT_COMPOSITION)
+ return event;
+ assert(!"expected composition snapshot was not queued");
+ return (struct yasl_event){0};
+}
+
+static void drain_display_events(struct yasl_display *display)
+{
+ struct yasl_event event;
+ while (yasl_display_poll_event(display, &event)) {}
+}
+
+static void check_xim_callbacks(struct yasl_display *display, struct x11_surface *s)
+{
+ const char *old_locale = setlocale(LC_CTYPE, NULL);
+ char *saved_locale = old_locale ? strdup(old_locale) : NULL;
+ const char *utf8_locale = setlocale(LC_CTYPE, "C.UTF-8");
+ if (!utf8_locale) utf8_locale = setlocale(LC_CTYPE, "C.utf8");
+ assert(utf8_locale);
+
+ drain_display_events(display);
+ assert(xim_preedit_start(NULL, (XPointer)s, NULL) == -1);
+
+ wchar_t initial[] = {L'A', (wchar_t)0x1f600, L'B'};
+ XIMText wide = {.length = 3, .encoding_is_wchar = True,
+ .string.wide_char = initial};
+ XIMPreeditDrawCallbackStruct draw = {
+ .caret = 2, .chg_first = 0, .chg_length = 0, .text = &wide,
+ };
+ xim_preedit_draw(NULL, (XPointer)s, (XPointer)&draw);
+ assert(s->ime_preedit && strcmp(s->ime_preedit, "A😀B") == 0);
+ struct yasl_event event = next_composition(display);
+ assert(event.composition.active && event.composition.len == 6);
+ assert(event.composition.begin == 5 && event.composition.end == 5);
+
+ /* XIMText.length counts characters. The data need not have a terminator. */
+ char multibyte[] = {(char)0xc3, (char)0xa9, 'Z', 'x'};
+ XIMText mb = {.length = 2, .encoding_is_wchar = False,
+ .string.multi_byte = multibyte};
+ draw = (XIMPreeditDrawCallbackStruct){
+ .caret = 3, .chg_first = 1, .chg_length = 1, .text = &mb,
+ };
+ xim_preedit_draw(NULL, (XPointer)s, (XPointer)&draw);
+ assert(s->ime_preedit && strcmp(s->ime_preedit, "AéZB") == 0);
+ event = next_composition(display);
+ assert(event.composition.active && event.composition.begin == 4);
+ assert(event.composition.end == 4);
+
+ /* A NULL text string updates feedback/caret while preserving the snapshot. */
+ XIMText feedback = {.length = 4, .encoding_is_wchar = False,
+ .string.multi_byte = NULL};
+ draw = (XIMPreeditDrawCallbackStruct){
+ .caret = 1, .chg_first = 0, .chg_length = 4, .text = &feedback,
+ };
+ xim_preedit_draw(NULL, (XPointer)s, (XPointer)&draw);
+ assert(strcmp(s->ime_preedit, "AéZB") == 0);
+ event = next_composition(display);
+ assert(event.composition.active && event.composition.begin == 1);
+
+ char malformed[] = {(char)0xff, 'x'};
+ XIMText invalid = {.length = 1, .encoding_is_wchar = False,
+ .string.multi_byte = malformed};
+ draw.text = &invalid;
+ draw.chg_first = 0;
+ draw.chg_length = 0;
+ xim_preedit_draw(NULL, (XPointer)s, (XPointer)&draw);
+ assert(strcmp(s->ime_preedit, "AéZB") == 0);
+ struct yasl_event unused;
+ assert(!yasl_display_poll_event(display, &unused));
+
+ wchar_t empty[] = {0};
+ XIMText deletion = {.length = 0, .encoding_is_wchar = True,
+ .string.wide_char = empty};
+ draw = (XIMPreeditDrawCallbackStruct){
+ .caret = 0, .chg_first = 0, .chg_length = 4, .text = &deletion,
+ };
+ xim_preedit_draw(NULL, (XPointer)s, (XPointer)&draw);
+ event = next_composition(display);
+ assert(!event.composition.active && event.composition.len == 0);
+ assert(!s->ime_preedit);
+
+ /* Done also publishes an explicit end snapshot. */
+ wide = (XIMText){.length = 3, .encoding_is_wchar = True,
+ .string.wide_char = initial};
+ draw = (XIMPreeditDrawCallbackStruct){
+ .caret = 1, .chg_first = 0, .chg_length = 0, .text = &wide,
+ };
+ xim_preedit_draw(NULL, (XPointer)s, (XPointer)&draw);
+ (void)next_composition(display);
+ xim_preedit_done(NULL, (XPointer)s, NULL);
+ event = next_composition(display);
+ assert(!event.composition.active && !s->ime_preedit);
+
+ if (saved_locale) {
+ setlocale(LC_CTYPE, saved_locale);
+ free(saved_locale);
+ }
+}
+
int main(void)
{
struct yasl_display_spec ds = {.backend = YASL_BACKEND_X11};
@@ -126,10 +333,13 @@ int main(void)
XSync(d->native, False);
dispatch(base);
input_queue_clear(&s->input);
+ /* Native creation and keymap updates may already have queued focus or
+ * reset input on the shared display stream. Isolate this synthetic key. */
+ struct yasl_event event;
+ while (yasl_surface_poll_event(surface, &event)) {}
XKeyEvent native = {.type = KeyPress, .display = d->native,
.window = s->window, .keycode = code};
key_event(s, &native, false);
- struct yasl_event event;
assert(yasl_surface_poll_event(surface, &event));
assert(event.type == YASL_EVENT_KEY && event.key.key == YASL_KEY_A);
assert(event.key.codepoint == cases[i].label);
@@ -149,6 +359,8 @@ int main(void)
s->ic = saved_ic;
check_selection_encoding(d, s);
check_double_click(d);
+ check_request_completion(d, s);
+ check_xim_callbacks(base, s);
/* Test CSD interactive actions, window menu, and IME caret */
assert(yasl_display_caps(base) & YASL_CAP_CSD);
diff --git a/tests/x11/test_tablet_native.c b/tests/x11/test_tablet_native.c
index b0d1366..706a5cc 100644
--- a/tests/x11/test_tablet_native.c
+++ b/tests/x11/test_tablet_native.c
@@ -57,14 +57,8 @@ static int connect_tool(const char *path)
static void native_draw(struct yasl_surface *surface, struct yasl_frame *frame, void *ud)
{
- struct yasl_frame_context context = {
- .surface = surface, .size = frame->target.size,
- .scale_120 = yasl_surface_geometry(surface).scale_120,
- .cpu = {.pixels = frame->target.pixels,
- .stride_bytes = frame->target.size.x * (int)sizeof(uint32_t)},
- .internal_handle = frame,
- };
- draw(ud, surface, &context);
+ (void)surface;
+ draw(ud, surface, frame);
}
static void native_configure(struct yasl_surface *surface, struct yasl_geometry geometry, void *ud)