summaryrefslogtreecommitdiff
path: root/src
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
parent3b8af02be94a510e385f44d58c35c86bb89424a6 (diff)
cursor hidden and tablet support
Diffstat (limited to 'src')
-rw-r--r--src/cocoa/window.m78
-rw-r--r--src/wayland/internal.h1
-rw-r--r--src/wayland/pointer.c26
-rw-r--r--src/wayland/surface.c2
-rw-r--r--src/x11/window.c239
5 files changed, 313 insertions, 33 deletions
diff --git a/src/cocoa/window.m b/src/cocoa/window.m
index 6e1e457..6235b77 100644
--- a/src/cocoa/window.m
+++ b/src/cocoa/window.m
@@ -91,6 +91,8 @@ struct cocoa_surface {
uint32_t state;
uint32_t mods;
enum yasl_cursor cursor;
+ enum yasl_pointer_device tablet_device;
+ bool tablet_down;
char title[160];
struct yasl_frame_clock clock;
@@ -106,6 +108,7 @@ struct cocoa_display {
char *app_id;
struct yasl_loop *loop;
id key_up_monitor;
+ NSCursor *hidden_cursor;
YaslAppDelegate *delegate;
NSPoint cascade;
struct cocoa_surface *dead;
@@ -284,6 +287,18 @@ static void push_pointer(struct cocoa_surface *w, NSEvent *ev, enum yasl_pointer
{
if (!w || w->closed || button < 0)
return;
+ bool proximity = ev.type == NSEventTypeTabletProximity ||
+ ev.subtype == NSEventSubtypeTabletProximity;
+ if (proximity) {
+ if (ev.isEnteringProximity) {
+ w->tablet_device = ev.pointingDeviceType == NSPointingDeviceTypeEraser ?
+ YASL_PTR_DEV_ERASER : ev.pointingDeviceType == NSPointingDeviceTypePen ?
+ YASL_PTR_DEV_PEN : YASL_PTR_DEV_MOUSE;
+ return;
+ }
+ kind = YASL_PTR_LEAVE;
+ button = 3;
+ }
w->mods = cocoa_mods(ev.modifierFlags);
if (kind != YASL_PTR_LEAVE)
w->pointer = event_pos(w, ev);
@@ -291,20 +306,26 @@ static void push_pointer(struct cocoa_surface *w, NSEvent *ev, enum yasl_pointer
enum yasl_pointer_device device = YASL_PTR_DEV_MOUSE;
struct yasl_pointer_pen pen = {0};
- float press = (float)ev.pressure;
- if (ev.subtype == NSEventSubtypeTabletPoint || press > 0.0f) {
- if (ev.pointingDeviceType == NSPointingDeviceTypeEraser)
- device = YASL_PTR_DEV_ERASER;
- else if (ev.pointingDeviceType == NSPointingDeviceTypePen)
- device = YASL_PTR_DEV_PEN;
-
+ if (kind == YASL_PTR_LEAVE && w->tablet_device != YASL_PTR_DEV_MOUSE) {
+ device = w->tablet_device;
+ w->tablet_down = false;
+ } else if (ev.type == NSEventTypeTabletPoint || ev.subtype == NSEventSubtypeTabletPoint) {
+ /* pointingDeviceType belongs to proximity events. Remember that
+ * event's tool so erasers and zero-pressure hover stay classified. */
+ device = w->tablet_device == YASL_PTR_DEV_ERASER ? YASL_PTR_DEV_ERASER : YASL_PTR_DEV_PEN;
+ float press = (float)ev.pressure;
if (press > 1.0f) press = 1.0f;
if (press > 0.0f)
pen.pressure = (uint16_t)(press * 65535.0f);
NSPoint tilt = ev.tilt;
- pen.tilt_x = (int8_t)(tilt.x * 90.0);
- pen.tilt_y = (int8_t)(tilt.y * 90.0);
+ pen.tilt_x = (int8_t)lround(fmax(-1, fmin(1, tilt.x)) * 90.0);
+ pen.tilt_y = (int8_t)lround(fmax(-1, fmin(1, -tilt.y)) * 90.0);
+ if (button == 0 && kind == YASL_PTR_PRESS) w->tablet_down = true;
+ if (button == 0 && kind == YASL_PTR_RELEASE) {
+ w->tablet_down = false;
+ pen.pressure = 0;
+ }
}
struct yasl_input_event input_ev = {
@@ -320,6 +341,10 @@ static void push_pointer(struct cocoa_surface *w, NSEvent *ev, enum yasl_pointer
},
};
input_queue_push(&w->input, &input_ev);
+ if (proximity) {
+ w->tablet_device = YASL_PTR_DEV_MOUSE;
+ w->tablet_down = false;
+ }
}
static NSCursor *cocoa_cursor(enum yasl_cursor cursor)
@@ -328,6 +353,17 @@ static NSCursor *cocoa_cursor(enum yasl_cursor cursor)
case YASL_CURSOR_TEXT: return [NSCursor IBeamCursor];
case YASL_CURSOR_LINK: return [NSCursor pointingHandCursor];
case YASL_CURSOR_CROSSHAIR: return [NSCursor crosshairCursor];
+ case YASL_CURSOR_HIDDEN:
+ if (!darwin_ctx.hidden_cursor) {
+ NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(1, 1)];
+ [image lockFocus];
+ [[NSColor clearColor] set];
+ NSRectFillUsingOperation(NSMakeRect(0, 0, 1, 1), NSCompositingOperationCopy);
+ [image unlockFocus];
+ darwin_ctx.hidden_cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSZeroPoint];
+ [image release];
+ }
+ return darwin_ctx.hidden_cursor;
case YASL_CURSOR_ARROW:
default: return [NSCursor arrowCursor];
}
@@ -566,6 +602,21 @@ static NSCursor *cocoa_cursor(enum yasl_cursor cursor)
push_pointer(self.surface, event, YASL_PTR_LEAVE, 3);
}
+- (void)tabletProximity:(NSEvent *)event
+{
+ push_pointer(self.surface, event, YASL_PTR_MOTION, 3);
+}
+
+- (void)tabletPoint:(NSEvent *)event
+{
+ struct cocoa_surface *w = self.surface;
+ if (!w) return;
+ bool down = (event.buttonMask & NSEventButtonMaskPenTip) != 0;
+ enum yasl_pointer_kind kind = down == w->tablet_down ? YASL_PTR_MOTION :
+ down ? YASL_PTR_PRESS : YASL_PTR_RELEASE;
+ push_pointer(w, event, kind, 0);
+}
+
- (void)scrollWheel:(NSEvent *)event
{
struct cocoa_surface *w = self.surface;
@@ -1042,6 +1093,7 @@ void cocoa_display_close(struct cocoa_display *d)
NSApp.delegate = nil;
[d->delegate release];
d->delegate = nil;
+ [d->hidden_cursor release];
free(d->app_id);
free(d->windows);
*d = (struct cocoa_display){0};
@@ -1050,7 +1102,7 @@ void cocoa_display_close(struct cocoa_display *d)
uint32_t cocoa_display_caps(const struct cocoa_display *d)
{
return d ? (YASL_CAP_PLACE_WINDOW | YASL_CAP_CSD | YASL_CAP_NATIVE_GRAPHICS |
- YASL_CAP_FRAME_PACING) : 0;
+ YASL_CAP_FRAME_PACING | YASL_CAP_TABLET) : 0;
}
struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d,
@@ -1230,12 +1282,8 @@ void cocoa_surface_set_background_color(struct cocoa_surface *w, uint32_t bg)
void cocoa_surface_set_cursor(struct cocoa_surface *w, enum yasl_cursor cursor)
{
- if (!w || w->closed || cursor == w->cursor) return;
+ if (!w || w->closed || cursor == w->cursor || (unsigned)cursor > YASL_CURSOR_HIDDEN) return;
w->cursor = cursor;
- if (cursor == YASL_CURSOR_NONE) {
- [NSCursor setHiddenUntilMouseMoves:YES];
- return;
- }
NSPoint p = [w->view convertPoint:w->window.mouseLocationOutsideOfEventStream fromView:nil];
if (w->window.isKeyWindow && NSPointInRect(p, w->view.bounds))
[cocoa_cursor(cursor) set];
diff --git a/src/wayland/internal.h b/src/wayland/internal.h
index 249fc8d..04142f7 100644
--- a/src/wayland/internal.h
+++ b/src/wayland/internal.h
@@ -284,6 +284,7 @@ struct wayland_surface {
struct input_queue input;
/* 4-byte integers */
+ enum yasl_cursor cursor;
int ime_cursor_end;
int ime_shown_begin, ime_shown_end;
int floating_width, floating_height;
diff --git a/src/wayland/pointer.c b/src/wayland/pointer.c
index 3b3ad73..3341d35 100644
--- a/src/wayland/pointer.c
+++ b/src/wayland/pointer.c
@@ -164,7 +164,7 @@ static void set_cursor_theme_fallback(struct seat *s, struct wayland_surface *w,
{"ew-resize", "ew-resize"},
{"ns-resize", "ns-resize"},
};
- int idx = shape < YASL_CURSOR_NONE ? (int)shape : 0;
+ int idx = (unsigned)shape < YASL_CURSOR_HIDDEN ? (int)shape : 0;
struct wl_cursor *cursor = wl_cursor_theme_get_cursor(c->cursor_theme, names[idx][0]);
if (!cursor) cursor = wl_cursor_theme_get_cursor(c->cursor_theme, names[idx][1]);
if (!cursor || !cursor->image_count) return;
@@ -211,7 +211,7 @@ static void seat_set_cursor(struct seat *s, struct wayland_surface *w, enum yasl
if (!s->pointer || !s->pointer_serial || s->pointer_focus != w ||
s->cursor_shape == (int)shape)
return;
- if (shape == YASL_CURSOR_NONE)
+ if (shape == YASL_CURSOR_HIDDEN)
wl_pointer_set_cursor(s->pointer, s->pointer_serial, NULL, 0, 0);
else if (s->cursor_shape_dev)
wp_cursor_shape_device_v1_set_shape(s->cursor_shape_dev,
@@ -225,7 +225,7 @@ static void tool_set_cursor(struct tablet_tool *t, enum yasl_cursor shape)
{
if (!t->proximity_serial)
return;
- if (shape == YASL_CURSOR_NONE)
+ if (shape == YASL_CURSOR_HIDDEN)
zwp_tablet_tool_v2_set_cursor(t->proxy, t->proximity_serial, NULL, 0, 0);
else if (t->shape_dev)
wp_cursor_shape_device_v1_set_shape(t->shape_dev, t->proximity_serial,
@@ -234,7 +234,8 @@ static void tool_set_cursor(struct tablet_tool *t, enum yasl_cursor shape)
void wayland_surface_set_cursor(struct wayland_surface *w, enum yasl_cursor shape)
{
- if (!w || !w->conn) return;
+ if (!w || !w->conn || w->destroyed || (unsigned)shape > YASL_CURSOR_HIDDEN) return;
+ w->cursor = shape;
for (struct seat *s = w->conn->seats; s; s = s->next) {
seat_set_cursor(s, w, shape);
if (s->tool && s->tablet_focus == w)
@@ -325,7 +326,7 @@ static void pointer_enter(void *data, struct wl_pointer *pointer,
s->pointer_pos = device_from_fixed(w, surface_x, surface_y);
s->pointer_serial = serial;
s->cursor_shape = -1;
- seat_set_cursor(s, w, w->parent ? YASL_CURSOR_ARROW : YASL_CURSOR_TEXT);
+ seat_set_cursor(s, w, w->cursor);
s->motion_pending = true;
pointer_event_done(s);
}
@@ -741,7 +742,7 @@ static void tablet_tool_proximity_in(void *data,
if (s->conn->cursor_shape_mgr && !t->shape_dev)
t->shape_dev = wp_cursor_shape_manager_v1_get_tablet_tool_v2(
s->conn->cursor_shape_mgr, t->proxy);
- tool_set_cursor(t, w->parent ? YASL_CURSOR_ARROW : YASL_CURSOR_TEXT);
+ tool_set_cursor(t, w->cursor);
s->tool_motion = true;
}
@@ -753,6 +754,7 @@ static void tablet_tool_proximity_out(void *data, struct zwp_tablet_tool_v2 *too
struct wayland_surface *w = s->tablet_focus;
if (w) {
press_queue_flush(s, &s->tool_presses, NULL, &s->tool_buttons, tool_event(s));
+ s->pen.pressure = 0;
struct yasl_mouse_event ev = tool_event(s);
ev.pos = s->tool_pos;
ev.button = 3;
@@ -763,6 +765,8 @@ static void tablet_tool_proximity_out(void *data, struct zwp_tablet_tool_v2 *too
s->tablet_focus = NULL;
s->tool = NULL;
s->tool_motion = false;
+ s->tool_wheel = 0;
+ s->tool_buttons = 0;
t->proximity_serial = 0;
}
@@ -867,7 +871,11 @@ static void tool_frame(void *d, struct zwp_tablet_tool_v2 *t, uint32_t time)
}
press_queue_flush(s, &s->tool_presses, NULL, &s->tool_buttons, like);
if (s->tool_wheel) {
- report_wheel(s, w, s->tool_pos, s->tool_wheel > 0 ? 5 : 4, -s->tool_wheel, 0.0f);
+ struct yasl_mouse_event ev = like;
+ ev.pos = s->tool_pos;
+ ev.kind = YASL_PTR_SCROLL;
+ ev.scroll_lines = -s->tool_wheel;
+ push_mouse(s, w, ev);
s->tool_wheel = 0;
}
}
@@ -877,9 +885,7 @@ static void tablet_tool_removed(void *data, struct zwp_tablet_tool_v2 *tool)
struct tablet_tool *t = data;
struct seat *s = t->seat;
if (s->tool == t) {
- s->tool = NULL;
- s->tablet_focus = NULL;
- s->tool_presses.n = 0;
+ tablet_tool_proximity_out(t, tool);
}
struct tablet_tool **link = &s->tablet_tools;
while (*link && *link != t)
diff --git a/src/wayland/surface.c b/src/wayland/surface.c
index f291190..ce1764b 100644
--- a/src/wayland/surface.c
+++ b/src/wayland/surface.c
@@ -284,6 +284,7 @@ uint32_t wayland_display_caps(const struct wayland_display *c)
{
if (!c) return 0;
uint32_t caps = YASL_CAP_POPUPS | YASL_CAP_CSD | YASL_CAP_FRAME_PACING;
+ if (c->tablet_mgr) caps |= YASL_CAP_TABLET;
struct seat *s = conn_active_seat(c);
const struct wl_data_device *device = s ? clipboard_data_device(s->clipboard) : NULL;
if (device && wl_data_device_get_version((struct wl_data_device *)device) >= 3)
@@ -346,6 +347,7 @@ struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *
display_enter(c);
conn_link(c, w);
wayland_surface_set_callbacks(w, NULL, NULL);
+ w->cursor = YASL_CURSOR_TEXT;
w->req_width = width;
w->req_height = height;
w->logical_width = width;
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};