From c58369db1c313ca3d62c5765c1ca0cbd61a3892d Mon Sep 17 00:00:00 2001 From: Johannes Herman Date: Tue, 12 Aug 2025 12:10:56 +0200 Subject: added tablet --- Makefile | 6 +- config.def.h | 31 +++- dwl.c | 228 +++++++++++++++++++++++++- patches/tablet-input-0.7.patch | 355 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 611 insertions(+), 9 deletions(-) create mode 100644 patches/tablet-input-0.7.patch diff --git a/Makefile b/Makefile index 3358bae..47a2bb2 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 36fa943..fc74d4c 100644 --- a/config.def.h +++ b/config.def.h @@ -4,15 +4,16 @@ ((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 int smartborders = 1; /* 1 means no outer gap when there is only one window */ static const int smartgaps = 1; /* 1 means no outer gap when there is only one window */ static int gaps = 1; /* 1 means gaps between windows are added */ -static const unsigned int gappx = 10; /* gap pixel between windows */ +static const unsigned int gappx = 8; /* gap pixel between windows */ static const unsigned int borderpx = 3; /* border pixel of windows */ static const float rootcolor[] = COLOR(0x282828ff); -static const float bordercolor[] = COLOR(0x7c6f64ff); +static const float bordercolor[] = COLOR(0x282828ee); static const float focuscolor[] = COLOR(0x458588ff); static const float urgentcolor[] = COLOR(0xcc241dff); /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ @@ -134,12 +135,16 @@ static const char *alttermcmd[] = { "foot", NULL }; static const char *browsercmd[] = { "firefox", NULL }; static const char *menucmd[] = { "bemenu-run", NULL }; +static const char *volume_raise[] = { "volume", "raise", NULL }; +static const char *volume_lower[] = { "volume", "lower", NULL }; +static const char *volume_mute[] = { "volume", "mute", NULL }; + static const Key keys[] = { /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ /* modifier key function argument */ { MODKEY, XKB_KEY_d, spawn, {.v = menucmd} }, { MODKEY, XKB_KEY_q, spawn, {.v = termcmd} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_q, spawn, {.v = alttermcmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, spawn, {.v = alttermcmd} }, { MODKEY, XKB_KEY_w, spawn, {.v = browsercmd} }, { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, @@ -147,18 +152,28 @@ static const Key keys[] = { { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_L, incnmaster, {.i = -1} }, { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_f, togglefullscreen, {0} }, { MODKEY, XKB_KEY_Return, zoom, {0} }, { MODKEY, XKB_KEY_Tab, view, {0} }, { MODKEY, XKB_KEY_g, togglegaps, {0} }, { MODKEY, XKB_KEY_p, togglesticky, {0} }, { MODKEY, XKB_KEY_c, killclient, {0} }, + { MODKEY, XKB_KEY_s, spawn, SHCMD("snap") }, + // { MODKEY|ShiftMask, XK_s, spawn, SHCMD("snap -s") }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_D, spawn, SHCMD("vis pop-cal 60") }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, spawn, SHCMD("vis pop-clock 1") }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_B, spawn, SHCMD("vis pop-bat 5") }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_N, spawn, SHCMD("vis pop-net 10") }, + { MODKEY, XKB_KEY_Escape, spawn, SHCMD("vis dismiss") }, + { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_v, spawn, {.v = volume_mute } }, + { MODKEY, XKB_KEY_v, spawn, {.v = volume_raise } }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_V, spawn, {.v = volume_lower } }, { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, // { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, - { MODKEY, XKB_KEY_s, setlayout, {.v = &layouts[3]} }, - { MODKEY, XKB_KEY_space, setlayout, {0} }, + // { MODKEY, XKB_KEY_s, setlayout, {.v = &layouts[3]} }, + // { MODKEY, XKB_KEY_space, setlayout, {0} }, { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, - { MODKEY, XKB_KEY_f, togglefullscreen, {0} }, { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_equal, tag, {.ui = ~0} }, { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, @@ -175,6 +190,10 @@ static const Key keys[] = { TAGKEYS( XKB_KEY_8, XKB_KEY_parenleft, 7), TAGKEYS( XKB_KEY_9, XKB_KEY_parenright, 8), { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_M, quit, {0} }, + { 0, XKB_KEY_XF86AudioMute, spawn, {.v = volume_mute } }, + { 0, XKB_KEY_XF86AudioRaiseVolume, spawn, {.v = volume_raise } }, + { 0, XKB_KEY_XF86AudioLowerVolume, spawn, {.v = volume_lower } }, + { 0, XKB_KEY_XF86AudioMicMute, spawn, SHCMD("mic mute") }, /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, diff --git a/dwl.c b/dwl.c index 396a566..684491a 100644 --- a/dwl.c +++ b/dwl.c @@ -50,6 +50,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -76,8 +79,6 @@ #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((((C)->tags & (M)->tagset[(M)->seltags]) && !(C)->swallowedby) || C->issticky)) -// #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && (((C)->tags & (M)->tagset[(M)->seltags]) || C->issticky)) - #define LENGTH(X) (sizeof X / sizeof X[0]) #define END(A) ((A) + LENGTH(A)) #define TAGMASK ((1u << TAGCOUNT) - 1) @@ -283,6 +284,7 @@ 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 cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); static void cursorframe(struct wl_listener *listener, void *data); static void cursorwarptohint(void); @@ -297,6 +299,9 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data); static void destroysessionlock(struct wl_listener *listener, void *data); static void destroysessionmgr(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); @@ -356,6 +361,12 @@ static void startdrag(struct wl_listener *listener, void *data); static void swallow(Client *c, Client *toswallow); 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 Client *termforwin(Client *c); static void tile(Monitor *m); static void togglefloating(const Arg *arg); @@ -428,6 +439,13 @@ static struct { int hotspot_y; } last_cursor; +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; @@ -1181,6 +1199,28 @@ 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); + LISTEN_STATIC(&tablet->wlr_device->events.destroy, destroytablet); + 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 cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) { @@ -1368,6 +1408,27 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) free(group); } +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; +} + Monitor * dirtomon(enum wlr_direction dir) { @@ -1625,6 +1686,12 @@ 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; default: /* TODO handle other input device types */ break; @@ -2706,6 +2773,8 @@ setup(void) hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), hidecursor, cursor); + tablet_mgr = wlr_tablet_v2_create(dpy); + /* * Creates a cursor, which is a wlroots utility for tracking the cursor * image shown on screen. @@ -2735,6 +2804,10 @@ setup(void) LISTEN_STATIC(&cursor->events.button, buttonpress); LISTEN_STATIC(&cursor->events.axis, axisnotify); LISTEN_STATIC(&cursor->events.frame, cursorframe); + LISTEN_STATIC(&cursor->events.tablet_tool_proximity, tablettoolproximity); + LISTEN_STATIC(&cursor->events.tablet_tool_axis, tablettoolaxis); + LISTEN_STATIC(&cursor->events.tablet_tool_button, tablettoolbutton); + LISTEN_STATIC(&cursor->events.tablet_tool_tip, tablettooltip); cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); LISTEN_STATIC(&cursor_shape_mgr->events.request_set_shape, setcursorshape); @@ -2903,6 +2976,7 @@ snail(Monitor *m) } } + void spawn(const Arg *arg) { @@ -3018,6 +3092,156 @@ termforwin(Client *c) return NULL; } +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(tablet, surface)) + 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); + LISTEN_STATIC(&tablet_tool->wlr_tool->events.destroy, destroytablettool); + LISTEN_STATIC(&tablet_tool->events.set_cursor, setcursor); + } + + 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)) + 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); +} + void tile(Monitor *m) { diff --git a/patches/tablet-input-0.7.patch b/patches/tablet-input-0.7.patch new file mode 100644 index 0000000..37fdf8a --- /dev/null +++ b/patches/tablet-input-0.7.patch @@ -0,0 +1,355 @@ +From e504dc0fccfc3994962f03dc824d8907c6afc64f Mon Sep 17 00:00:00 2001 +From: choc +Date: Sat, 4 May 2024 01:16:12 +0800 +Subject: [PATCH] implement wlr-tablet-v2 + +--- + Makefile | 6 +- + config.def.h | 1 + + dwl.c | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 230 insertions(+), 1 deletion(-) + +diff --git a/Makefile b/Makefile +index f955e7b..ce1b556 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 22d2171..3ad98ef 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 ac9c36b..b8d129f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -50,6 +50,9 @@ + #include + #include + #include ++#include ++#include ++#include + #include + #include + #include +@@ -270,6 +273,7 @@ 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 cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); +@@ -284,6 +288,9 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(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); +@@ -337,6 +344,11 @@ 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); +@@ -396,6 +408,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; +@@ -1133,6 +1152,28 @@ 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); ++ LISTEN_STATIC(&tablet->wlr_device->events.destroy, destroytablet); ++ 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 + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1321,6 +1362,27 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) + free(group); + } + ++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; ++} ++ + Monitor * + dirtomon(enum wlr_direction dir) + { +@@ -1540,6 +1602,12 @@ 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; + default: + /* TODO handle other input device types */ + break; +@@ -2567,6 +2635,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. +@@ -2596,6 +2666,10 @@ setup(void) + LISTEN_STATIC(&cursor->events.button, buttonpress); + LISTEN_STATIC(&cursor->events.axis, axisnotify); + LISTEN_STATIC(&cursor->events.frame, cursorframe); ++ LISTEN_STATIC(&cursor->events.tablet_tool_proximity, tablettoolproximity); ++ LISTEN_STATIC(&cursor->events.tablet_tool_axis, tablettoolaxis); ++ LISTEN_STATIC(&cursor->events.tablet_tool_button, tablettoolbutton); ++ LISTEN_STATIC(&cursor->events.tablet_tool_tip, tablettooltip); + + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + LISTEN_STATIC(&cursor_shape_mgr->events.request_set_shape, setcursorshape); +@@ -2689,6 +2763,156 @@ 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(tablet, surface)) ++ 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); ++ LISTEN_STATIC(&tablet_tool->wlr_tool->events.destroy, destroytablettool); ++ LISTEN_STATIC(&tablet_tool->events.set_cursor, setcursor); ++ } ++ ++ 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)) ++ 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); ++} ++ + void + tile(Monitor *m) + { +-- +2.43.0 + -- cgit v1.2.3