summaryrefslogtreecommitdiff
path: root/tests/x11/test_tablet_native.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/x11/test_tablet_native.c')
-rw-r--r--tests/x11/test_tablet_native.c180
1 files changed, 180 insertions, 0 deletions
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 <yasl/native.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/XInput2.h>
+#include <assert.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#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;
+}