From 5206933399775e5ff46baefbbb3a776c2a9bb84c Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Mon, 12 Oct 2026 10:53:22 +0300 Subject: feat: add type-safe native platform headers and portable drag support - Add include/yasl/native/{wayland,cocoa,x11}.h with type-safe handle accessors. - Unify drag-and-drop and window carry support across Wayland, X11, and Cocoa. - Preserve full ABI compatibility for capability flags. - Install native headers conditionally based on enabled backend. - Include missing tablet example and backend test suites. --- tests/x11/test_tablet_native.c | 180 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 tests/x11/test_tablet_native.c (limited to 'tests/x11/test_tablet_native.c') diff --git a/tests/x11/test_tablet_native.c b/tests/x11/test_tablet_native.c new file mode 100644 index 0000000..0ddb6d8 --- /dev/null +++ b/tests/x11/test_tablet_native.c @@ -0,0 +1,180 @@ +/* Real Xorg driver -> XI2 -> YASL queue -> the portable example -> CPU pixels. */ +#define main tablet_example_main +#include "../../examples/tablet.c" +#undef main +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "inputtest-protocol.h" + +static void send_all(int fd, const void *message, size_t n) +{ + const char *p = message; + while (n) { + ssize_t sent = write(fd, p, n); + if (sent < 0 && errno == EINTR) continue; + assert(sent > 0); + p += sent; + n -= (size_t)sent; + } +} + +static void receive_all(int fd, void *message, size_t n) +{ + char *p = message; + while (n) { + ssize_t got = read(fd, p, n); + if (got < 0 && errno == EINTR) continue; + assert(got > 0); + p += got; + n -= (size_t)got; + } +} + +static int connect_tool(const char *path) +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + assert(fd >= 0); + struct sockaddr_un address = {.sun_family = AF_UNIX}; + assert(strlen(path) < sizeof(address.sun_path)); + strcpy(address.sun_path, path); + assert(connect(fd, (struct sockaddr *)&address, sizeof(address)) == 0); + xf86ITEventClientVersion version = {{sizeof(version), XF86IT_EVENT_CLIENT_VERSION}, 1, 1}; + send_all(fd, &version, sizeof(version)); + xf86ITResponseServerVersion response; + receive_all(fd, &response, sizeof(response)); + assert(response.header.type == XF86IT_RESPONSE_SERVER_VERSION); + assert(response.header.length == sizeof(response) && response.major == 1 && response.minor >= 1); + return fd; +} + +static void pump(int fd, struct yasl_display *display, struct yasl_surface *surface, struct app *app) +{ + xf86ITEventWaitForSync sync = {{sizeof(sync), XF86IT_EVENT_WAIT_FOR_SYNC}}; + send_all(fd, &sync, sizeof(sync)); + xf86ITResponseHeader response; + receive_all(fd, &response, sizeof(response)); + assert(response.type == XF86IT_RESPONSE_SYNC_FINISHED && response.length == sizeof(response)); + /* The driver acknowledgment guarantees input processing has finished. + * Round-trip on YASL's X connection before draining its event queue. */ + XSync(yasl_display_get_native_handle(display), False); + assert(yasl_display_dispatch(display, 0) == 0); + struct yasl_input_event event; + while (yasl_surface_input_poll(surface, &event)) { + input(app, &event); + yasl_surface_schedule_redraw(surface); + } + yasl_display_flush(display); + assert(!app->failed); +} + +static uint32_t canvas_pixel(struct app *app, int x, int y) +{ + return app->canvas[(size_t)y * app->geometry.size.x + x]; +} + +static void set_eraser_property(Display *display) +{ + int count = 0; + XIDeviceInfo *devices = XIQueryDevice(display, XIAllDevices, &count); + assert(devices); + bool found = false; + for (int i = 0; i < count; i++) { + /* The name deliberately doesn't contain "eraser": exercise the + * driver's tool property, independently of the name fallback. */ + if (strcmp(devices[i].name, "YASL Test Reverse Tool")) continue; + Atom property = XInternAtom(display, "Wacom Tool Type", False); + uint32_t eraser = (uint32_t)XInternAtom(display, "ERASER", False); + XIChangeProperty(display, devices[i].deviceid, property, XA_ATOM, 32, + PropModeReplace, (unsigned char *)&eraser, 1); + found = true; + } + XIFreeDeviceInfo(devices); + assert(found); + XSync(display, False); +} + +int main(int argc, char **argv) +{ + assert(argc == 3); /* pen socket, eraser socket */ + struct app app = {.running = true, .tablet_supported = true, .backend = YASL_BACKEND_X11}; + struct yasl_display_spec ds = {.backend = YASL_BACKEND_X11}; + struct yasl_display *display = yasl_display_open(NULL, &ds); + assert(display && (yasl_display_caps(display) & YASL_CAP_TABLET)); + struct yasl_surface_spec ss = {.title = "YASL native pressure test", .size = {500, 400}}; + struct yasl_surface *surface = yasl_surface_create_toplevel(display, &ss); + assert(surface); + struct yasl_surface_cbs cbs = {.draw = draw, .configure = configure}; + yasl_surface_set_callbacks(surface, &cbs, &app); + yasl_surface_schedule_redraw(surface); + Display *x = yasl_display_get_native_handle(display); + Window window = (Window)(uintptr_t)yasl_surface_get_native_handle(surface); + XMoveWindow(x, window, 0, 0); + XSetInputFocus(x, window, RevertToParent, CurrentTime); + XSync(x, False); + assert(yasl_display_dispatch(display, 0) == 0); + yasl_display_flush(display); + set_eraser_property(x); + int pen_fd = connect_tool(argv[1]), eraser_fd = connect_tool(argv[2]); + xf86ITEventMotion motion = {.header = {sizeof(motion), XF86IT_EVENT_MOTION}, .is_absolute = 1}; + motion.valuators.mask[0] = (1 << 0) | (1 << 1) | (1 << 4); + motion.valuators.valuators[0] = 150.0 * 65535 / (DisplayWidth(x, DefaultScreen(x)) - 1); + motion.valuators.valuators[1] = 200.0 * 65535 / (DisplayHeight(x, DefaultScreen(x)) - 1); + xf86ITEventProximity proximity = {.header = {sizeof(proximity), XF86IT_EVENT_PROXIMITY}, + .is_prox_in = 1, .valuators = motion.valuators}; + send_all(pen_fd, &proximity, sizeof(proximity)); + send_all(pen_fd, &motion, sizeof(motion)); + pump(pen_fd, display, surface, &app); + assert(app.visible && !app.down && app.pointer.device == YASL_PTR_DEV_PEN); + assert(app.pointer.pos.x == 150 && app.pointer.pos.y == 200); + assert(canvas_pixel(&app, 150, 200) == PAPER); + xf86ITEventButton button = {.header = {sizeof(button), XF86IT_EVENT_BUTTON}, + .is_absolute = 1, .button = 1, .is_press = 1, .valuators = motion.valuators}; + button.valuators.valuators[4] = 250; /* Driver pressure range is 0..1000. */ + send_all(pen_fd, &button, sizeof(button)); + pump(pen_fd, display, surface, &app); + assert(app.down && app.pointer.pen.pressure == 16384); + assert(canvas_pixel(&app, 150, 200) == INK && canvas_pixel(&app, 170, 200) == PAPER); + motion.valuators.mask[0] = 1 << 4; /* No position change. */ + motion.valuators.valuators[4] = 1000; + send_all(pen_fd, &motion, sizeof(motion)); + pump(pen_fd, display, surface, &app); + assert(app.down && app.pointer.pen.pressure == 65535); + assert(canvas_pixel(&app, 170, 200) == INK); + /* Check the actual window's pixels too, after the example presents. */ + XImage *image = XGetImage(x, window, 170, 200, 1, 1, AllPlanes, ZPixmap); + assert(image && (XGetPixel(image, 0, 0) & 0xffffff) == (INK & 0xffffff)); + XDestroyImage(image); + button.is_press = 0; + button.valuators.mask[0] = 0; /* Release omits all valuators. */ + send_all(pen_fd, &button, sizeof(button)); + pump(pen_fd, display, surface, &app); + assert(!app.down && !app.pointer.pen.pressure); + + send_all(eraser_fd, &proximity, sizeof(proximity)); + button.is_press = 1; + button.valuators = proximity.valuators; + button.valuators.valuators[4] = 1000; + send_all(eraser_fd, &button, sizeof(button)); + pump(eraser_fd, display, surface, &app); + assert(app.down && app.pointer.device == YASL_PTR_DEV_ERASER && app.pointer.pen.pressure == 65535); + assert(canvas_pixel(&app, 150, 200) == PAPER && canvas_pixel(&app, 170, 200) == PAPER); + button.is_press = 0; + send_all(eraser_fd, &button, sizeof(button)); + pump(eraser_fd, display, surface, &app); + assert(!app.down && !app.pointer.pen.pressure); + close(pen_fd); + close(eraser_fd); + free(app.canvas); + yasl_surface_destroy(surface); + yasl_display_close(display); + puts("Native Xorg pen pressure, stationary pressure, release, and eraser drawing passed."); + return 0; +} -- cgit v1.2.3