summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/cocoa/test_cursor.m53
-rw-r--r--tests/cocoa/test_ownership.m4
-rw-r--r--tests/cocoa/test_tablet.m123
-rw-r--r--tests/common/test_backends.c18
-rw-r--r--tests/common/test_tablet_example.c53
-rw-r--r--tests/helpers/run_x11_tablet.py124
-rw-r--r--tests/meson.build16
-rw-r--r--tests/test_api.cpp19
-rw-r--r--tests/wayland/test_cursor.c118
-rw-r--r--tests/wayland/test_tablet.c62
-rw-r--r--tests/x11/inputtest-protocol.h92
-rw-r--r--tests/x11/test_tablet.c103
-rw-r--r--tests/x11/test_tablet_native.c180
13 files changed, 962 insertions, 3 deletions
diff --git a/tests/cocoa/test_cursor.m b/tests/cocoa/test_cursor.m
new file mode 100644
index 0000000..0f3ba06
--- /dev/null
+++ b/tests/cocoa/test_cursor.m
@@ -0,0 +1,53 @@
+#import <AppKit/AppKit.h>
+#include <yasl/native.h>
+#include <assert.h>
+
+static void assert_transparent(NSCursor *cursor)
+{
+ assert(cursor);
+ NSBitmapImageRep *bitmap = [NSBitmapImageRep imageRepWithData:cursor.image.TIFFRepresentation];
+ assert(bitmap && bitmap.hasAlpha);
+ for (NSInteger y = 0; y < bitmap.pixelsHigh; y++)
+ for (NSInteger x = 0; x < bitmap.pixelsWide; x++)
+ assert([bitmap colorAtX:x y:y].alphaComponent == 0);
+}
+
+int main(void)
+{
+ @autoreleasepool {
+ struct yasl_display *display = yasl_display_open(NULL, NULL);
+ assert(display);
+ struct yasl_surface_spec spec = {.title = "Cursor test", .size = {320, 240}};
+ struct yasl_surface *surface = yasl_surface_create_toplevel(display, &spec);
+ assert(surface);
+ NSWindow *window = (NSWindow *)yasl_surface_get_native_handle(surface);
+ NSView *view = window.contentView;
+ yasl_surface_set_cursor(surface, YASL_CURSOR_HIDDEN);
+ [view cursorUpdate:nil];
+ NSCursor *hidden = [NSCursor currentCursor];
+ assert_transparent(hidden);
+
+ NSEvent *motion = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
+ location:NSMakePoint(100, 120) modifierFlags:0 timestamp:0
+ windowNumber:window.windowNumber context:nil eventNumber:0 clickCount:0 pressure:0];
+ [view mouseMoved:motion];
+ [view cursorUpdate:nil];
+ assert([NSCursor currentCursor] == hidden);
+ struct yasl_input_event event;
+ bool moved = false;
+ while (yasl_surface_input_poll(surface, &event))
+ if (event.kind == YASL_INPUT_MOUSE && event.mouse.kind == YASL_PTR_MOTION)
+ moved = true;
+ assert(moved);
+
+ [[NSCursor arrowCursor] set]; /* Simulate leaving and re-entering. */
+ [view cursorUpdate:nil];
+ assert([NSCursor currentCursor] == hidden);
+ yasl_surface_set_cursor(surface, YASL_CURSOR_ARROW);
+ [view cursorUpdate:nil];
+ assert([NSCursor currentCursor] == [NSCursor arrowCursor]);
+ yasl_surface_destroy(surface);
+ yasl_display_close(display);
+ }
+ return 0;
+}
diff --git a/tests/cocoa/test_ownership.m b/tests/cocoa/test_ownership.m
index 168a47b..2b2f52d 100644
--- a/tests/cocoa/test_ownership.m
+++ b/tests/cocoa/test_ownership.m
@@ -1,4 +1,5 @@
#include "yasl/native.h"
+#include "yasl/native/cocoa.h"
#import <AppKit/AppKit.h>
#include "yasl/yasl.h"
#include <assert.h>
@@ -13,7 +14,10 @@ int main(void)
struct yasl_surface *s = yasl_surface_create_toplevel(d, &spec);
assert(s);
assert(yasl_display_get_native_handle(d) == (void *)NSApp);
+ assert(yasl_cocoa_display_get_application(d) == NSApp);
NSWindow *window = (NSWindow *)yasl_surface_get_native_handle(s);
+ assert(yasl_cocoa_surface_get_window(s) == window);
+ assert(yasl_cocoa_surface_get_view(s) == window.contentView);
assert([window isKindOfClass:[NSWindow class]] && window.contentView);
struct yasl_input_event event;
while (yasl_surface_input_poll(s, &event)) {}
diff --git a/tests/cocoa/test_tablet.m b/tests/cocoa/test_tablet.m
new file mode 100644
index 0000000..f1489a1
--- /dev/null
+++ b/tests/cocoa/test_tablet.m
@@ -0,0 +1,123 @@
+#import <AppKit/AppKit.h>
+#include <yasl/native.h>
+#include <assert.h>
+
+/* A strict event double catches querying tablet-only properties on ordinary
+ * mouse events, and proximity-only properties on tablet point events. */
+@interface TabletSample : NSObject
+@property NSEventType type;
+@property NSEventSubtype subtype;
+@property NSEventModifierFlags modifierFlags;
+@property NSPoint locationInWindow;
+@property NSInteger buttonNumber;
+@property NSUInteger buttonMask;
+@property(getter=isEnteringProximity) BOOL enteringProximity;
+@property NSPointingDeviceType tool;
+@property float samplePressure;
+@property NSPoint sampleTilt;
+@end
+
+@implementation TabletSample
+- (float)pressure
+{
+ assert(self.type == NSEventTypeTabletPoint || self.subtype == NSEventSubtypeTabletPoint);
+ return self.samplePressure;
+}
+- (NSPoint)tilt
+{
+ assert(self.type == NSEventTypeTabletPoint || self.subtype == NSEventSubtypeTabletPoint);
+ return self.sampleTilt;
+}
+- (NSPointingDeviceType)pointingDeviceType
+{
+ assert(self.type == NSEventTypeTabletProximity || self.subtype == NSEventSubtypeTabletProximity);
+ return self.tool;
+}
+@end
+
+static struct yasl_mouse_event next(struct yasl_surface *surface)
+{
+ struct yasl_input_event event;
+ while (yasl_surface_input_poll(surface, &event))
+ if (event.kind == YASL_INPUT_MOUSE) return event.mouse;
+ assert(!"Expected a pointer event");
+ return (struct yasl_mouse_event){0};
+}
+
+int main(void)
+{
+ @autoreleasepool {
+ struct yasl_display *display = yasl_display_open(NULL, NULL);
+ assert(display && (yasl_display_caps(display) & YASL_CAP_TABLET));
+ struct yasl_surface_spec spec = {.title = "Tablet test", .size = {320, 240}};
+ struct yasl_surface *surface = yasl_surface_create_toplevel(display, &spec);
+ assert(surface);
+ NSWindow *window = (NSWindow *)yasl_surface_get_native_handle(surface);
+ NSView *view = window.contentView;
+ struct yasl_input_event event;
+ while (yasl_surface_input_poll(surface, &event)) {}
+ TabletSample *sample = [TabletSample new];
+ NSEvent *native = (NSEvent *)sample;
+ sample.type = NSEventTypeTabletProximity;
+ sample.enteringProximity = YES;
+ sample.tool = NSPointingDeviceTypePen;
+ [view tabletProximity:native];
+ sample.type = NSEventTypeMouseMoved;
+ sample.subtype = NSEventSubtypeTabletPoint;
+ sample.locationInWindow = NSMakePoint(100, 120);
+ sample.sampleTilt = NSMakePoint(-0.5, 0.5);
+ [view mouseMoved:native];
+ struct yasl_mouse_event ev = next(surface);
+ assert(ev.device == YASL_PTR_DEV_PEN && ev.kind == YASL_PTR_MOTION && !ev.pen.pressure);
+ assert(ev.pen.tilt_x == -45 && ev.pen.tilt_y == -45);
+ sample.type = NSEventTypeLeftMouseDown;
+ sample.samplePressure = 0.5;
+ [view mouseDown:native];
+ ev = next(surface);
+ assert(ev.device == YASL_PTR_DEV_PEN && ev.kind == YASL_PTR_PRESS && ev.button == 0);
+ assert(ev.pen.pressure == 32767);
+ struct yasl_vector2i position = ev.pos;
+ /* Native tablet events cover stationary pressure changes. */
+ sample.type = NSEventTypeTabletPoint;
+ sample.subtype = NSEventSubtypeMouseEvent;
+ sample.buttonMask = NSEventButtonMaskPenTip;
+ sample.samplePressure = 1.5;
+ [view tabletPoint:native];
+ ev = next(surface);
+ assert(ev.kind == YASL_PTR_MOTION && ev.pen.pressure == 65535);
+ assert(ev.pos.x == position.x && ev.pos.y == position.y);
+ sample.samplePressure = 0; /* Zero does not release a held tip button. */
+ [view tabletPoint:native];
+ assert(next(surface).kind == YASL_PTR_MOTION);
+ sample.buttonMask = 0;
+ [view tabletPoint:native];
+ ev = next(surface);
+ assert(ev.kind == YASL_PTR_RELEASE && !ev.pen.pressure && ev.device == YASL_PTR_DEV_PEN);
+ sample.type = NSEventTypeTabletProximity;
+ sample.enteringProximity = NO;
+ [view tabletProximity:native];
+ assert(next(surface).kind == YASL_PTR_LEAVE);
+ sample.enteringProximity = YES;
+ sample.tool = NSPointingDeviceTypeEraser;
+ [view tabletProximity:native];
+ sample.type = NSEventTypeTabletPoint;
+ sample.samplePressure = 0.75;
+ sample.buttonMask = NSEventButtonMaskPenTip;
+ [view tabletPoint:native];
+ ev = next(surface);
+ assert(ev.device == YASL_PTR_DEV_ERASER && ev.kind == YASL_PTR_PRESS && ev.pen.pressure > 0);
+ sample.type = NSEventTypeTabletProximity;
+ sample.enteringProximity = NO;
+ [view tabletProximity:native];
+ ev = next(surface);
+ assert(ev.device == YASL_PTR_DEV_ERASER && ev.kind == YASL_PTR_LEAVE && !ev.pen.pressure);
+ sample.type = NSEventTypeMouseMoved;
+ [view mouseMoved:native];
+ ev = next(surface);
+ assert(ev.device == YASL_PTR_DEV_MOUSE && !ev.pen.pressure);
+ [sample release];
+ yasl_surface_destroy(surface);
+ yasl_display_close(display);
+ }
+ return 0;
+}
diff --git a/tests/common/test_backends.c b/tests/common/test_backends.c
index 605dac0..4c99646 100644
--- a/tests/common/test_backends.c
+++ b/tests/common/test_backends.c
@@ -1,5 +1,11 @@
#define _POSIX_C_SOURCE 200809L
#include "yasl/native.h"
+#if YASL_HAS_WAYLAND
+#include "yasl/native/wayland.h"
+#endif
+#if YASL_HAS_X11
+#include "yasl/native/x11.h"
+#endif
#include "yasl/loop.h"
#include "yasl/yasl.h"
@@ -145,6 +151,8 @@ static void x11_behavior(struct yasl_loop *loop, struct yasl_display *d, struct
Display *native = yasl_display_get_native_handle(d);
Window window = (Window)(uintptr_t)yasl_surface_get_native_handle(w->surface);
assert(native && window);
+ assert(yasl_x11_display_get_display(d) == native);
+ assert(yasl_x11_surface_get_window(w->surface) == (uintptr_t)window);
XSync(native, False);
XImage *image = XGetImage(native, window, 0, 0, 1, 1, AllPlanes, ZPixmap);
assert(image && (XGetPixel(image, 0, 0) & 0xffffff) == 0x4080c0);
@@ -282,6 +290,16 @@ int main(int argc, char **argv)
struct yasl_display *b = yasl_display_open(loop, &spec);
assert(a && b);
struct window wa = create(a), wb = create(b);
+#if YASL_HAS_WAYLAND
+ assert(yasl_wayland_display_get_wl_display(a) != NULL);
+ assert(yasl_wayland_surface_get_wl_surface(wa.surface) != NULL);
+ assert(!yasl_x11_display_get_display(a));
+#endif
+#if YASL_HAS_X11
+ assert(yasl_x11_display_get_display(b) != NULL);
+ assert(yasl_x11_surface_get_window(wb.surface) != 0);
+ assert(!yasl_wayland_display_get_wl_display(b));
+#endif
attach(&wa);
attach(&wb);
pump(loop, a, b);
diff --git a/tests/common/test_tablet_example.c b/tests/common/test_tablet_example.c
new file mode 100644
index 0000000..1658174
--- /dev/null
+++ b/tests/common/test_tablet_example.c
@@ -0,0 +1,53 @@
+#define main tablet_example_main
+#include "../../examples/tablet.c"
+#undef main
+#include <assert.h>
+
+static void pointer(struct app *app, enum yasl_pointer_kind kind,
+ enum yasl_pointer_device device, int button, int x, int y, uint16_t pressure)
+{
+ struct yasl_input_event event = {.kind = YASL_INPUT_MOUSE, .mouse = {
+ .kind = kind, .device = device, .button = button, .pos = {x, y}, .pen.pressure = pressure}};
+ input(app, &event);
+}
+
+static uint32_t pixel(const struct app *app, int x, int y)
+{
+ return app->canvas[(size_t)y * app->geometry.size.x + x];
+}
+
+int main(void)
+{
+ struct app app = {0};
+ assert(resize_canvas(&app, (struct yasl_geometry){{400, 300}, 120}));
+ pointer(&app, YASL_PTR_MOTION, YASL_PTR_DEV_PEN, 3, 80, 160, 65535);
+ assert(pixel(&app, 80, 160) == PAPER); /* Hover never paints. */
+ pointer(&app, YASL_PTR_PRESS, YASL_PTR_DEV_PEN, 2, 80, 160, 65535);
+ assert(!app.down && pixel(&app, 80, 160) == PAPER); /* Side button isn't the tip. */
+ pointer(&app, YASL_PTR_PRESS, YASL_PTR_DEV_PEN, 0, 80, 160, 0);
+ assert(app.down && pixel(&app, 80, 160) == INK && pixel(&app, 90, 160) == PAPER);
+ pointer(&app, YASL_PTR_MOTION, YASL_PTR_DEV_PEN, 0, 80, 160, 65535);
+ assert(pixel(&app, 100, 160) == INK); /* Stationary pressure widens brush. */
+ pointer(&app, YASL_PTR_MOTION, YASL_PTR_DEV_PEN, 0, 180, 160, 20000);
+ assert(pixel(&app, 130, 160) == INK); /* Segments are continuous. */
+ pointer(&app, YASL_PTR_RELEASE, YASL_PTR_DEV_PEN, 0, 180, 160, 0);
+ pointer(&app, YASL_PTR_MOTION, YASL_PTR_DEV_PEN, 3, 250, 160, 65535);
+ assert(!app.down && pixel(&app, 250, 160) == PAPER);
+ pointer(&app, YASL_PTR_PRESS, YASL_PTR_DEV_ERASER, 0, 80, 160, 65535);
+ assert(pixel(&app, 80, 160) == PAPER && pixel(&app, 130, 160) == INK);
+ struct yasl_input_event reset = {.kind = YASL_INPUT_RESET};
+ input(&app, &reset);
+ pointer(&app, YASL_PTR_MOTION, YASL_PTR_DEV_ERASER, 0, 130, 160, 65535);
+ assert(!app.down && pixel(&app, 130, 160) == INK);
+ pointer(&app, YASL_PTR_PRESS, YASL_PTR_DEV_MOUSE, 0, 300, 200, 0);
+ assert(pixel(&app, 300, 200) == INK);
+ struct yasl_input_event blur = {.kind = YASL_INPUT_FOCUS, .focus.focused = false};
+ input(&app, &blur);
+ assert(!app.down);
+ assert(resize_canvas(&app, (struct yasl_geometry){{800, 600}, 240}));
+ assert(pixel(&app, 600, 400) == INK && !app.previous); /* DPI preserves logical drawing. */
+ clear_canvas(&app);
+ assert(pixel(&app, 600, 400) == PAPER);
+ free(app.canvas);
+ return 0;
+}
diff --git a/tests/helpers/run_x11_tablet.py b/tests/helpers/run_x11_tablet.py
new file mode 100644
index 0000000..cdd36ab
--- /dev/null
+++ b/tests/helpers/run_x11_tablet.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+"""Run pen pressure through Xorg's virtual input driver and the example app."""
+
+import os
+from pathlib import Path
+import subprocess
+import sys
+import tempfile
+import time
+
+
+def main():
+ xorg, test = sys.argv[1:]
+ module_path = os.environ.get("YASL_XORG_MODULE_PATH")
+ if not module_path:
+ module_path = subprocess.check_output(
+ [xorg, "-showDefaultModulePath"], text=True, timeout=5).strip()
+ if any(c in module_path for c in ('"', "\n", "\r")):
+ raise ValueError("Invalid Xorg module path")
+ paths = [Path(path) for path in module_path.split(",")]
+ for module in ("drivers/dummy_drv.so", "input/inputtest_drv.so"):
+ if not any((path / module).is_file() for path in paths):
+ raise RuntimeError(f"Missing Xorg module: {module}")
+ with tempfile.TemporaryDirectory(prefix="yasl-tablet-") as directory:
+ root = Path(directory)
+ pen, eraser = root / "pen.sock", root / "eraser.sock"
+ config = root / "xorg.conf"
+ config.write_text(f'''
+Section "ServerFlags"
+ Option "AutoAddDevices" "false"
+ Option "AutoAddGPU" "false"
+ Option "DontVTSwitch" "true"
+EndSection
+Section "Files"
+ ModulePath "{module_path}"
+EndSection
+Section "Module"
+ Disable "glx"
+EndSection
+Section "Device"
+ Identifier "virtual-video"
+ Driver "dummy"
+ VideoRam 16384
+EndSection
+Section "Monitor"
+ Identifier "virtual-monitor"
+ HorizSync 5.0 - 1000.0
+ VertRefresh 5.0 - 200.0
+ Modeline "1200x900" 96.96 1200 1280 1408 1616 900 901 904 932
+EndSection
+Section "Screen"
+ Identifier "virtual-screen"
+ Device "virtual-video"
+ Monitor "virtual-monitor"
+ DefaultDepth 24
+ SubSection "Display"
+ Depth 24
+ Modes "1200x900"
+ EndSubSection
+EndSection
+Section "InputDevice"
+ Identifier "YASL Test Pen"
+ Driver "inputtest"
+ Option "DeviceType" "PointerAbsoluteProximity"
+ Option "PointerHasPressure" "true"
+ Option "SocketPath" "{pen}"
+EndSection
+Section "InputDevice"
+ Identifier "YASL Test Reverse Tool"
+ Driver "inputtest"
+ Option "DeviceType" "PointerAbsoluteProximity"
+ Option "PointerHasPressure" "true"
+ Option "SocketPath" "{eraser}"
+EndSection
+Section "InputDevice"
+ Identifier "YASL Test Keyboard"
+ Driver "inputtest"
+ Option "DeviceType" "Keyboard"
+ Option "SocketPath" "{root / 'keyboard.sock'}"
+EndSection
+Section "ServerLayout"
+ Identifier "virtual-layout"
+ Screen "virtual-screen"
+ InputDevice "YASL Test Pen" "CorePointer"
+ InputDevice "YASL Test Reverse Tool" "SendCoreEvents"
+ InputDevice "YASL Test Keyboard" "CoreKeyboard"
+EndSection
+''')
+ display_file = root / "display"
+ with display_file.open("w") as display_fd, (root / "server.log").open("w+") as log:
+ server = subprocess.Popen([
+ xorg, "-config", str(config), "-logfile", str(root / "xorg.log"),
+ "-displayfd", str(display_fd.fileno()), "-noreset", "-nolisten", "tcp",
+ "-novtswitch", "-sharevts", "-seat", f"yasl-tablet-test-{os.getpid()}",
+ ], pass_fds=(display_fd.fileno(),), stdout=log, stderr=log)
+ try:
+ deadline = time.monotonic() + 10
+ while not (display_file.read_text().strip() and pen.is_socket() and eraser.is_socket()):
+ if server.poll() is not None:
+ raise RuntimeError("The virtual X server stopped")
+ if time.monotonic() >= deadline:
+ raise TimeoutError("The virtual X server did not start")
+ time.sleep(0.02)
+ env = os.environ.copy()
+ env["DISPLAY"] = ":" + display_file.read_text().strip()
+ env.pop("XAUTHORITY", None)
+ subprocess.run([test, str(pen), str(eraser)], env=env, check=True, timeout=10)
+ except BaseException:
+ log.flush()
+ log.seek(0, 2)
+ log.seek(max(0, log.tell() - 8000))
+ print(log.read(), file=sys.stderr)
+ raise
+ finally:
+ server.terminate()
+ try:
+ server.wait(timeout=5)
+ except subprocess.TimeoutExpired:
+ server.kill()
+ server.wait()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/meson.build b/tests/meson.build
index 177e400..75ff00d 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -45,11 +45,21 @@ if system == 'linux'
export_args += ['--allow-export=__start_asan_globals',
'--allow-export=__stop_asan_globals']
endif
+ exported_headers = files(
+ '../include/yasl/yasl.h',
+ '../include/yasl/timing.h',
+ '../include/yasl/loop.h',
+ '../include/yasl/native.h',
+ )
+ if has_wayland
+ exported_headers += files('../include/yasl/native/wayland.h')
+ endif
+ if has_x11
+ exported_headers += files('../include/yasl/native/x11.h')
+ endif
test('public-exports', python,
args: [files('helpers/check_exports.py')] + export_args +
- [nm, yasl_lib.get_shared_lib(),
- files('../include/yasl/yasl.h', '../include/yasl/timing.h',
- '../include/yasl/loop.h', '../include/yasl/native.h')],
+ [nm, yasl_lib.get_shared_lib(), exported_headers],
)
endif
diff --git a/tests/test_api.cpp b/tests/test_api.cpp
index 70c4ff7..9fb8525 100644
--- a/tests/test_api.cpp
+++ b/tests/test_api.cpp
@@ -1,4 +1,10 @@
#include "yasl/native.h"
+#if YASL_HAS_WAYLAND
+#include "yasl/native/wayland.h"
+#endif
+#if YASL_HAS_X11
+#include "yasl/native/x11.h"
+#endif
#include "yasl/loop.h"
#include <yasl/timing.h>
#include <yasl/yasl.h>
@@ -23,5 +29,18 @@ int main()
yasl_input_event event{};
assert(!yasl_surface_input_poll(nullptr, &event));
+ assert(!yasl_display_drag_start(nullptr, nullptr, nullptr, nullptr, nullptr));
+ assert(!yasl_surface_drag_carry(nullptr, {0, 0}));
+
+#if YASL_HAS_WAYLAND
+ assert(!yasl_wayland_display_get_wl_display(nullptr));
+ assert(!yasl_wayland_surface_get_wl_surface(nullptr));
+#endif
+
+#if YASL_HAS_X11
+ assert(!yasl_x11_display_get_display(nullptr));
+ assert(!yasl_x11_surface_get_window(nullptr));
+#endif
+
return 0;
}
diff --git a/tests/wayland/test_cursor.c b/tests/wayland/test_cursor.c
new file mode 100644
index 0000000..e43bff4
--- /dev/null
+++ b/tests/wayland/test_cursor.c
@@ -0,0 +1,118 @@
+#include "wayland/internal.h"
+#include <assert.h>
+
+/* Record cursor requests without a compositor or tablet hardware. */
+static int pointer_hides, tool_hides, pointer_shapes, tool_shapes;
+static uint32_t pointer_serial, tool_serial, pointer_shape, tool_shape;
+static struct wp_cursor_shape_device_v1 *pointer_device;
+
+static void pointer_cursor(struct wl_pointer *pointer, uint32_t serial,
+ struct wl_surface *surface, int32_t x, int32_t y)
+{
+ assert(pointer && !surface && !x && !y);
+ pointer_hides++;
+ pointer_serial = serial;
+}
+
+static void tool_cursor(struct zwp_tablet_tool_v2 *tool, uint32_t serial,
+ struct wl_surface *surface, int32_t x, int32_t y)
+{
+ assert(tool && !surface && !x && !y);
+ tool_hides++;
+ tool_serial = serial;
+}
+
+static void cursor_shape(struct wp_cursor_shape_device_v1 *device,
+ uint32_t serial, uint32_t shape)
+{
+ if (device == pointer_device) {
+ pointer_shapes++;
+ pointer_serial = serial;
+ pointer_shape = shape;
+ } else {
+ assert(device);
+ tool_shapes++;
+ tool_serial = serial;
+ tool_shape = shape;
+ }
+}
+
+static uint32_t pointer_version(struct wl_pointer *pointer)
+{
+ assert(pointer);
+ return WL_POINTER_FRAME_SINCE_VERSION;
+}
+
+#define wl_pointer_set_cursor pointer_cursor
+#define zwp_tablet_tool_v2_set_cursor tool_cursor
+#define wp_cursor_shape_device_v1_set_shape cursor_shape
+#define wl_pointer_get_version pointer_version
+#include "../../src/wayland/pointer.c"
+
+int main(void)
+{
+ struct wayland_display display = {.base.ops = &yasl_wayland_backend};
+ struct wayland_surface surface = {.base.display = &display.base, .conn = &display,
+ .scale_factor = 1, .cursor = YASL_CURSOR_TEXT};
+ surface.surface = (struct wl_surface *)&surface;
+ display.surfaces = &surface;
+ struct seat seat = {.conn = &display};
+ seat.pointer = (struct wl_pointer *)&seat;
+ display.seats = &seat;
+ struct tablet_tool tool = {.seat = &seat};
+ tool.proxy = (struct zwp_tablet_tool_v2 *)&tool;
+
+ /* The example selects hidden before either input device has entered. */
+ yasl_surface_set_cursor(&surface.base, YASL_CURSOR_HIDDEN);
+ assert(!pointer_hides && !tool_hides);
+ pointer_enter(&seat, seat.pointer, 10, surface.surface, 0, 0);
+ assert(pointer_hides == 1 && pointer_serial == 10);
+ tablet_tool_proximity_in(&tool, tool.proxy, 11, NULL, surface.surface);
+ assert(tool_hides == 1 && tool_serial == 11);
+
+ /* Hiding also works with no cursor-shape protocol, and preserves input. */
+ pointer_motion(&seat, seat.pointer, 0, wl_fixed_from_int(12), wl_fixed_from_int(34));
+ pointer_frame(&seat, seat.pointer);
+ tablet_tool_motion(&tool, tool.proxy, wl_fixed_from_int(56), wl_fixed_from_int(78));
+ tool_frame(&tool, tool.proxy, 0);
+ struct yasl_input_event event;
+ assert(yasl_surface_input_poll(&surface.base, &event));
+ assert(event.kind == YASL_INPUT_MOUSE && event.mouse.device == YASL_PTR_DEV_MOUSE);
+ assert(event.mouse.pos.x == 12 && event.mouse.pos.y == 34);
+ assert(yasl_surface_input_poll(&surface.base, &event));
+ assert(event.kind == YASL_INPUT_MOUSE && event.mouse.device == YASL_PTR_DEV_PEN);
+ assert(event.mouse.pos.x == 56 && event.mouse.pos.y == 78);
+ assert(!yasl_surface_input_poll(&surface.base, &event));
+
+ /* Each new entry needs a request with its own serial. */
+ pointer_leave(&seat, seat.pointer, 12, surface.surface);
+ pointer_frame(&seat, seat.pointer);
+ tablet_tool_proximity_out(&tool, tool.proxy);
+ pointer_enter(&seat, seat.pointer, 13, surface.surface, 0, 0);
+ tool.eraser = true;
+ tablet_tool_proximity_in(&tool, tool.proxy, 14, NULL, surface.surface);
+ assert(pointer_hides == 2 && pointer_serial == 13);
+ assert(tool_hides == 2 && tool_serial == 14);
+
+ /* Restore a visible shape on both currently focused devices. */
+ pointer_device = seat.cursor_shape_dev = (struct wp_cursor_shape_device_v1 *)&seat;
+ tool.shape_dev = (struct wp_cursor_shape_device_v1 *)&tool;
+ yasl_surface_set_cursor(&surface.base, YASL_CURSOR_ARROW);
+ assert(pointer_shapes == 1 && tool_shapes == 1);
+ assert(pointer_shape == WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
+ assert(tool_shape == WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
+ yasl_surface_set_cursor(&surface.base, YASL_CURSOR_HIDDEN);
+ assert(pointer_hides == 3 && tool_hides == 3);
+ assert(pointer_shapes == 1 && tool_shapes == 1);
+
+ /* Updating an unfocused surface must not alter the active cursor. */
+ struct wayland_surface other = {.base.display = &display.base, .conn = &display};
+ yasl_surface_set_cursor(&other.base, YASL_CURSOR_CROSSHAIR);
+ assert(pointer_shapes == 1 && tool_shapes == 1);
+ yasl_surface_set_cursor(&surface.base, (enum yasl_cursor)-1);
+ yasl_surface_set_cursor(&surface.base, (enum yasl_cursor)(YASL_CURSOR_HIDDEN + 1));
+ assert(pointer_hides == 3 && tool_hides == 3);
+ assert(pointer_shapes == 1 && tool_shapes == 1);
+ input_queue_clear(&surface.input);
+ return 0;
+}
diff --git a/tests/wayland/test_tablet.c b/tests/wayland/test_tablet.c
new file mode 100644
index 0000000..449c446
--- /dev/null
+++ b/tests/wayland/test_tablet.c
@@ -0,0 +1,62 @@
+#include "../../src/wayland/pointer.c"
+#include <assert.h>
+
+static struct yasl_mouse_event next(struct wayland_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;
+}
+
+int main(void)
+{
+ struct wayland_display display = {.base.ops = &yasl_wayland_backend};
+ struct wayland_surface surface = {.base.display = &display.base, .conn = &display,
+ .scale_factor = 1.5};
+ struct seat seat = {.conn = &display, .tablet_focus = &surface};
+ struct tablet_tool tool = {.seat = &seat};
+ seat.tool = &tool;
+ tablet_tool_motion(&tool, NULL, wl_fixed_from_int(100), wl_fixed_from_int(120));
+ tool_pressure(&tool, NULL, 32768);
+ tool_tilt(&tool, NULL, wl_fixed_from_int(-35), wl_fixed_from_int(42));
+ tablet_tool_down(&tool, NULL, 1);
+ tool_frame(&tool, NULL, 0);
+ struct yasl_mouse_event ev = next(&surface);
+ assert(ev.kind == YASL_PTR_MOTION && ev.device == YASL_PTR_DEV_PEN);
+ assert(ev.pos.x == 150 && ev.pos.y == 180 && ev.pen.pressure == 32768);
+ assert(ev.pen.tilt_x == -35 && ev.pen.tilt_y == 42);
+ ev = next(&surface);
+ assert(ev.kind == YASL_PTR_PRESS && ev.button == 0 && ev.pen.pressure == 32768);
+
+ /* Stationary pressure and tilt changes are still motion samples. */
+ tool_pressure(&tool, NULL, 100000);
+ tool_tilt(&tool, NULL, wl_fixed_from_int(-120), wl_fixed_from_int(120));
+ tool_frame(&tool, NULL, 0);
+ ev = next(&surface);
+ assert(ev.kind == YASL_PTR_MOTION && ev.pen.pressure == 65535);
+ assert(ev.pos.x == 150 && ev.pos.y == 180);
+ assert(ev.pen.tilt_x == -90 && ev.pen.tilt_y == 90);
+ tablet_tool_up(&tool, NULL);
+ tool_frame(&tool, NULL, 0);
+ ev = next(&surface);
+ assert(ev.kind == YASL_PTR_RELEASE && ev.button == 0 && !ev.pen.pressure);
+
+ tool_type(&tool, NULL, ZWP_TABLET_TOOL_V2_TYPE_ERASER);
+ tool_pressure(&tool, NULL, 22000);
+ tool_frame(&tool, NULL, 0);
+ assert(next(&surface).device == YASL_PTR_DEV_ERASER);
+ tool_wheel(&tool, NULL, 0, 1);
+ tool_frame(&tool, NULL, 0);
+ ev = next(&surface);
+ assert(ev.kind == YASL_PTR_SCROLL && ev.device == YASL_PTR_DEV_ERASER);
+ tablet_tool_proximity_out(&tool, NULL);
+ ev = next(&surface);
+ assert(ev.kind == YASL_PTR_LEAVE && ev.device == YASL_PTR_DEV_ERASER && !ev.pen.pressure);
+ assert(!seat.tool && !seat.tablet_focus && !seat.tool_buttons);
+ 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);
+ input_queue_clear(&surface.input);
+ return 0;
+}
diff --git a/tests/x11/inputtest-protocol.h b/tests/x11/inputtest-protocol.h
new file mode 100644
index 0000000..18f62ba
--- /dev/null
+++ b/tests/x11/inputtest-protocol.h
@@ -0,0 +1,92 @@
+/* Subset of Xorg's xf86-input-inputtest protocol 1.1 used by this test.
+ * https://www.x.org/releases/individual/xserver/xorg-server-21.1.25.tar.xz
+ *
+ * Copyright © 2020 Povilas Kanapickas <povilas@radix.lt>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission. Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef YASL_TEST_INPUTTEST_PROTOCOL_H
+#define YASL_TEST_INPUTTEST_PROTOCOL_H
+
+#include <stdint.h>
+
+enum xf86ITResponseType {
+ XF86IT_RESPONSE_SERVER_VERSION,
+ XF86IT_RESPONSE_SYNC_FINISHED,
+};
+
+typedef struct {
+ uint32_t length;
+ enum xf86ITResponseType type;
+} xf86ITResponseHeader;
+
+typedef struct {
+ xf86ITResponseHeader header;
+ uint16_t major, minor;
+} xf86ITResponseServerVersion;
+
+enum xf86ITEventType {
+ XF86IT_EVENT_CLIENT_VERSION,
+ XF86IT_EVENT_WAIT_FOR_SYNC,
+ XF86IT_EVENT_MOTION,
+ XF86IT_EVENT_PROXIMITY,
+ XF86IT_EVENT_BUTTON,
+};
+
+typedef struct {
+ uint32_t length;
+ enum xf86ITEventType type;
+} xf86ITEventHeader;
+
+typedef struct {
+ uint32_t has_unaccelerated;
+ uint8_t mask[8];
+ double valuators[64];
+ double unaccelerated[64];
+} xf86ITValuatorData;
+
+typedef struct {
+ xf86ITEventHeader header;
+ uint16_t major, minor;
+} xf86ITEventClientVersion;
+
+typedef struct { xf86ITEventHeader header; } xf86ITEventWaitForSync;
+
+typedef struct {
+ xf86ITEventHeader header;
+ uint32_t is_absolute;
+ xf86ITValuatorData valuators;
+} xf86ITEventMotion;
+
+typedef struct {
+ xf86ITEventHeader header;
+ uint32_t is_prox_in;
+ xf86ITValuatorData valuators;
+} xf86ITEventProximity;
+
+typedef struct {
+ xf86ITEventHeader header;
+ int32_t is_absolute;
+ int32_t button;
+ uint32_t is_press;
+ xf86ITValuatorData valuators;
+} xf86ITEventButton;
+
+#endif
diff --git a/tests/x11/test_tablet.c b/tests/x11/test_tablet.c
new file mode 100644
index 0000000..39717df
--- /dev/null
+++ b/tests/x11/test_tablet.c
@@ -0,0 +1,103 @@
+/* Exercise native XI2 samples through the backend and public input queue. */
+#include "../../src/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;
+}
+
+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);
+ 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);
+ return 0;
+}
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;
+}