summaryrefslogtreecommitdiff
path: root/src/x11
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-12 10:17:33 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-12 10:17:33 +0300
commit881fb6cb1d64868132287bd9637f4ae1cbc3504c (patch)
tree5688d5f8be69805ccf290178675b522802155e6d /src/x11
parent3b8af02be94a510e385f44d58c35c86bb89424a6 (diff)
cursor hidden and tablet support
Diffstat (limited to 'src/x11')
-rw-r--r--src/x11/window.c239
1 files changed, 231 insertions, 8 deletions
diff --git a/src/x11/window.c b/src/x11/window.c
index 67276cf..85f1d22 100644
--- a/src/x11/window.c
+++ b/src/x11/window.c
@@ -9,14 +9,17 @@
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/keysym.h>
+#include <X11/extensions/XInput2.h>
#include <xkbcommon/xkbcommon.h>
#include <assert.h>
#include <errno.h>
#include <limits.h>
+#include <math.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
+#include <strings.h>
#define TEXT_LIMIT (16 * 1024 * 1024)
@@ -53,6 +56,18 @@ struct paste_request {
Window window;
};
+struct tablet_axis {
+ int number;
+ double min, max, value;
+};
+
+struct x11_pointer {
+ struct x11_pointer *next;
+ int id;
+ enum yasl_pointer_device device;
+ struct tablet_axis pressure, tilt_x, tilt_y;
+};
+
struct x11_display {
struct yasl_display base;
Display *native;
@@ -70,6 +85,10 @@ struct x11_display {
uint32_t mods;
char *app_id;
bool owns_loop;
+ int xi_opcode;
+ bool xi2;
+ Atom abs_pressure, abs_tilt_x, abs_tilt_y, tool_type, eraser;
+ struct x11_pointer *pointers;
};
static struct x11_display *display_of(const struct yasl_surface *s)
@@ -113,6 +132,176 @@ static uint32_t modifiers(unsigned state)
((state & LockMask) ? YASL_MOD_CAPS : 0);
}
+static void pointer_info(struct x11_display *d, struct x11_pointer *p,
+ const XIDeviceInfo *info)
+{
+ p->id = info->deviceid;
+ p->device = YASL_PTR_DEV_MOUSE;
+ p->pressure.number = p->tilt_x.number = p->tilt_y.number = -1;
+ bool touch = false;
+ for (int i = 0; i < info->num_classes; i++) {
+ XIAnyClassInfo *c = info->classes[i];
+ if (c->type == XITouchClass) touch = true;
+ if (c->type != XIValuatorClass) continue;
+ XIValuatorClassInfo *v = (XIValuatorClassInfo *)c;
+ if (v->mode != XIModeAbsolute) continue;
+ struct tablet_axis *axis = v->label == d->abs_pressure ? &p->pressure :
+ v->label == d->abs_tilt_x ? &p->tilt_x :
+ v->label == d->abs_tilt_y ? &p->tilt_y : NULL;
+ if (axis) *axis = (struct tablet_axis){v->number, v->min, v->max, v->value};
+ }
+ if (!touch && p->pressure.number >= 0) {
+ p->device = YASL_PTR_DEV_PEN;
+ /* Xorg's Wacom and libinput drivers expose separate eraser devices.
+ * Use the tool property when available, with the device name as a
+ * fallback for servers (including Xwayland) without that property. */
+ if (info->name) {
+ for (const char *s = info->name; *s; s++)
+ if (!strncasecmp(s, "eraser", 6)) { p->device = YASL_PTR_DEV_ERASER; break; }
+ }
+ }
+}
+
+static struct x11_pointer *pointer_for(struct x11_display *d, int id)
+{
+ for (struct x11_pointer *p = d->pointers; p; p = p->next)
+ if (p->id == id) return p;
+ int count = 0;
+ XIDeviceInfo *info = XIQueryDevice(d->native, id, &count);
+ if (!info) return NULL;
+ struct x11_pointer *p = count ? calloc(1, sizeof(*p)) : NULL;
+ if (p) {
+ pointer_info(d, p, info);
+ if (p->device == YASL_PTR_DEV_PEN || p->device == YASL_PTR_DEV_ERASER) {
+ Atom type;
+ int format;
+ unsigned long n, after;
+ unsigned char *value = NULL;
+ if (XIGetProperty(d->native, id, d->tool_type, 0, 1, False,
+ XA_ATOM, &type, &format, &n, &after, &value) == Success &&
+ type == XA_ATOM && format == 32 && n == 1 && value)
+ /* XIGetProperty packs format-32 values as uint32_t, unlike
+ * XGetWindowProperty's array of native-sized longs. */
+ p->device = *(uint32_t *)value == d->eraser ? YASL_PTR_DEV_ERASER : p->device;
+ if (value) XFree(value);
+ }
+ p->next = d->pointers;
+ d->pointers = p;
+ }
+ XIFreeDeviceInfo(info);
+ return p;
+}
+
+static void clear_pointers(struct x11_display *d)
+{
+ while (d->pointers) {
+ struct x11_pointer *p = d->pointers;
+ d->pointers = p->next;
+ free(p);
+ }
+}
+
+static void update_axes(struct x11_pointer *p, const XIValuatorState *state)
+{
+ const double *value = state->values;
+ for (int i = 0; i < state->mask_len * 8; i++) {
+ if (!XIMaskIsSet(state->mask, i)) continue;
+ struct tablet_axis *axes[] = {&p->pressure, &p->tilt_x, &p->tilt_y};
+ for (unsigned a = 0; a < sizeof(axes) / sizeof(axes[0]); a++)
+ if (axes[a]->number == i && isfinite(*value)) axes[a]->value = *value;
+ value++;
+ }
+}
+
+static uint16_t pressure_value(const struct tablet_axis *a)
+{
+ if (a->number < 0 || !(a->max > a->min) || !isfinite(a->value)) return 0;
+ double v = (a->value - a->min) / (a->max - a->min);
+ return (uint16_t)lround(fmax(0, fmin(1, v)) * 65535);
+}
+
+static int8_t tilt_value(const struct tablet_axis *a)
+{
+ /* Xorg tablet tilt valuators are already in degrees. */
+ return a->number < 0 || !isfinite(a->value) ? 0 :
+ (int8_t)lround(fmax(-90, fmin(90, a->value)));
+}
+
+static int pointer_button(unsigned button)
+{
+ return button == 1 ? 0 : button == 2 ? 1 : button == 3 ? 2 :
+ button >= 8 && button <= INT16_MAX ? (int)button - 5 : 3;
+}
+
+static void xi_pointer_event(struct x11_surface *s, const XIDeviceEvent *ev)
+{
+ struct x11_display *d = display_of(&s->base);
+ struct x11_pointer *p = pointer_for(d, ev->sourceid);
+ struct yasl_mouse_event mouse = {
+ .pos = {yasl_scale_to_device(ev->event_x, YASL_SCALE_UNIT),
+ yasl_scale_to_device(ev->event_y, YASL_SCALE_UNIT)},
+ .device = p ? p->device : YASL_PTR_DEV_MOUSE,
+ .mods = modifiers(ev->mods.effective),
+ .button = 3,
+ };
+ if (p && (p->device == YASL_PTR_DEV_PEN || p->device == YASL_PTR_DEV_ERASER)) {
+ update_axes(p, &ev->valuators);
+ if (ev->evtype == XI_ButtonRelease && ev->detail == 1)
+ p->pressure.value = p->pressure.min;
+ mouse.pen = (struct yasl_pointer_pen){
+ pressure_value(&p->pressure), tilt_value(&p->tilt_x), tilt_value(&p->tilt_y)};
+ }
+ if (ev->evtype == XI_Motion) mouse.kind = YASL_PTR_MOTION;
+ else if (ev->detail >= 4 && ev->detail <= 7) {
+ if (ev->evtype == XI_ButtonRelease) return;
+ mouse.kind = YASL_PTR_SCROLL;
+ mouse.scroll_lines = (ev->detail == 4 || ev->detail == 6) ? -1 : 1;
+ } else {
+ mouse.kind = ev->evtype == XI_ButtonPress ? YASL_PTR_PRESS : YASL_PTR_RELEASE;
+ mouse.button = pointer_button(ev->detail);
+ }
+ d->mods = mouse.mods;
+ push(s, (struct yasl_input_event){.kind = YASL_INPUT_MOUSE, .mouse = mouse}, NULL, 0);
+}
+
+static void xi_event(struct x11_display *d, XGenericEventCookie *cookie)
+{
+ if (cookie->evtype == XI_HierarchyChanged || cookie->evtype == XI_DeviceChanged) {
+ if (cookie->evtype == XI_DeviceChanged &&
+ ((XIDeviceChangedEvent *)cookie->data)->reason == XISlaveSwitch) return;
+ clear_pointers(d);
+ if (cookie->evtype == XI_HierarchyChanged &&
+ (((XIHierarchyEvent *)cookie->data)->flags &
+ (XISlaveRemoved | XIDeviceDisabled | XISlaveDetached | XIMasterRemoved))) {
+ /* Removal/disable may interrupt a drag before a release arrives. */
+ for (struct x11_surface *s = d->surfaces; s; s = s->next)
+ push(s, (struct yasl_input_event){.kind = YASL_INPUT_RESET}, NULL, 0);
+ }
+ return;
+ }
+ bool crossing = cookie->evtype == XI_Enter || cookie->evtype == XI_Leave;
+ if (!crossing && cookie->evtype != XI_Motion && cookie->evtype != XI_ButtonPress &&
+ cookie->evtype != XI_ButtonRelease) return;
+ Window window = crossing ? ((XIEnterEvent *)cookie->data)->event :
+ ((XIDeviceEvent *)cookie->data)->event;
+ struct x11_surface *s = d->surfaces;
+ while (s && s->window != window) s = s->next;
+ if (!s) return;
+ if (!crossing) xi_pointer_event(s, cookie->data);
+ else {
+ const XIEnterEvent *ev = cookie->data;
+ struct x11_pointer *p = pointer_for(d, ev->sourceid);
+ if (p) p->pressure.value = p->pressure.min;
+ push(s, (struct yasl_input_event){.kind = YASL_INPUT_MOUSE, .mouse = {
+ .pos = {yasl_scale_to_device(ev->event_x, YASL_SCALE_UNIT),
+ yasl_scale_to_device(ev->event_y, YASL_SCALE_UNIT)},
+ .device = p ? p->device : YASL_PTR_DEV_MOUSE,
+ .button = 3, .mods = modifiers(ev->mods.effective),
+ .kind = cookie->evtype == XI_Leave ? YASL_PTR_LEAVE : YASL_PTR_MOTION,
+ }}, NULL, 0);
+ }
+}
+
static bool resize(struct x11_surface *s, struct yasl_vector2i size)
{
if (size.x <= 0 || size.y <= 0 || size.x > 32767 || size.y > 32767 ||
@@ -233,8 +422,19 @@ static struct yasl_surface *create_toplevel(struct yasl_display *base,
0, 0, (unsigned)spec->size.x, (unsigned)spec->size.y, 0, 0, 0);
s->gc = XCreateGC(d->native, s->window, 0, NULL);
XSelectInput(d->native, s->window, ExposureMask | StructureNotifyMask |
- FocusChangeMask | KeyPressMask | KeyReleaseMask | ButtonPressMask |
- ButtonReleaseMask | PointerMotionMask | LeaveWindowMask | PropertyChangeMask);
+ FocusChangeMask | KeyPressMask | KeyReleaseMask | PropertyChangeMask |
+ (d->xi2 ? 0 : ButtonPressMask | ButtonReleaseMask |
+ PointerMotionMask | LeaveWindowMask));
+ if (d->xi2) {
+ unsigned char bits[XIMaskLen(XI_LASTEVENT)] = {0};
+ XISetMask(bits, XI_Motion);
+ XISetMask(bits, XI_ButtonPress);
+ XISetMask(bits, XI_ButtonRelease);
+ XISetMask(bits, XI_Enter);
+ XISetMask(bits, XI_Leave);
+ XIEventMask mask = {XIAllMasterDevices, sizeof(bits), bits};
+ XISelectEvents(d->native, s->window, &mask, 1);
+ }
XSetWMProtocols(d->native, s->window, &d->delete_window, 1);
XClassHint hint = { .res_name = d->app_id, .res_class = d->app_id };
XSetClassHint(d->native, s->window, &hint);
@@ -382,6 +582,12 @@ static void dispatch(struct yasl_display *base)
while (XPending(d->native)) {
XEvent event;
XNextEvent(d->native, &event);
+ if (event.type == GenericEvent && d->xi2 &&
+ event.xcookie.extension == d->xi_opcode && XGetEventData(d->native, &event.xcookie)) {
+ xi_event(d, &event.xcookie);
+ XFreeEventData(d->native, &event.xcookie);
+ continue;
+ }
if (XFilterEvent(&event, None)) continue;
if (event.type == SelectionRequest) {
selection_request(d, &event.xselectionrequest);
@@ -456,8 +662,7 @@ static void dispatch(struct yasl_display *base)
mouse.scroll_lines = (button == 4 || button == 6) ? -1 : 1;
} else {
mouse.kind = event.type == ButtonPress ? YASL_PTR_PRESS : YASL_PTR_RELEASE;
- mouse.button = button == 1 ? 0x110 : button == 2 ? 0x112 :
- button == 3 ? 0x111 : (int)button + 0x10b;
+ mouse.button = pointer_button(button);
}
}
d->mods = mouse.mods = modifiers(state);
@@ -577,6 +782,23 @@ static struct yasl_display *display_open(struct yasl_loop *loop,
d->native = XOpenDisplay(NULL);
if (!d->native) { free(d); errno = ENODEV; return NULL; }
d->screen = DefaultScreen(d->native);
+ int xi_event_base, xi_error_base;
+ int major = 2, minor = 0;
+ d->xi2 = XQueryExtension(d->native, "XInputExtension", &d->xi_opcode,
+ &xi_event_base, &xi_error_base) &&
+ XIQueryVersion(d->native, &major, &minor) == Success && major >= 2;
+ if (d->xi2) {
+ d->abs_pressure = XInternAtom(d->native, "Abs Pressure", False);
+ d->abs_tilt_x = XInternAtom(d->native, "Abs Tilt X", False);
+ d->abs_tilt_y = XInternAtom(d->native, "Abs Tilt Y", False);
+ d->tool_type = XInternAtom(d->native, "Wacom Tool Type", False);
+ d->eraser = XInternAtom(d->native, "ERASER", False);
+ unsigned char bits[XIMaskLen(XI_LASTEVENT)] = {0};
+ XISetMask(bits, XI_HierarchyChanged);
+ XISetMask(bits, XI_DeviceChanged);
+ XIEventMask mask = {XIAllDevices, sizeof(bits), bits};
+ XISelectEvents(d->native, RootWindow(d->native, d->screen), &mask, 1);
+ }
if (!loop) {
d->loop = yasl_loop_create();
d->owns_loop = true;
@@ -635,6 +857,7 @@ static void display_close(struct yasl_display *base)
d->base.loop = NULL;
}
if (d->im) XCloseIM(d->im);
+ clear_pointers(d);
XDestroyWindow(d->native, d->selection_window);
XCloseDisplay(d->native);
free(d->selection[0]);
@@ -645,8 +868,8 @@ static void display_close(struct yasl_display *base)
static uint32_t caps(const struct yasl_display *d)
{
- (void)d;
- return YASL_CAP_PLACE_WINDOW | YASL_CAP_PRIMARY_SELECTION;
+ return YASL_CAP_PLACE_WINDOW | YASL_CAP_PRIMARY_SELECTION |
+ (((const struct x11_display *)d)->xi2 ? YASL_CAP_TABLET : 0);
}
static void copy(struct yasl_display *base, enum yasl_selection sel, const char *text)
@@ -734,10 +957,10 @@ static void cursor(struct yasl_surface *base, enum yasl_cursor cursor)
XC_fleur, XC_bottom_right_corner, XC_bottom_left_corner,
XC_sb_h_double_arrow, XC_sb_v_double_arrow};
struct x11_surface *s = (struct x11_surface *)base;
- if (s->destroyed || (unsigned)cursor > YASL_CURSOR_NONE) return;
+ if (s->destroyed || (unsigned)cursor > YASL_CURSOR_HIDDEN) return;
struct x11_display *d = display_of(base);
Cursor native;
- if (cursor == YASL_CURSOR_NONE) {
+ if (cursor == YASL_CURSOR_HIDDEN) {
char bits[] = {0};
Pixmap pixmap = XCreateBitmapFromData(d->native, s->window, bits, 1, 1);
XColor color = {0};