/* Exercise native XI2 samples through the backend and public input queue. */ #include "../../src/platform/x11/window.c" static struct yasl_mouse_event next(struct x11_surface *surface) { struct yasl_input_event event; assert(yasl_surface_input_poll(&surface->base, &event)); assert(event.surface == &surface->base && event.kind == YASL_INPUT_MOUSE); return event.mouse; } struct drag_test_recorder { int over_count; int leave_count; int drop_count; struct yasl_vector2i last_pos; }; static void on_test_drag(struct yasl_surface *s, enum yasl_drag_event ev, struct yasl_vector2i pos, void *ud) { (void)s; struct drag_test_recorder *rec = ud; rec->last_pos = pos; if (ev == YASL_DRAG_OVER) rec->over_count++; else if (ev == YASL_DRAG_LEAVE) rec->leave_count++; else if (ev == YASL_DRAG_DROP) rec->drop_count++; } struct drop_test_recorder { int over_count; int leave_count; struct yasl_vector2i last_pos; }; static void on_test_drop(struct yasl_surface *s, enum yasl_drop_event ev, struct yasl_vector2i pos, void *ud) { (void)s; struct drop_test_recorder *rec = ud; rec->last_pos = pos; if (ev == YASL_DROP_OVER) rec->over_count++; else if (ev == YASL_DROP_LEAVE) rec->leave_count++; } static void on_test_drag_end(void *ud, enum yasl_drag_end how) { enum yasl_drag_end *res = ud; *res = how; } static void test_x11_gaps(struct x11_display *d, struct x11_surface *s) { /* 1. Caps */ assert(caps(&d->base) & YASL_CAP_POPUPS); assert(caps(&d->base) & YASL_CAP_DRAG); assert(caps(&d->base) & YASL_CAP_DRAG_CARRY); /* 2. Popups: spec validation, calculation, creation, reposition, destruction */ struct yasl_popup_spec invalid_specs[] = { {.size = {0, 100}}, {.size = {100, 0}}, {.size = {35000, 100}}, {.size = {100, 100}, .content_offset = {-1, 0}}, {.size = {100, 100}, .content_offset = {100, 0}, .content_size = {10, 0}}, {.size = {100, 100}, .anchor = {INT_MIN, 0, 1, 1}}, }; for (size_t i = 0; i < sizeof(invalid_specs)/sizeof(invalid_specs[0]); i++) { assert(!popup_spec_valid(s, &invalid_specs[i])); assert(!surface_create_popup(&s->base, &invalid_specs[i])); } struct yasl_popup_spec valid_spec = { .size = {100, 120}, .anchor = {30, 40, 50, 60}, .beside = false, .content_offset = {5, 10}, }; assert(popup_spec_valid(s, &valid_spec)); int x = 0, y = 0; popup_calculate_position(d, s, &valid_spec, &x, &y); assert(x == 30 - 5); assert(y == 40 + 60 - 10); valid_spec.beside = true; popup_calculate_position(d, s, &valid_spec, &x, &y); assert(x == 30 + 50 - 5); assert(y == 40 - 10); struct yasl_surface *popup_base = surface_create_popup(&s->base, &valid_spec); assert(popup_base != NULL); struct x11_surface *popup = (struct x11_surface *)popup_base; assert(popup->is_popup); assert(popup->parent == s); assert(popup->size.x == 100 && popup->size.y == 120); struct yasl_popup_spec repo_spec = { .size = {150, 180}, .anchor = {10, 20, 30, 40}, .beside = false, .content_offset = {0, 0}, }; assert(surface_popup_reposition(popup_base, &repo_spec)); assert(popup->size.x == 150 && popup->size.y == 180); surface_destroy(popup_base); assert(d->surfaces == s); /* 3. Drag-and-drop target events */ struct x11_surface s2 = {.base.display = &d->base, .window = 43}; s->next = &s2; s2.next = NULL; struct drag_test_recorder rec1 = {0}; struct drag_test_recorder rec2 = {0}; struct yasl_window_cbs cbs1 = {.drag = on_test_drag}; struct yasl_window_cbs cbs2 = {.drag = on_test_drag}; set_callbacks(&s->base, &cbs1, &rec1); set_callbacks(&s2.base, &cbs2, &rec2); enum yasl_drag_end end_reason = (enum yasl_drag_end)-1; struct yasl_drag_listener l = {.end = on_test_drag_end}; assert(drag_start(&d->base, &s->base, NULL, &l, &end_reason)); assert(d->drag.active); /* Motion on s (window 42) -> YASL_DRAG_OVER on s */ XIDeviceEvent mev = {.evtype = XI_Motion, .sourceid = 8, .event = 42, .event_x = 12.0, .event_y = 34.0}; XGenericEventCookie cookie = {.evtype = XI_Motion, .data = &mev}; xi_event(d, &cookie); assert(rec1.over_count == 1); assert(rec1.last_pos.x == 12 && rec1.last_pos.y == 34); /* Motion on s2 (window 43) -> YASL_DRAG_LEAVE on s, YASL_DRAG_OVER on s2 */ mev.event = 43; mev.event_x = 56.0; mev.event_y = 78.0; xi_event(d, &cookie); assert(rec1.leave_count == 1); assert(rec2.over_count == 1); assert(rec2.last_pos.x == 56 && rec2.last_pos.y == 78); /* ButtonRelease -> YASL_DRAG_DROP on s2 and drag end */ XIDeviceEvent rel = {.evtype = XI_ButtonRelease, .sourceid = 8, .event = 43, .detail = 1}; cookie = (XGenericEventCookie){.evtype = XI_ButtonRelease, .data = &rel}; xi_event(d, &cookie); assert(rec2.drop_count == 1); assert(end_reason == YASL_DRAG_DROPPED); assert(!d->drag.active); /* Drag cancel */ assert(drag_start(&d->base, &s->base, NULL, &l, &end_reason)); cookie = (XGenericEventCookie){.evtype = XI_Motion, .data = &mev}; mev.event = 42; xi_event(d, &cookie); assert(rec1.over_count == 2); drag_cancel(&d->base); assert(rec1.leave_count == 2); assert(end_reason == YASL_DRAG_CANCELLED); assert(!d->drag.active); /* Hierarchy change cancels drag and pushes reset */ input_queue_clear(&s->input); assert(drag_start(&d->base, &s->base, NULL, &l, &end_reason)); XIHierarchyEvent hiev = {.evtype = XI_HierarchyChanged, .flags = XISlaveRemoved}; cookie = (XGenericEventCookie){.evtype = XI_HierarchyChanged, .data = &hiev}; xi_event(d, &cookie); assert(end_reason == YASL_DRAG_CANCELLED); assert(!d->drag.active); struct yasl_input_event reset_ev; assert(yasl_surface_input_poll(&s->base, &reset_ev)); assert(reset_ev.kind == YASL_INPUT_RESET); /* 4. Drag image parameter */ uint32_t pixels[16] = {0xFF00FF00}; struct yasl_drag_image img = {.pixels = pixels, .size = {4, 4}, .hotspot = {2, 2}}; assert(drag_start(&d->base, &s->base, &img, &l, &end_reason)); assert(d->drag.active); assert(drag_carry(&s->base, (struct yasl_vector2i){10, 10})); drag_cancel(&d->base); /* 5. XDND drop-target events and URI parsing */ size_t out_len = 0; char *parsed = parse_file_uris("file:///tmp/doc.txt\r\n", 21, &out_len); assert(parsed && !strcmp(parsed, "file:///tmp/doc.txt\r\n") && out_len == 21); free(parsed); parsed = parse_file_uris("# comment\nfile:///a\nfile:///b\n", 30, &out_len); assert(parsed && !strcmp(parsed, "file:///a\r\nfile:///b\r\n") && out_len == 22); free(parsed); parsed = parse_file_uris("http://example.com\r\n", 20, &out_len); assert(parsed == NULL && out_len == 0); /* Test XDND ClientMessages */ d->protocols = 1; d->delete_window = 2; d->xdnd_enter = 10; d->xdnd_position = 11; d->xdnd_leave = 12; d->xdnd_drop = 13; d->uri_list = 20; struct drop_test_recorder drec = {0}; cbs1.drop = on_test_drop; set_callbacks(&s->base, &cbs1, &drec); XClientMessageEvent enter_msg = { .type = ClientMessage, .message_type = d->xdnd_enter, .format = 32, .data.l = {99, 5 << 24, d->uri_list, 0, 0}, }; handle_client_message(d, s, &enter_msg); assert(d->xdnd.accepts); XClientMessageEvent pos_msg = { .type = ClientMessage, .message_type = d->xdnd_position, .format = 32, .data.l = {99, 0, (100 << 16) | 200, 1000, 0}, }; handle_client_message(d, s, &pos_msg); assert(drec.over_count == 1); assert(drec.last_pos.x == 100 && drec.last_pos.y == 200); XClientMessageEvent leave_msg = { .type = ClientMessage, .message_type = d->xdnd_leave, .format = 32, .data.l = {99, 0, 0, 0, 0}, }; handle_client_message(d, s, &leave_msg); assert(drec.leave_count == 1); handle_client_message(d, s, &enter_msg); handle_client_message(d, s, &pos_msg); assert(drec.over_count == 2); XClientMessageEvent drop_msg = { .type = ClientMessage, .message_type = d->xdnd_drop, .format = 32, .data.l = {99, 0, 1005, 0, 0}, }; handle_client_message(d, s, &drop_msg); assert(drec.leave_count == 2); assert(d->pending_drop.active); /* Push YASL_INPUT_DROP_FILES */ push(s, (struct yasl_input_event){.kind = YASL_INPUT_DROP_FILES}, "file:///sample.png\r\n", 20); struct yasl_input_event drop_ev; assert(yasl_surface_input_poll(&s->base, &drop_ev)); assert(drop_ev.kind == YASL_INPUT_DROP_FILES); assert(drop_ev.drop_files.len == 20); assert(!strcmp(drop_ev.drop_files.files, "file:///sample.png\r\n")); /* Clean up s2 */ s->next = NULL; input_queue_clear(&s->input); input_queue_clear(&s2.input); } int main(void) { struct x11_display display = { .base.ops = &yasl_x11_backend, .abs_pressure = 10, .abs_tilt_x = 11, .abs_tilt_y = 12, }; struct x11_surface surface = {.base.display = &display.base, .window = 42}; display.surfaces = &surface; XIValuatorClassInfo pressure = {.type = XIValuatorClass, .number = 2, .label = 10, .min = 100, .max = 1100, .mode = XIModeAbsolute}; XIValuatorClassInfo tilt_x = {.type = XIValuatorClass, .number = 4, .label = 11, .min = -64, .max = 63, .mode = XIModeAbsolute}; XIValuatorClassInfo tilt_y = {.type = XIValuatorClass, .number = 7, .label = 12, .min = -64, .max = 63, .mode = XIModeAbsolute}; XIAnyClassInfo *classes[] = {(void *)&pressure, (void *)&tilt_x, (void *)&tilt_y}; XIDeviceInfo info = {.deviceid = 8, .name = "Tablet pen", .num_classes = 3, .classes = classes}; struct x11_pointer pointer = {0}; pointer_info(&display, &pointer, &info); assert(pointer.device == YASL_PTR_DEV_PEN); display.pointers = &pointer; /* Cache avoids requiring a real X server. */ unsigned char mask[1] = {0}; XISetMask(mask, 2); XISetMask(mask, 4); XISetMask(mask, 7); double values[] = {600, -35, 42}; XIDeviceEvent sample = {.evtype = XI_ButtonPress, .sourceid = 8, .event = 42, .detail = 1, .event_x = 200.5, .event_y = 175.25, .mods.effective = ShiftMask, .valuators = {sizeof(mask), mask, values}}; XGenericEventCookie cookie = {.evtype = XI_ButtonPress, .data = &sample}; xi_event(&display, &cookie); struct yasl_mouse_event ev = next(&surface); assert(ev.kind == YASL_PTR_PRESS && ev.button == 0 && ev.device == YASL_PTR_DEV_PEN); assert(ev.pos.x == 201 && ev.pos.y == 175 && ev.mods == YASL_MOD_SHIFT); assert(ev.pen.pressure == 32768 && ev.pen.tilt_x == -35 && ev.pen.tilt_y == 42); /* XI2 sends only changed axes, with values packed in mask order. */ mask[0] = 0; XISetMask(mask, 2); values[0] = 1100; sample.evtype = cookie.evtype = XI_Motion; xi_event(&display, &cookie); ev = next(&surface); assert(ev.kind == YASL_PTR_MOTION && ev.pen.pressure == 65535); assert(ev.pen.tilt_x == -35 && ev.pen.tilt_y == 42); values[0] = 5000; xi_event(&display, &cookie); assert(next(&surface).pen.pressure == 65535); values[0] = -50; xi_event(&display, &cookie); assert(next(&surface).pen.pressure == 0); info.name = "Tablet ERASER"; pointer_info(&display, &pointer, &info); assert(pointer.device == YASL_PTR_DEV_ERASER); values[0] = 900; sample.evtype = cookie.evtype = XI_ButtonPress; xi_event(&display, &cookie); ev = next(&surface); assert(ev.device == YASL_PTR_DEV_ERASER && ev.pen.pressure > 0); /* Release must clear pressure even when the driver omits that valuator. */ sample.valuators.mask_len = 0; sample.evtype = cookie.evtype = XI_ButtonRelease; xi_event(&display, &cookie); ev = next(&surface); assert(ev.kind == YASL_PTR_RELEASE && ev.button == 0 && ev.pen.pressure == 0); assert(ev.device == YASL_PTR_DEV_ERASER); XIEnterEvent crossing = {.sourceid = 8, .event = 42, .event_x = 200, .event_y = 175}; cookie = (XGenericEventCookie){.evtype = XI_Leave, .data = &crossing}; xi_event(&display, &cookie); ev = next(&surface); assert(ev.kind == YASL_PTR_LEAVE && ev.device == YASL_PTR_DEV_ERASER && !ev.pen.pressure); /* No tablet axes must produce an ordinary, pressure-free mouse. */ info.num_classes = 0; pointer_info(&display, &pointer, &info); sample.evtype = XI_ButtonPress; sample.detail = 3; xi_pointer_event(&surface, &sample); ev = next(&surface); assert(ev.device == YASL_PTR_DEV_MOUSE && ev.button == 2 && ev.pen.pressure == 0); for (int button = 4; button <= 7; button++) { sample.detail = button; sample.evtype = XI_ButtonPress; xi_pointer_event(&surface, &sample); ev = next(&surface); assert(ev.kind == YASL_PTR_SCROLL); assert(ev.scroll.x == (button == 6 ? -1 : button == 7 ? 1 : 0)); assert(ev.scroll.y == (button == 4 ? -1 : button == 5 ? 1 : 0)); sample.evtype = XI_ButtonRelease; xi_pointer_event(&surface, &sample); struct yasl_input_event unused; assert(!yasl_surface_input_poll(&surface.base, &unused)); } assert(pointer_button(2) == 1 && pointer_button(8) == 3); pointer.pressure = (struct tablet_axis){2, 1, 1, 50}; assert(pressure_value(&pointer.pressure) == 0); pointer.tilt_x = (struct tablet_axis){4, -90, 90, -200}; assert(tilt_value(&pointer.tilt_x) == -90); pointer.tilt_x.value = 200; assert(tilt_value(&pointer.tilt_x) == 90); display.pointers = NULL; input_queue_clear(&surface.input); test_x11_gaps(&display, &surface); return 0; }