From f74eef46583348237814b4559c5e00b7d2ed5761 Mon Sep 17 00:00:00 2001 From: A Frederick Christensen Date: Tue, 17 Feb 2026 08:33:50 -0600 Subject: [PATCH] Apply both tablet-input and touch-input patches --- Makefile | 6 +- config.def.h | 1 + dwl.c | 398 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 404 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 578194f..e0d1835 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,8 @@ dwl: dwl.o util.o $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ - wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ + tablet-v2-protocol.h util.o: util.c util.h # wayland-scanner is a tool which generates C headers and rigging for Wayland @@ -45,6 +46,9 @@ wlr-output-power-management-unstable-v1-protocol.h: xdg-shell-protocol.h: $(WAYLAND_SCANNER) server-header \ $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ +tablet-v2-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/unstable/tablet/tablet-unstable-v2.xml $@ config.h: cp config.def.h $@ diff --git a/config.def.h b/config.def.h index 8a6eda0..1f20dfd 100644 --- a/config.def.h +++ b/config.def.h @@ -4,6 +4,7 @@ ((hex >> 8) & 0xFF) / 255.0f, \ (hex & 0xFF) / 255.0f } /* appearance */ +static const int tabletmaptosurface = 0; /* map tablet input to surface(1) or monitor(0) */ static const int sloppyfocus = 1; /* focus follows mouse */ static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ static const unsigned int borderpx = 1; /* border pixel of windows */ diff --git a/dwl.c b/dwl.c index 320910d..4e32240 100644 --- a/dwl.c +++ b/dwl.c @@ -52,6 +52,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -162,6 +166,12 @@ typedef struct { struct wl_listener destroy; } KeyboardGroup; +typedef struct TouchGroup { + struct wl_list link; + struct wlr_touch *touch; + Monitor *m; +} TouchGroup; + typedef struct { /* Must keep this field first */ unsigned int type; /* LayerShell */ @@ -269,7 +279,10 @@ static void createnotify(struct wl_listener *listener, void *data); static void createpointer(struct wlr_pointer *pointer); static void createpointerconstraint(struct wl_listener *listener, void *data); static void createpopup(struct wl_listener *listener, void *data); +static void createtablet(struct wlr_input_device *device); +static void createtouch(struct wlr_touch *touch); static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); +static void createtouch(struct wlr_touch *touch); static void cursorframe(struct wl_listener *listener, void *data); static void cursorwarptohint(void); static void destroydecoration(struct wl_listener *listener, void *data); @@ -282,6 +295,9 @@ static void destroynotify(struct wl_listener *listener, void *data); static void destroypointerconstraint(struct wl_listener *listener, void *data); static void destroysessionlock(struct wl_listener *listener, void *data); static void destroykeyboardgroup(struct wl_listener *listener, void *data); +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 Monitor *dirtomon(enum wlr_direction dir); static void focusclient(Client *c, int lift); static void focusmon(const Arg *arg); @@ -334,11 +350,20 @@ static void spawn(const Arg *arg); static void startdrag(struct wl_listener *listener, void *data); static void tag(const Arg *arg); static void tagmon(const Arg *arg); +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 void tile(Monitor *m); static void togglefloating(const Arg *arg); static void togglefullscreen(const Arg *arg); static void toggletag(const Arg *arg); static void toggleview(const Arg *arg); +static void touchdown(struct wl_listener *listener, void *data); +static void touchup(struct wl_listener *listener, void *data); +static void touchframe(struct wl_listener *listener, void *data); +static void touchmotion(struct wl_listener *listener, void *data); static void unlocksession(struct wl_listener *listener, void *data); static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); static void unmapnotify(struct wl_listener *listener, void *data); @@ -391,6 +416,13 @@ static struct wlr_pointer_constraint_v1 *active_constraint; static struct wlr_cursor *cursor; static struct wlr_xcursor_manager *cursor_mgr; +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 wlr_scene_rect *root_bg; static struct wlr_session_lock_manager_v1 *session_lock_mgr; static struct wlr_scene_rect *locked_bg; @@ -406,6 +438,7 @@ static struct wlr_output_layout *output_layout; static struct wlr_box sgeom; static struct wl_list mons; static Monitor *selmon; +static struct wl_list touches; /* global event handlers */ static struct wl_listener cursor_axis = {.notify = axisnotify}; @@ -413,6 +446,12 @@ static struct wl_listener cursor_button = {.notify = buttonpress}; static struct wl_listener cursor_frame = {.notify = cursorframe}; static struct wl_listener cursor_motion = {.notify = motionrelative}; static struct wl_listener cursor_motion_absolute = {.notify = motionabsolute}; +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}; static struct wl_listener gpu_reset = {.notify = gpureset}; static struct wl_listener layout_change = {.notify = updatemons}; static struct wl_listener new_idle_inhibitor = {.notify = createidleinhibitor}; @@ -435,6 +474,10 @@ static struct wl_listener request_set_sel = {.notify = setsel}; static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; static struct wl_listener request_start_drag = {.notify = requeststartdrag}; static struct wl_listener start_drag = {.notify = startdrag}; +static struct wl_listener touch_down = {.notify = touchdown}; +static struct wl_listener touch_frame = {.notify = touchframe}; +static struct wl_listener touch_motion = {.notify = touchmotion}; +static struct wl_listener touch_up = {.notify = touchup}; static struct wl_listener new_session_lock = {.notify = locksession}; #ifdef XWAYLAND @@ -782,6 +825,10 @@ cleanuplisteners(void) wl_list_remove(&request_set_cursor_shape.link); wl_list_remove(&request_start_drag.link); wl_list_remove(&start_drag.link); + wl_list_remove(&touch_down.link); + wl_list_remove(&touch_frame.link); + wl_list_remove(&touch_motion.link); + wl_list_remove(&touch_up.link); wl_list_remove(&new_session_lock.link); #ifdef XWAYLAND wl_list_remove(&new_xwayland_surface.link); @@ -1200,6 +1247,38 @@ createpopup(struct wl_listener *listener, void *data) LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); } +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 +createtouch(struct wlr_touch *wlr_touch) +{ + TouchGroup *touch = ecalloc(1, sizeof(TouchGroup)); + + touch->touch = wlr_touch; + wl_list_insert(&touches, &touch->link); + wlr_cursor_attach_input_device(cursor, &wlr_touch->base); +} + void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) { @@ -1384,6 +1463,29 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) free(group); } +void +destroytablet(struct wl_listener *listener, void *data) +{ + wl_list_remove(&tablet_device_destroy.link); + wlr_cursor_detach_input_device(cursor, tablet->wlr_device); + 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; +} + Monitor * dirtomon(enum wlr_direction dir) { @@ -1591,6 +1693,15 @@ inputdevice(struct wl_listener *listener, void *data) case WLR_INPUT_DEVICE_POINTER: createpointer(wlr_pointer_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_TOUCH: + createtouch(wlr_touch_from_input_device(device)); + break; default: /* TODO handle other input device types */ break; @@ -1603,6 +1714,8 @@ inputdevice(struct wl_listener *listener, void *data) caps = WL_SEAT_CAPABILITY_POINTER; if (!wl_list_empty(&kb_group->wlr_group->devices)) caps |= WL_SEAT_CAPABILITY_KEYBOARD; + if (!wl_list_empty(&touches)) + caps |= WL_SEAT_CAPABILITY_TOUCH; wlr_seat_set_capabilities(seat, caps); } @@ -2587,6 +2700,8 @@ setup(void) relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + tablet_mgr = wlr_tablet_v2_create(dpy); + /* * Creates a cursor, which is a wlroots utility for tracking the cursor * image shown on screen. @@ -2616,6 +2731,18 @@ 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); + + + wl_list_init(&touches); + + wl_signal_add(&cursor->events.touch_down, &touch_down); + wl_signal_add(&cursor->events.touch_frame, &touch_frame); + wl_signal_add(&cursor->events.touch_motion, &touch_motion); + wl_signal_add(&cursor->events.touch_up, &touch_up); cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); @@ -2711,6 +2838,163 @@ tagmon(const Arg *arg) setmon(sel, dirtomon(arg->i), 0); } +void +tabletapplymap(double x, double y, struct wlr_input_device *dev) +{ + Client *p; + struct wlr_box geom = {0}; + if (tabletmaptosurface && tablet_curr_surface) { + toplevel_from_wlr_surface(tablet_curr_surface, &p, NULL); + if (p) { + for (; client_get_parent(p); p = client_get_parent(p)); + geom.x = p->geom.x + p->bw; + geom.y = p->geom.y + p->bw; + geom.width = p->geom.width - 2 * p->bw; + geom.height = p->geom.height - 2 * p->bw; + } + } + wlr_cursor_map_input_to_region(cursor, dev, &geom); + wlr_cursor_map_input_to_output(cursor, dev, selmon->wlr_output); +} + +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(x, y, tablet->wlr_device); + + // 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; + } +} + +double tilt_x = 0; +double tilt_y = 0; + +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) + tilt_x = event->tilt_x; + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y) + 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, tilt_x, 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); +} + void tile(Monitor *m) { @@ -2789,6 +3073,120 @@ toggleview(const Arg *arg) printstatus(); } +void +touchdown(struct wl_listener *listener, void *data) +{ + struct wlr_touch_down_event *event = data; + double lx, ly; + double sx, sy; + struct wlr_surface *surface; + Client *c = NULL; + uint32_t serial = 0; + Monitor *m; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + // Map the input to the appropriate output, to ensure that rotation is + // handled. + wl_list_for_each(m, &mons, link) { + if (m == NULL || m->wlr_output == NULL) { + continue; + } + if (event->touch->output_name != NULL && 0 != strcmp(event->touch->output_name, m->wlr_output->name)) { + continue; + } + + wlr_cursor_map_input_to_output(cursor, &event->touch->base, m->wlr_output); + } + + wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly); + + /* Find the client under the pointer and send the event along. */ + xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); + if (sloppyfocus) + focusclient(c, 0); + + if (surface != NULL) { + serial = wlr_seat_touch_notify_down(seat, surface, event->time_msec, event->touch_id, sx, sy); + } + + if (serial && wlr_seat_touch_num_points(seat) == 1) { + /* Emulate a mouse click if the touch event wasn't handled */ + struct wlr_pointer_button_event *button_event = data; + struct wlr_pointer_motion_absolute_event *motion_event = data; + double dx, dy; + + wlr_cursor_absolute_to_layout_coords(cursor, &motion_event->pointer->base, motion_event->x, motion_event->y, &lx, &ly); + wlr_cursor_warp_closest(cursor, &motion_event->pointer->base, lx, ly); + dx = lx - cursor->x; + dy = ly - cursor->y; + motionnotify(motion_event->time_msec, &motion_event->pointer->base, dx, dy, dx, dy); + + button_event->button = BTN_LEFT; + button_event->state = WL_POINTER_BUTTON_STATE_PRESSED; + buttonpress(listener, button_event); + } +} + +void +touchup(struct wl_listener *listener, void *data) +{ + struct wlr_touch_up_event *event = data; + + if (!wlr_seat_touch_get_point(seat, event->touch_id)) { + return; + } + + if (wlr_seat_touch_num_points(seat) == 1) { + struct wlr_pointer_button_event *button_event = data; + + button_event->button = BTN_LEFT; + button_event->state = WL_POINTER_BUTTON_STATE_RELEASED; + buttonpress(listener, button_event); + } + + wlr_seat_touch_notify_up(seat, event->time_msec, event->touch_id); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +} + +void +touchframe(struct wl_listener *listener, void *data) +{ + wlr_seat_touch_notify_frame(seat); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +} + +void +touchmotion(struct wl_listener *listener, void *data) +{ + struct wlr_touch_motion_event *event = data; + double lx, ly; + double sx, sy; + struct wlr_surface *surface; + Client *c = NULL; + + if (!wlr_seat_touch_get_point(seat, event->touch_id)) { + return; + } + + wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly); + xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); + + if (c != NULL && surface != NULL) { + if (sloppyfocus) + focusclient(c, 0); + wlr_seat_touch_point_focus(seat, surface, event->time_msec, event->touch_id, sx, sy); + } else { + if (sloppyfocus) + focusclient(NULL, 0); + wlr_seat_touch_point_clear_focus(seat, event->time_msec, event->touch_id); + } + wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, sx, sy); + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +} + + void unlocksession(struct wl_listener *listener, void *data) { -- 2.52.0