diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 10:17:33 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 10:17:33 +0300 |
| commit | 881fb6cb1d64868132287bd9637f4ae1cbc3504c (patch) | |
| tree | 5688d5f8be69805ccf290178675b522802155e6d /src/x11 | |
| parent | 3b8af02be94a510e385f44d58c35c86bb89424a6 (diff) | |
cursor hidden and tablet support
Diffstat (limited to 'src/x11')
| -rw-r--r-- | src/x11/window.c | 239 |
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}; |
