From b4fce75270a75d91280b5ebf0b8d07652d3d7961 Mon Sep 17 00:00:00 2001 From: sqooky! Date: Wed, 10 Sep 2025 13:23:26 -0400 Subject: [PATCH 01/12] feat: tablet support --- src/ext-protocol/all.h | 3 +- src/ext-protocol/tablet.h | 230 ++++++++++++++++++++++++++++++++++++++ src/mango.c | 31 +++-- 3 files changed, 255 insertions(+), 9 deletions(-) create mode 100644 src/ext-protocol/tablet.h diff --git a/src/ext-protocol/all.h b/src/ext-protocol/all.h index 0103248b..1ec7407e 100644 --- a/src/ext-protocol/all.h +++ b/src/ext-protocol/all.h @@ -1,4 +1,5 @@ #include "dwl-ipc.h" #include "ext-workspace.h" #include "foreign-toplevel.h" -#include "text-input.h" \ No newline at end of file +#include "tablet.h" +#include "text-input.h" diff --git a/src/ext-protocol/tablet.h b/src/ext-protocol/tablet.h new file mode 100644 index 00000000..79580818 --- /dev/null +++ b/src/ext-protocol/tablet.h @@ -0,0 +1,230 @@ +#include +#include +#include + +static const int tabletmaptosurface = + 0; /* map tablet input to surface(1) or monitor(0) */ + +static void createtablet(struct wlr_input_device *device); +static void destroytablet(struct wl_listener *listener, void *data); +static void destroytabletsurfacenotify(struct wl_listener *listener, + void *data); +static void destroytablettool(struct wl_listener *listener, void *data); + +static void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, + bool change_x, bool change_y, double x, double y, + double dx, double dy); +static void tablettoolproximity(struct wl_listener *listener, void *data); +static void tablettoolaxis(struct wl_listener *listener, void *data); +static void tablettoolbutton(struct wl_listener *listener, void *data); +static void tablettooltip(struct wl_listener *listener, void *data); +static struct wlr_tablet_manager_v2 *tablet_mgr; +static struct wlr_tablet_v2_tablet *tablet = NULL; +static struct wlr_tablet_v2_tablet_tool *tablet_tool = NULL; +static struct wlr_tablet_v2_tablet_pad *tablet_pad = NULL; +static struct wlr_surface *tablet_curr_surface = NULL; +static struct wl_listener destroy_tablet_surface_listener = { + .notify = destroytabletsurfacenotify}; +static struct wl_listener tablet_device_destroy = {.notify = destroytablet}; +static struct wl_listener tablet_tool_axis = {.notify = tablettoolaxis}; +static struct wl_listener tablet_tool_button = {.notify = tablettoolbutton}; +static struct wl_listener tablet_tool_destroy = {.notify = destroytablettool}; +static struct wl_listener tablet_tool_proximity = {.notify = + tablettoolproximity}; +static struct wl_listener tablet_tool_tip = {.notify = tablettooltip}; + +void createtablet(struct wlr_input_device *device) { + if (!tablet) { + struct libinput_device *device_handle = NULL; + if (!wlr_input_device_is_libinput(device) || + !(device_handle = wlr_libinput_get_device_handle(device))) + return; + + tablet = wlr_tablet_create(tablet_mgr, seat, device); + wl_signal_add(&tablet->wlr_device->events.destroy, + &tablet_device_destroy); + if (libinput_device_config_send_events_get_modes(device_handle)) { + libinput_device_config_send_events_set_mode(device_handle, + send_events_mode); + wlr_cursor_attach_input_device(cursor, device); + } + } else if (device == tablet->wlr_device) { + wlr_log(WLR_ERROR, "createtablet: duplicate device"); + } else { + wlr_log(WLR_ERROR, "createtablet: already have one tablet"); + } +} + +void destroytablet(struct wl_listener *listener, void *data) { tablet = NULL; } + +void destroytabletsurfacenotify(struct wl_listener *listener, void *data) { + if (tablet_curr_surface) + wl_list_remove(&destroy_tablet_surface_listener.link); + tablet_curr_surface = NULL; +} + +void destroytablettool(struct wl_listener *listener, void *data) { + destroytabletsurfacenotify(NULL, NULL); + tablet_tool = NULL; +} + +void tabletapplymap(double tablet_width, double tablet_height, + struct wlr_fbox box, double *x, double *y) +{ + if ((!box.x && !box.y && !box.width && !box.height) + || !tablet_width || !tablet_height) { + return; + } + + if (!box.width) { + box.width = tablet_width - box.x; + } + if (!box.height) { + box.height = tablet_height - box.y; + } + + if (box.x + box.width <= tablet_width) { + const double max_x = 1; + double width_offset = max_x * box.x / tablet_width; + *x = (*x - width_offset) * tablet_width / box.width; + } + if (box.y + box.height <= tablet_height) { + const double max_y = 1; + double height_offset = max_y * box.y / tablet_height; + *y = (*y - height_offset) * tablet_height / box.height; + } +} + +void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, + bool change_y, double x, double y, double dx, double dy) { + struct wlr_surface *surface = NULL; + double sx, sy; + + if (!change_x && !change_y) + return; + + tabletapplymap(tablet->wlr_tablet->width_mm, tablet->wlr_tablet->height_mm, + (struct wlr_fbox){0}, &x, &y); + + // TODO: apply constraints + switch (tablet_tool->wlr_tool->type) { + case WLR_TABLET_TOOL_TYPE_LENS: + case WLR_TABLET_TOOL_TYPE_MOUSE: + wlr_cursor_move(cursor, tablet->wlr_device, dx, dy); + break; + default: + wlr_cursor_warp_absolute(cursor, tablet->wlr_device, change_x ? x : NAN, + change_y ? y : NAN); + break; + } + + motionnotify(0, NULL, 0, 0, 0, 0); + + xytonode(cursor->x, cursor->y, &surface, NULL, NULL, &sx, &sy); + if (surface && !wlr_surface_accepts_tablet_v2(surface, tablet)) + surface = NULL; + + if (surface != tablet_curr_surface) { + if (tablet_curr_surface) { + // TODO: wait until all buttons released before leaving + if (tablet_tool) + wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool); + if (tablet_pad) + wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad, + tablet_curr_surface); + wl_list_remove(&destroy_tablet_surface_listener.link); + } + if (surface) { + if (tablet_pad) + wlr_tablet_v2_tablet_pad_notify_enter(tablet_pad, tablet, + surface); + if (tablet_tool) + wlr_tablet_v2_tablet_tool_notify_proximity_in(tablet_tool, + tablet, surface); + wl_signal_add(&surface->events.destroy, + &destroy_tablet_surface_listener); + } + tablet_curr_surface = surface; + } + + if (surface) + wlr_tablet_v2_tablet_tool_notify_motion(tablet_tool, sx, sy); +} + +void tablettoolproximity(struct wl_listener *listener, void *data) { + struct wlr_tablet_tool_proximity_event *event = data; + struct wlr_tablet_tool *tool = event->tool; + + if (!tablet_tool) { + tablet_tool = wlr_tablet_tool_create(tablet_mgr, seat, tool); + wl_signal_add(&tablet_tool->wlr_tool->events.destroy, + &tablet_tool_destroy); + wl_signal_add(&tablet_tool->events.set_cursor, &request_cursor); + } + + switch (event->state) { + case WLR_TABLET_TOOL_PROXIMITY_OUT: + wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool); + destroytabletsurfacenotify(NULL, NULL); + break; + case WLR_TABLET_TOOL_PROXIMITY_IN: + tablettoolmotion(tablet_tool, true, true, event->x, event->y, 0, 0); + break; + } +} + +void tablettoolaxis(struct wl_listener *listener, void *data) { + struct wlr_tablet_tool_axis_event *event = data; + + tablettoolmotion(tablet_tool, event->updated_axes & WLR_TABLET_TOOL_AXIS_X, + event->updated_axes & WLR_TABLET_TOOL_AXIS_Y, event->x, + event->y, event->dx, event->dy); + + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) + wlr_tablet_v2_tablet_tool_notify_pressure(tablet_tool, event->pressure); + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) + wlr_tablet_v2_tablet_tool_notify_distance(tablet_tool, event->distance); + if (event->updated_axes & + (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) { + printf("DEBUGGING: In axis event handling\n"); + wlr_tablet_v2_tablet_tool_notify_tilt(tablet_tool, event->tilt_x, + event->tilt_y); + } + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) + wlr_tablet_v2_tablet_tool_notify_rotation(tablet_tool, event->rotation); + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) + wlr_tablet_v2_tablet_tool_notify_slider(tablet_tool, event->slider); + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) + wlr_tablet_v2_tablet_tool_notify_wheel(tablet_tool, event->wheel_delta, + 0); +} + +void tablettoolbutton(struct wl_listener *listener, void *data) { + struct wlr_tablet_tool_button_event *event = data; + wlr_tablet_v2_tablet_tool_notify_button( + tablet_tool, event->button, + (enum zwp_tablet_pad_v2_button_state)event->state); +} + +void tablettooltip(struct wl_listener *listener, void *data) { + struct wlr_tablet_tool_tip_event *event = data; + + if (!tablet_curr_surface) { + struct wlr_pointer_button_event fakeptrbtnevent = { + .button = BTN_LEFT, + .state = event->state == WLR_TABLET_TOOL_TIP_UP + ? WL_POINTER_BUTTON_STATE_RELEASED + : WL_POINTER_BUTTON_STATE_PRESSED, + .time_msec = event->time_msec, + }; + buttonpress(NULL, (void *)&fakeptrbtnevent); + } + + if (event->state == WLR_TABLET_TOOL_TIP_UP) { + wlr_tablet_v2_tablet_tool_notify_up(tablet_tool); + return; + } + + wlr_tablet_v2_tablet_tool_notify_down(tablet_tool); + wlr_tablet_tool_v2_start_implicit_grab(tablet_tool); +} diff --git a/src/mango.c b/src/mango.c index b5f208ad..db226a00 100644 --- a/src/mango.c +++ b/src/mango.c @@ -68,6 +68,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -3164,6 +3167,12 @@ void inputdevice(struct wl_listener *listener, void *data) { case WLR_INPUT_DEVICE_KEYBOARD: createkeyboard(wlr_keyboard_from_input_device(device)); break; + case WLR_INPUT_DEVICE_TABLET: + createtablet(device); + break; + case WLR_INPUT_DEVICE_TABLET_PAD: + tablet_pad = wlr_tablet_pad_create(tablet_mgr, seat, device); + break; case WLR_INPUT_DEVICE_POINTER: createpointer(wlr_pointer_from_input_device(device)); break; @@ -4629,14 +4638,15 @@ void setup(void) { dpy = wl_display_create(); event_loop = wl_display_get_event_loop(dpy); pointer_manager = wlr_relative_pointer_manager_v1_create(dpy); - /* The backend is a wlroots feature which abstracts the underlying input - * and output hardware. The autocreate option will choose the most - * suitable backend based on the current environment, such as opening an - * X11 window if an X11 server is running. The NULL argument here - * optionally allows you to pass in a custom renderer if wlr_renderer - * doesn't meet your needs. The backend uses the renderer, for example, - * to fall back to software cursors if the backend does not support - * hardware cursors (some older GPUs don't). */ + tablet_mgr = wlr_tablet_v2_create(dpy); + /* The backend is a wlroots feature which abstracts the underlying input and + * output hardware. The autocreate option will choose the most suitable + * backend based on the current environment, such as opening an X11 window + * if an X11 server is running. The NULL argument here optionally allows you + * to pass in a custom renderer if wlr_renderer doesn't meet your needs. The + * backend uses the renderer, for example, to fall back to software cursors + * if the backend does not support hardware cursors (some older GPUs + * don't). */ if (!(backend = wlr_backend_autocreate(event_loop, &session))) die("couldn't create backend"); @@ -4803,6 +4813,11 @@ void setup(void) { wl_signal_add(&cursor->events.button, &cursor_button); wl_signal_add(&cursor->events.axis, &cursor_axis); wl_signal_add(&cursor->events.frame, &cursor_frame); + wl_signal_add(&cursor->events.tablet_tool_proximity, + &tablet_tool_proximity); + wl_signal_add(&cursor->events.tablet_tool_axis, &tablet_tool_axis); + wl_signal_add(&cursor->events.tablet_tool_button, &tablet_tool_button); + wl_signal_add(&cursor->events.tablet_tool_tip, &tablet_tool_tip); // 这两句代码会造成obs窗口里的鼠标光标消失,不知道注释有什么影响 cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); From 92ed929a3c38cc10b453017520592e8f2e500cfa Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 13 Sep 2025 10:14:02 +0800 Subject: [PATCH 02/12] fix: miss remove destroy destroy listener after tablet devece destroy --- src/ext-protocol/tablet.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ext-protocol/tablet.h b/src/ext-protocol/tablet.h index 79580818..2e1f21eb 100644 --- a/src/ext-protocol/tablet.h +++ b/src/ext-protocol/tablet.h @@ -55,7 +55,10 @@ void createtablet(struct wlr_input_device *device) { } } -void destroytablet(struct wl_listener *listener, void *data) { tablet = NULL; } +void destroytablet(struct wl_listener *listener, void *data) { + wl_list_remove(&listener->link); + tablet = NULL; +} void destroytabletsurfacenotify(struct wl_listener *listener, void *data) { if (tablet_curr_surface) @@ -69,10 +72,9 @@ void destroytablettool(struct wl_listener *listener, void *data) { } void tabletapplymap(double tablet_width, double tablet_height, - struct wlr_fbox box, double *x, double *y) -{ - if ((!box.x && !box.y && !box.width && !box.height) - || !tablet_width || !tablet_height) { + struct wlr_fbox box, double *x, double *y) { + if ((!box.x && !box.y && !box.width && !box.height) || !tablet_width || + !tablet_height) { return; } @@ -104,7 +106,7 @@ void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, return; tabletapplymap(tablet->wlr_tablet->width_mm, tablet->wlr_tablet->height_mm, - (struct wlr_fbox){0}, &x, &y); + (struct wlr_fbox){0}, &x, &y); // TODO: apply constraints switch (tablet_tool->wlr_tool->type) { From 53f6e6c3151ca4c4e2071679c7ed31e8a8e48bf4 Mon Sep 17 00:00:00 2001 From: werapi Date: Wed, 7 Jan 2026 11:02:19 +0100 Subject: [PATCH 03/12] fix: tablet pen press not focusing windows --- src/ext-protocol/tablet.h | 23 +++++++++++++++-------- src/mango.c | 22 ++++++++++++++-------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/ext-protocol/tablet.h b/src/ext-protocol/tablet.h index 2e1f21eb..449a55f6 100644 --- a/src/ext-protocol/tablet.h +++ b/src/ext-protocol/tablet.h @@ -211,15 +211,22 @@ void tablettoolbutton(struct wl_listener *listener, void *data) { void tablettooltip(struct wl_listener *listener, void *data) { struct wlr_tablet_tool_tip_event *event = data; + struct wlr_pointer_button_event fakeptrbtnevent = { + .button = BTN_LEFT, + .state = event->state == WLR_TABLET_TOOL_TIP_UP + ? WL_POINTER_BUTTON_STATE_RELEASED + : WL_POINTER_BUTTON_STATE_PRESSED, + .time_msec = event->time_msec, + }; + + if (handle_buttonpress(&fakeptrbtnevent)) + return; + if (!tablet_curr_surface) { - struct wlr_pointer_button_event fakeptrbtnevent = { - .button = BTN_LEFT, - .state = event->state == WLR_TABLET_TOOL_TIP_UP - ? WL_POINTER_BUTTON_STATE_RELEASED - : WL_POINTER_BUTTON_STATE_PRESSED, - .time_msec = event->time_msec, - }; - buttonpress(NULL, (void *)&fakeptrbtnevent); + wlr_seat_pointer_notify_button(seat, fakeptrbtnevent.time_msec, + fakeptrbtnevent.button, + fakeptrbtnevent.state); + return; } if (event->state == WLR_TABLET_TOOL_TIP_UP) { diff --git a/src/mango.c b/src/mango.c index 8d9b357a..bfbcbdbd 100644 --- a/src/mango.c +++ b/src/mango.c @@ -495,6 +495,7 @@ static void axisnotify(struct wl_listener *listener, void *data); // 滚轮事件处理 static void buttonpress(struct wl_listener *listener, void *data); // 鼠标按键事件处理 +static bool handle_buttonpress(struct wlr_pointer_button_event *event); static int ongesture(struct wlr_pointer_swipe_end_event *event); static void swipe_begin(struct wl_listener *listener, void *data); static void swipe_update(struct wl_listener *listener, void *data); @@ -1770,6 +1771,13 @@ bool check_trackpad_disabled(struct wlr_pointer *pointer) { void // 鼠标按键事件 buttonpress(struct wl_listener *listener, void *data) { struct wlr_pointer_button_event *event = data; + + if (!handle_buttonpress(event)) + wlr_seat_pointer_notify_button(seat, event->time_msec, event->button, + event->state); +} + +bool handle_buttonpress(struct wlr_pointer_button_event *event) { struct wlr_keyboard *hard_keyboard, *keyboard; unsigned int hard_mods, mods; Client *c; @@ -1785,7 +1793,7 @@ buttonpress(struct wl_listener *listener, void *data) { wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); if (check_trackpad_disabled(event->pointer)) { - return; + return true; } switch (event->state) { @@ -1834,12 +1842,12 @@ buttonpress(struct wl_listener *listener, void *data) { event->button == m->button && m->func && (selmon->isoverview == 1 || m->button == BTN_MIDDLE) && c) { m->func(&m->arg); - return; + return true; } else if (CLEANMASK(mods) == CLEANMASK(m->mod) && event->button == m->button && m->func && CLEANMASK(m->mod) != 0) { m->func(&m->arg); - return; + return true; } } break; @@ -1870,16 +1878,14 @@ buttonpress(struct wl_listener *listener, void *data) { apply_window_snap(tmpc); } tmpc->drag_to_tile = false; - return; + return true; } else { cursor_mode = CurNormal; } break; } - /* If the event wasn't handled by the compositor, notify the client with - * pointer focus that a button press has occurred */ - wlr_seat_pointer_notify_button(seat, event->time_msec, event->button, - event->state); + /* If the event wasn't handled by the compositor, return false */ + return false; } void checkidleinhibitor(struct wlr_surface *exclude) { From 790fbf48cb4d58a345194002a140dc93a773fbc3 Mon Sep 17 00:00:00 2001 From: werapi Date: Tue, 2 Dec 2025 17:06:00 +0100 Subject: [PATCH 04/12] opt: update cursor and idle state on tablet motion --- src/ext-protocol/tablet.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ext-protocol/tablet.h b/src/ext-protocol/tablet.h index 449a55f6..f034e078 100644 --- a/src/ext-protocol/tablet.h +++ b/src/ext-protocol/tablet.h @@ -151,6 +151,9 @@ void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, if (surface) wlr_tablet_v2_tablet_tool_notify_motion(tablet_tool, sx, sy); + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + handlecursoractivity(); } void tablettoolproximity(struct wl_listener *listener, void *data) { From 3c06e19fcd59f20a49bb9c6de10488099f43fa42 Mon Sep 17 00:00:00 2001 From: werapi Date: Wed, 7 Jan 2026 14:28:47 +0100 Subject: [PATCH 05/12] opt: handle sloppyfocus for tablet motion --- src/ext-protocol/tablet.h | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/ext-protocol/tablet.h b/src/ext-protocol/tablet.h index f034e078..a874c76a 100644 --- a/src/ext-protocol/tablet.h +++ b/src/ext-protocol/tablet.h @@ -100,6 +100,8 @@ void tabletapplymap(double tablet_width, double tablet_height, void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, double x, double y, double dx, double dy) { struct wlr_surface *surface = NULL; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL; double sx, sy; if (!change_x && !change_y) @@ -122,7 +124,23 @@ void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, motionnotify(0, NULL, 0, 0, 0, 0); - xytonode(cursor->x, cursor->y, &surface, NULL, NULL, &sx, &sy); + xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); + if (cursor_mode == CurPressed && !seat->drag && + surface != seat->pointer_state.focused_surface && + toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, + &l) >= 0) { + c = w; + surface = seat->pointer_state.focused_surface; + sx = cursor->x - (l ? l->scene->node.x : w->geom.x); + sy = cursor->y - (l ? l->scene->node.y : w->geom.y); + } + + if (sloppyfocus && c && c->scene->node.enabled && + (surface != seat->pointer_state.focused_surface || + (selmon && selmon->sel && c != selmon->sel)) && + !client_is_unmanaged(c)) + focusclient(c, 0); + if (surface && !wlr_surface_accepts_tablet_v2(surface, tablet)) surface = NULL; From 01f93be87e3b9d08c6854be2244b038d49dfd9af Mon Sep 17 00:00:00 2001 From: werapi Date: Fri, 9 Jan 2026 11:47:24 +0100 Subject: [PATCH 06/12] fix: avoid dereferencing a NULL pointer in buttonpress for tablet events --- src/mango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index bfbcbdbd..6d98f2d5 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1792,7 +1792,7 @@ bool handle_buttonpress(struct wlr_pointer_button_event *event) { handlecursoractivity(); wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); - if (check_trackpad_disabled(event->pointer)) { + if (event->pointer && check_trackpad_disabled(event->pointer)) { return true; } From cdf5f95e2534f1d452f113d203b881dce5b0de29 Mon Sep 17 00:00:00 2001 From: werapi Date: Fri, 20 Mar 2026 01:57:50 +0100 Subject: [PATCH 07/12] opt: remove dead code from tablet.h --- src/ext-protocol/tablet.h | 37 ++----------------------------------- 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/src/ext-protocol/tablet.h b/src/ext-protocol/tablet.h index 8dcbf2a7..03b9615c 100644 --- a/src/ext-protocol/tablet.h +++ b/src/ext-protocol/tablet.h @@ -2,9 +2,6 @@ #include #include -static const int tabletmaptosurface = - 0; /* map tablet input to surface(1) or monitor(0) */ - static void createtablet(struct wlr_input_device *device); static void destroytablet(struct wl_listener *listener, void *data); static void destroytabletsurfacenotify(struct wl_listener *listener, @@ -44,8 +41,8 @@ void createtablet(struct wlr_input_device *device) { wl_signal_add(&tablet->wlr_device->events.destroy, &tablet_device_destroy); if (libinput_device_config_send_events_get_modes(device_handle)) { - libinput_device_config_send_events_set_mode(device_handle, - config.send_events_mode); + libinput_device_config_send_events_set_mode( + device_handle, config.send_events_mode); wlr_cursor_attach_input_device(cursor, device); } } else if (device == tablet->wlr_device) { @@ -71,32 +68,6 @@ void destroytablettool(struct wl_listener *listener, void *data) { tablet_tool = NULL; } -void tabletapplymap(double tablet_width, double tablet_height, - struct wlr_fbox box, double *x, double *y) { - if ((!box.x && !box.y && !box.width && !box.height) || !tablet_width || - !tablet_height) { - return; - } - - if (!box.width) { - box.width = tablet_width - box.x; - } - if (!box.height) { - box.height = tablet_height - box.y; - } - - if (box.x + box.width <= tablet_width) { - const double max_x = 1; - double width_offset = max_x * box.x / tablet_width; - *x = (*x - width_offset) * tablet_width / box.width; - } - if (box.y + box.height <= tablet_height) { - const double max_y = 1; - double height_offset = max_y * box.y / tablet_height; - *y = (*y - height_offset) * tablet_height / box.height; - } -} - void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, double x, double y, double dx, double dy) { struct wlr_surface *surface = NULL; @@ -107,9 +78,6 @@ void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, if (!change_x && !change_y) return; - tabletapplymap(tablet->wlr_tablet->width_mm, tablet->wlr_tablet->height_mm, - (struct wlr_fbox){0}, &x, &y); - // TODO: apply constraints switch (tablet_tool->wlr_tool->type) { case WLR_TABLET_TOOL_TYPE_LENS: @@ -209,7 +177,6 @@ void tablettoolaxis(struct wl_listener *listener, void *data) { wlr_tablet_v2_tablet_tool_notify_distance(tablet_tool, event->distance); if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) { - printf("DEBUGGING: In axis event handling\n"); wlr_tablet_v2_tablet_tool_notify_tilt(tablet_tool, event->tilt_x, event->tilt_y); } From 04bde885f1def833e32e1b43a9c7fde40de0bf32 Mon Sep 17 00:00:00 2001 From: werapi Date: Fri, 20 Mar 2026 04:49:44 +0100 Subject: [PATCH 08/12] fix: missing tablet signals cleanup --- src/mango.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mango.c b/src/mango.c index 7686a503..111563b0 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2212,6 +2212,10 @@ void cleanuplisteners(void) { wl_list_remove(&cursor_frame.link); wl_list_remove(&cursor_motion.link); wl_list_remove(&cursor_motion_absolute.link); + wl_list_remove(&tablet_tool_proximity.link); + wl_list_remove(&tablet_tool_axis.link); + wl_list_remove(&tablet_tool_button.link); + wl_list_remove(&tablet_tool_tip.link); wl_list_remove(&gpu_reset.link); wl_list_remove(&new_idle_inhibitor.link); wl_list_remove(&layout_change.link); From ba71bc1de7e640ad9026d605bb956e1a80a46d14 Mon Sep 17 00:00:00 2001 From: werapi Date: Fri, 20 Mar 2026 06:20:27 +0100 Subject: [PATCH 09/12] opt: refactor tablet.h to support mutiple tablets and tools tablet pads are untested --- src/ext-protocol/tablet.h | 335 +++++++++++++++++++++++++++++--------- src/mango.c | 4 +- 2 files changed, 258 insertions(+), 81 deletions(-) diff --git a/src/ext-protocol/tablet.h b/src/ext-protocol/tablet.h index 03b9615c..a1e7734a 100644 --- a/src/ext-protocol/tablet.h +++ b/src/ext-protocol/tablet.h @@ -4,89 +4,236 @@ static void createtablet(struct wlr_input_device *device); static void destroytablet(struct wl_listener *listener, void *data); +static void createtabletpad(struct wlr_input_device *device); +static void destroytabletpad(struct wl_listener *listener, void *data); +static void tabletpadtabletdestroy(struct wl_listener *listener, void *data); +static void tabletpadattach(struct wl_listener *listener, void *data); static void destroytabletsurfacenotify(struct wl_listener *listener, void *data); static void destroytablettool(struct wl_listener *listener, void *data); -static void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, - bool change_x, bool change_y, double x, double y, - double dx, double dy); static void tablettoolproximity(struct wl_listener *listener, void *data); static void tablettoolaxis(struct wl_listener *listener, void *data); static void tablettoolbutton(struct wl_listener *listener, void *data); static void tablettooltip(struct wl_listener *listener, void *data); static struct wlr_tablet_manager_v2 *tablet_mgr; -static struct wlr_tablet_v2_tablet *tablet = NULL; -static struct wlr_tablet_v2_tablet_tool *tablet_tool = NULL; -static struct wlr_tablet_v2_tablet_pad *tablet_pad = NULL; -static struct wlr_surface *tablet_curr_surface = NULL; -static struct wl_listener destroy_tablet_surface_listener = { - .notify = destroytabletsurfacenotify}; -static struct wl_listener tablet_device_destroy = {.notify = destroytablet}; + +struct Tablet { + struct wlr_tablet_v2_tablet *tablet_v2; + struct wl_listener destroy; + struct wl_list link; +}; +static struct wl_list tablets; + +struct TabletTool { + struct wlr_tablet_v2_tablet_tool *tool_v2; + struct Tablet *tablet; + struct wlr_surface *curr_surface; + struct wl_listener destroy; + struct wl_listener surface_destroy; + double tilt_x, tilt_y; +}; + +struct TabletPad { + struct wlr_tablet_v2_tablet_pad *pad_v2; + struct Tablet *tablet; + struct wl_listener tablet_destroy; + struct wl_listener attach; + struct wl_listener destroy; + struct wl_list link; +}; +static struct wl_list tablet_pads; + +static void attach_tablet_pad(struct TabletPad *tablet_pad, + struct Tablet *tablet); +static void tablettoolmotion(struct TabletTool *tool, bool change_x, + bool change_y, double x, double y, double dx, + double dy); + static struct wl_listener tablet_tool_axis = {.notify = tablettoolaxis}; static struct wl_listener tablet_tool_button = {.notify = tablettoolbutton}; -static struct wl_listener tablet_tool_destroy = {.notify = destroytablettool}; static struct wl_listener tablet_tool_proximity = {.notify = tablettoolproximity}; static struct wl_listener tablet_tool_tip = {.notify = tablettooltip}; void createtablet(struct wlr_input_device *device) { + struct Tablet *tablet = calloc(1, sizeof(struct Tablet)); if (!tablet) { - struct libinput_device *device_handle = NULL; - if (!wlr_input_device_is_libinput(device) || - !(device_handle = wlr_libinput_get_device_handle(device))) - return; + wlr_log(WLR_ERROR, "could not allocate tablet"); + return; + } - tablet = wlr_tablet_create(tablet_mgr, seat, device); - wl_signal_add(&tablet->wlr_device->events.destroy, - &tablet_device_destroy); - if (libinput_device_config_send_events_get_modes(device_handle)) { - libinput_device_config_send_events_set_mode( - device_handle, config.send_events_mode); - wlr_cursor_attach_input_device(cursor, device); + struct libinput_device *device_handle = NULL; + if (!wlr_input_device_is_libinput(device) || + !(device_handle = wlr_libinput_get_device_handle(device))) { + free(tablet); + return; + } + + tablet->tablet_v2 = wlr_tablet_create(tablet_mgr, seat, device); + tablet->tablet_v2->wlr_tablet->data = tablet; + tablet->destroy.notify = destroytablet; + wl_signal_add(&tablet->tablet_v2->wlr_device->events.destroy, + &tablet->destroy); + if (libinput_device_config_send_events_get_modes(device_handle)) { + libinput_device_config_send_events_set_mode(device_handle, + config.send_events_mode); + wlr_cursor_attach_input_device(cursor, device); + } + + wl_list_insert(&tablets, &tablet->link); + + /* Search for a sibling tablet pad */ + struct libinput_device_group *group = libinput_device_get_device_group( + wlr_libinput_get_device_handle(device)); + struct TabletPad *tablet_pad; + wl_list_for_each(tablet_pad, &tablet_pads, link) { + struct wlr_input_device *pad_device = tablet_pad->pad_v2->wlr_device; + if (!wlr_input_device_is_libinput(pad_device)) { + continue; + } + + struct libinput_device_group *pad_group = + libinput_device_get_device_group( + wlr_libinput_get_device_handle(pad_device)); + + if (pad_group == group) { + attach_tablet_pad(tablet_pad, tablet); + break; } - } else if (device == tablet->wlr_device) { - wlr_log(WLR_ERROR, "createtablet: duplicate device"); - } else { - wlr_log(WLR_ERROR, "createtablet: already have one tablet"); } } void destroytablet(struct wl_listener *listener, void *data) { + struct Tablet *tablet = wl_container_of(listener, tablet, destroy); + wl_list_remove(&listener->link); - tablet = NULL; + wl_list_remove(&tablet->link); + free(tablet); +} + +void tabletpadtabletdestroy(struct wl_listener *listener, void *data) { + struct TabletPad *tablet_pad = + wl_container_of(listener, tablet_pad, tablet_destroy); + + tablet_pad->tablet = NULL; + + wl_list_remove(&tablet_pad->tablet_destroy.link); + wl_list_init(&tablet_pad->tablet_destroy.link); +} + +void attach_tablet_pad(struct TabletPad *tablet_pad, struct Tablet *tablet) { + tablet_pad->tablet = tablet; + + wl_list_remove(&tablet_pad->tablet_destroy.link); + tablet_pad->tablet_destroy.notify = tabletpadtabletdestroy; + wl_signal_add(&tablet->tablet_v2->wlr_device->events.destroy, + &tablet_pad->tablet_destroy); +} + +void tabletpadattach(struct wl_listener *listener, void *data) { + struct TabletPad *tablet_pad = + wl_container_of(listener, tablet_pad, attach); + struct wlr_tablet_tool *wlr_tool = data; + struct TabletTool *tool = wlr_tool->data; + + if (!tool) { + return; + } + + attach_tablet_pad(tablet_pad, tool->tablet); +} + +void createtabletpad(struct wlr_input_device *device) { + struct TabletPad *tablet_pad = calloc(1, sizeof(struct TabletPad)); + if (!tablet_pad) { + wlr_log(WLR_ERROR, "could not allocate tablet_pad"); + return; + } + tablet_pad->pad_v2 = wlr_tablet_pad_create(tablet_mgr, seat, device); + tablet_pad->destroy.notify = destroytabletpad; + tablet_pad->attach.notify = tabletpadattach; + wl_list_init(&tablet_pad->tablet_destroy.link); + wl_signal_add(&tablet_pad->pad_v2->wlr_device->events.destroy, + &tablet_pad->destroy); + wl_signal_add(&tablet_pad->pad_v2->wlr_pad->events.attach_tablet, + &tablet_pad->attach); + wl_list_insert(&tablet_pads, &tablet_pad->link); + + /* Search for a sibling tablet */ + if (!wlr_input_device_is_libinput(tablet_pad->pad_v2->wlr_device)) { + /* We can only do this on libinput devices */ + return; + } + + struct libinput_device_group *group = libinput_device_get_device_group( + wlr_libinput_get_device_handle(tablet_pad->pad_v2->wlr_device)); + struct Tablet *tablet; + wl_list_for_each(tablet, &tablets, link) { + struct wlr_input_device *tablet_device = tablet->tablet_v2->wlr_device; + if (!wlr_input_device_is_libinput(tablet_device)) { + continue; + } + + struct libinput_device_group *tablet_group = + libinput_device_get_device_group( + wlr_libinput_get_device_handle(tablet_device)); + + if (tablet_group == group) { + attach_tablet_pad(tablet_pad, tablet); + break; + } + } +} + +void destroytabletpad(struct wl_listener *listener, void *data) { + struct TabletPad *tablet_pad = + wl_container_of(listener, tablet_pad, destroy); + + wl_list_remove(&listener->link); + wl_list_remove(&tablet_pad->link); + wl_list_remove(&tablet_pad->tablet_destroy.link); + wl_list_remove(&tablet_pad->attach.link); + free(tablet_pad); } void destroytabletsurfacenotify(struct wl_listener *listener, void *data) { - if (tablet_curr_surface) - wl_list_remove(&destroy_tablet_surface_listener.link); - tablet_curr_surface = NULL; + struct TabletTool *tool = wl_container_of(listener, tool, surface_destroy); + wl_list_remove(&tool->surface_destroy.link); + tool->curr_surface = NULL; } void destroytablettool(struct wl_listener *listener, void *data) { - destroytabletsurfacenotify(NULL, NULL); - tablet_tool = NULL; + struct TabletTool *tool = wl_container_of(listener, tool, destroy); + + if (tool->curr_surface) + wl_list_remove(&tool->surface_destroy.link); + + wl_list_remove(&listener->link); + free(tool); } -void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, - bool change_y, double x, double y, double dx, double dy) { +void tablettoolmotion(struct TabletTool *tool, bool change_x, bool change_y, + double x, double y, double dx, double dy) { struct wlr_surface *surface = NULL; Client *c = NULL, *w = NULL; LayerSurface *l = NULL; + struct Tablet *tablet = tool->tablet; + struct TabletPad *tablet_pad; double sx, sy; if (!change_x && !change_y) return; // TODO: apply constraints - switch (tablet_tool->wlr_tool->type) { + switch (tool->tool_v2->wlr_tool->type) { case WLR_TABLET_TOOL_TYPE_LENS: case WLR_TABLET_TOOL_TYPE_MOUSE: - wlr_cursor_move(cursor, tablet->wlr_device, dx, dy); + wlr_cursor_move(cursor, tablet->tablet_v2->wlr_device, dx, dy); break; default: - wlr_cursor_warp_absolute(cursor, tablet->wlr_device, change_x ? x : NAN, - change_y ? y : NAN); + wlr_cursor_warp_absolute(cursor, tablet->tablet_v2->wlr_device, + change_x ? x : NAN, change_y ? y : NAN); break; } @@ -109,34 +256,35 @@ void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, !client_is_unmanaged(c)) focusclient(c, 0); - if (surface && !wlr_surface_accepts_tablet_v2(surface, tablet)) + if (surface && !wlr_surface_accepts_tablet_v2(surface, tablet->tablet_v2)) surface = NULL; - if (surface != tablet_curr_surface) { - if (tablet_curr_surface) { + if (surface != tool->curr_surface) { + if (tool->curr_surface) { // TODO: wait until all buttons released before leaving - if (tablet_tool) - wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool); - if (tablet_pad) - wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad, - tablet_curr_surface); - wl_list_remove(&destroy_tablet_surface_listener.link); + wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tool_v2); + wl_list_for_each(tablet_pad, &tablet_pads, link) { + if (tablet_pad->tablet && tablet_pad->tablet == tablet) + wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad->pad_v2, + tool->curr_surface); + } + wl_list_remove(&tool->surface_destroy.link); } if (surface) { - if (tablet_pad) - wlr_tablet_v2_tablet_pad_notify_enter(tablet_pad, tablet, - surface); - if (tablet_tool) - wlr_tablet_v2_tablet_tool_notify_proximity_in(tablet_tool, - tablet, surface); - wl_signal_add(&surface->events.destroy, - &destroy_tablet_surface_listener); + wl_list_for_each(tablet_pad, &tablet_pads, link) { + if (tablet_pad->tablet && tablet_pad->tablet == tablet) + wlr_tablet_v2_tablet_pad_notify_enter( + tablet_pad->pad_v2, tablet->tablet_v2, surface); + } + wlr_tablet_v2_tablet_tool_notify_proximity_in( + tool->tool_v2, tablet->tablet_v2, surface); + wl_signal_add(&surface->events.destroy, &tool->surface_destroy); } - tablet_curr_surface = surface; + tool->curr_surface = surface; } if (surface) - wlr_tablet_v2_tablet_tool_notify_motion(tablet_tool, sx, sy); + wlr_tablet_v2_tablet_tool_notify_motion(tool->tool_v2, sx, sy); wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); handlecursoractivity(); @@ -144,60 +292,87 @@ void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, void tablettoolproximity(struct wl_listener *listener, void *data) { struct wlr_tablet_tool_proximity_event *event = data; - struct wlr_tablet_tool *tool = event->tool; + struct wlr_tablet_tool *wlr_tool = event->tool; + struct TabletTool *tool = wlr_tool->data; - if (!tablet_tool) { - tablet_tool = wlr_tablet_tool_create(tablet_mgr, seat, tool); - wl_signal_add(&tablet_tool->wlr_tool->events.destroy, - &tablet_tool_destroy); - wl_signal_add(&tablet_tool->events.set_cursor, &request_cursor); + if (!tool) { + tool = calloc(1, sizeof(struct TabletTool)); + if (!tool) { + wlr_log(WLR_ERROR, "could not allocate tablet_tool"); + return; + } + tool->tool_v2 = wlr_tablet_tool_create(tablet_mgr, seat, wlr_tool); + tool->surface_destroy.notify = destroytabletsurfacenotify; + tool->destroy.notify = destroytablettool; + tool->tablet = event->tablet->data; + wlr_tool->data = tool; + wl_signal_add(&tool->tool_v2->wlr_tool->events.destroy, &tool->destroy); + wl_signal_add(&tool->tool_v2->events.set_cursor, &request_cursor); } switch (event->state) { case WLR_TABLET_TOOL_PROXIMITY_OUT: - wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool); - destroytabletsurfacenotify(NULL, NULL); + wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tool_v2); + if (tool->curr_surface) + wl_list_remove(&tool->surface_destroy.link); + tool->curr_surface = NULL; break; case WLR_TABLET_TOOL_PROXIMITY_IN: - tablettoolmotion(tablet_tool, true, true, event->x, event->y, 0, 0); + tablettoolmotion(tool, true, true, event->x, event->y, 0, 0); break; } } void tablettoolaxis(struct wl_listener *listener, void *data) { struct wlr_tablet_tool_axis_event *event = data; + struct TabletTool *tool = event->tool->data; + if (!tool) + return; - tablettoolmotion(tablet_tool, event->updated_axes & WLR_TABLET_TOOL_AXIS_X, + tablettoolmotion(tool, event->updated_axes & WLR_TABLET_TOOL_AXIS_X, event->updated_axes & WLR_TABLET_TOOL_AXIS_Y, event->x, event->y, event->dx, event->dy); if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) - wlr_tablet_v2_tablet_tool_notify_pressure(tablet_tool, event->pressure); + wlr_tablet_v2_tablet_tool_notify_pressure(tool->tool_v2, + event->pressure); if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) - wlr_tablet_v2_tablet_tool_notify_distance(tablet_tool, event->distance); + wlr_tablet_v2_tablet_tool_notify_distance(tool->tool_v2, + event->distance); + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X) + tool->tilt_x = event->tilt_x; + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y) + tool->tilt_y = event->tilt_y; if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) { - wlr_tablet_v2_tablet_tool_notify_tilt(tablet_tool, event->tilt_x, - event->tilt_y); + wlr_tablet_v2_tablet_tool_notify_tilt(tool->tool_v2, tool->tilt_x, + tool->tilt_y); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) - wlr_tablet_v2_tablet_tool_notify_rotation(tablet_tool, event->rotation); + wlr_tablet_v2_tablet_tool_notify_rotation(tool->tool_v2, + event->rotation); if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) - wlr_tablet_v2_tablet_tool_notify_slider(tablet_tool, event->slider); + wlr_tablet_v2_tablet_tool_notify_slider(tool->tool_v2, event->slider); if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) - wlr_tablet_v2_tablet_tool_notify_wheel(tablet_tool, event->wheel_delta, - 0); + wlr_tablet_v2_tablet_tool_notify_wheel(tool->tool_v2, + event->wheel_delta, 0); } void tablettoolbutton(struct wl_listener *listener, void *data) { struct wlr_tablet_tool_button_event *event = data; + struct TabletTool *tool = event->tool->data; + if (!tool) + return; wlr_tablet_v2_tablet_tool_notify_button( - tablet_tool, event->button, + tool->tool_v2, event->button, (enum zwp_tablet_pad_v2_button_state)event->state); } void tablettooltip(struct wl_listener *listener, void *data) { struct wlr_tablet_tool_tip_event *event = data; + struct TabletTool *tool = event->tool->data; + if (!tool) + return; struct wlr_pointer_button_event fakeptrbtnevent = { .button = BTN_LEFT, @@ -210,7 +385,7 @@ void tablettooltip(struct wl_listener *listener, void *data) { if (handle_buttonpress(&fakeptrbtnevent)) return; - if (!tablet_curr_surface) { + if (!tool->curr_surface) { wlr_seat_pointer_notify_button(seat, fakeptrbtnevent.time_msec, fakeptrbtnevent.button, fakeptrbtnevent.state); @@ -218,10 +393,10 @@ void tablettooltip(struct wl_listener *listener, void *data) { } if (event->state == WLR_TABLET_TOOL_TIP_UP) { - wlr_tablet_v2_tablet_tool_notify_up(tablet_tool); + wlr_tablet_v2_tablet_tool_notify_up(tool->tool_v2); return; } - wlr_tablet_v2_tablet_tool_notify_down(tablet_tool); - wlr_tablet_tool_v2_start_implicit_grab(tablet_tool); + wlr_tablet_v2_tablet_tool_notify_down(tool->tool_v2); + wlr_tablet_tool_v2_start_implicit_grab(tool->tool_v2); } diff --git a/src/mango.c b/src/mango.c index 111563b0..f8e25dba 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3660,7 +3660,7 @@ void inputdevice(struct wl_listener *listener, void *data) { createtablet(device); break; case WLR_INPUT_DEVICE_TABLET_PAD: - tablet_pad = wlr_tablet_pad_create(tablet_mgr, seat, device); + createtabletpad(device); break; case WLR_INPUT_DEVICE_POINTER: createpointer(wlr_pointer_from_input_device(device)); @@ -5716,6 +5716,8 @@ void setup(void) { * to let us know when new input devices are available on the backend. */ wl_list_init(&inputdevices); + wl_list_init(&tablets); + wl_list_init(&tablet_pads); wl_list_init(&keyboard_shortcut_inhibitors); wl_signal_add(&backend->events.new_input, &new_input_device); virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); From 95544b82b29aae8e778e9630c303771bc430b05c Mon Sep 17 00:00:00 2001 From: werapi Date: Fri, 20 Mar 2026 07:19:16 +0100 Subject: [PATCH 10/12] fix: correct set_cursor for tablet tool --- src/ext-protocol/tablet.h | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/ext-protocol/tablet.h b/src/ext-protocol/tablet.h index a1e7734a..beeed0b7 100644 --- a/src/ext-protocol/tablet.h +++ b/src/ext-protocol/tablet.h @@ -11,6 +11,7 @@ static void tabletpadattach(struct wl_listener *listener, void *data); static void destroytabletsurfacenotify(struct wl_listener *listener, void *data); static void destroytablettool(struct wl_listener *listener, void *data); +static void tablettoolsetcursor(struct wl_listener *listener, void *data); static void tablettoolproximity(struct wl_listener *listener, void *data); static void tablettoolaxis(struct wl_listener *listener, void *data); @@ -31,6 +32,7 @@ struct TabletTool { struct wlr_surface *curr_surface; struct wl_listener destroy; struct wl_listener surface_destroy; + struct wl_listener set_cursor; double tilt_x, tilt_y; }; @@ -208,11 +210,29 @@ void destroytablettool(struct wl_listener *listener, void *data) { if (tool->curr_surface) wl_list_remove(&tool->surface_destroy.link); - + wl_list_remove(&tool->set_cursor.link); wl_list_remove(&listener->link); free(tool); } +static void tablettoolsetcursor(struct wl_listener *listener, void *data) { + struct TabletTool *tool = wl_container_of(listener, tool, set_cursor); + struct wlr_tablet_v2_event_cursor *event = data; + + struct wlr_seat_client *focused_client = NULL; + if (tool->tool_v2->focused_surface) { + focused_client = wlr_seat_client_for_wl_client( + seat, + wl_resource_get_client(tool->tool_v2->focused_surface->resource)); + } + + if (focused_client != event->seat_client) + return; + + wlr_cursor_set_surface(cursor, event->surface, event->hotspot_x, + event->hotspot_y); +} + void tablettoolmotion(struct TabletTool *tool, bool change_x, bool change_y, double x, double y, double dx, double dy) { struct wlr_surface *surface = NULL; @@ -304,10 +324,11 @@ void tablettoolproximity(struct wl_listener *listener, void *data) { tool->tool_v2 = wlr_tablet_tool_create(tablet_mgr, seat, wlr_tool); tool->surface_destroy.notify = destroytabletsurfacenotify; tool->destroy.notify = destroytablettool; + tool->set_cursor.notify = tablettoolsetcursor; tool->tablet = event->tablet->data; wlr_tool->data = tool; wl_signal_add(&tool->tool_v2->wlr_tool->events.destroy, &tool->destroy); - wl_signal_add(&tool->tool_v2->events.set_cursor, &request_cursor); + wl_signal_add(&tool->tool_v2->events.set_cursor, &tool->set_cursor); } switch (event->state) { From 8d3613ac54dc552de2f6133d353cb8b2671d72e6 Mon Sep 17 00:00:00 2001 From: werapi Date: Fri, 20 Mar 2026 09:41:46 +0100 Subject: [PATCH 11/12] fix: update selmon on tablet tool motion --- src/ext-protocol/tablet.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ext-protocol/tablet.h b/src/ext-protocol/tablet.h index beeed0b7..0bc25396 100644 --- a/src/ext-protocol/tablet.h +++ b/src/ext-protocol/tablet.h @@ -259,6 +259,9 @@ void tablettoolmotion(struct TabletTool *tool, bool change_x, bool change_y, motionnotify(0, NULL, 0, 0, 0, 0); + if (config.sloppyfocus) + selmon = xytomon(cursor->x, cursor->y); + xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); if (cursor_mode == CurPressed && !seat->drag && surface != seat->pointer_state.focused_surface && From 4ed8301c7f9eb559bf16100f0076bbae56e503fd Mon Sep 17 00:00:00 2001 From: werapi Date: Wed, 25 Mar 2026 16:37:23 +0100 Subject: [PATCH 12/12] feat: map tablet input to config.tablet_map_to_mon on tool creation --- src/config/parse_config.h | 12 ++++++++++++ src/ext-protocol/tablet.h | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index fda401d9..62c8fc87 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -282,6 +282,8 @@ typedef struct { double axis_scroll_factor; + char *tablet_map_to_mon; + int32_t blur; int32_t blur_layer; int32_t blur_optimized; @@ -1677,6 +1679,10 @@ bool parse_option(Config *config, char *key, char *value) { config->button_map = atoi(value); } else if (strcmp(key, "axis_scroll_factor") == 0) { config->axis_scroll_factor = atof(value); + } else if (strcmp(key, "tablet_map_to_mon") == 0) { + if (config->tablet_map_to_mon) + free(config->tablet_map_to_mon); + config->tablet_map_to_mon = strdup(value); } else if (strcmp(key, "gappih") == 0) { config->gappih = atoi(value); } else if (strcmp(key, "gappiv") == 0) { @@ -3094,6 +3100,11 @@ void free_config(void) { config.cursor_theme = NULL; } + if (config.tablet_map_to_mon) { + free(config.tablet_map_to_mon); + config.tablet_map_to_mon = NULL; + } + // 释放 circle_layout free_circle_layout(&config); @@ -3518,6 +3529,7 @@ bool parse_config(void) { config.tag_rules = NULL; config.tag_rules_count = 0; config.cursor_theme = NULL; + config.tablet_map_to_mon = NULL; strcpy(config.keymode, "default"); create_config_keymap(); diff --git a/src/ext-protocol/tablet.h b/src/ext-protocol/tablet.h index 0bc25396..6aa7ae86 100644 --- a/src/ext-protocol/tablet.h +++ b/src/ext-protocol/tablet.h @@ -317,6 +317,7 @@ void tablettoolproximity(struct wl_listener *listener, void *data) { struct wlr_tablet_tool_proximity_event *event = data; struct wlr_tablet_tool *wlr_tool = event->tool; struct TabletTool *tool = wlr_tool->data; + Monitor *m_iter; if (!tool) { tool = calloc(1, sizeof(struct TabletTool)); @@ -332,6 +333,18 @@ void tablettoolproximity(struct wl_listener *listener, void *data) { wlr_tool->data = tool; wl_signal_add(&tool->tool_v2->wlr_tool->events.destroy, &tool->destroy); wl_signal_add(&tool->tool_v2->events.set_cursor, &tool->set_cursor); + + if (config.tablet_map_to_mon) { + wl_list_for_each(m_iter, &mons, link) { + if (match_monitor_spec(config.tablet_map_to_mon, m_iter)) { + wlr_log(WLR_DEBUG, "Mapping tablet %s to output %s", + event->tablet->base.name, config.tablet_map_to_mon); + wlr_cursor_map_input_to_output(cursor, &event->tablet->base, + m_iter->wlr_output); + break; + } + } + } } switch (event->state) {