aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/sway/input/cursor.h3
-rw-r--r--include/sway/input/seat.h2
-rw-r--r--include/sway/input/tablet.h62
-rw-r--r--include/sway/server.h2
-rw-r--r--protocols/meson.build1
-rw-r--r--sway/input/cursor.c242
-rw-r--r--sway/input/seat.c29
-rw-r--r--sway/input/tablet.c345
-rw-r--r--sway/meson.build1
-rw-r--r--sway/server.c3
10 files changed, 644 insertions, 46 deletions
diff --git a/include/sway/input/cursor.h b/include/sway/input/cursor.h
index 516718c9..e46c9b18 100644
--- a/include/sway/input/cursor.h
+++ b/include/sway/input/cursor.h
@@ -21,6 +21,8 @@ struct sway_cursor {
struct sway_node *node;
} previous;
struct wlr_xcursor_manager *xcursor_manager;
+ struct wl_list tablets;
+ struct wl_list tablet_pads;
const char *image;
struct wl_client *image_client;
@@ -42,6 +44,7 @@ struct sway_cursor {
struct wl_listener tool_axis;
struct wl_listener tool_tip;
+ struct wl_listener tool_proximity;
struct wl_listener tool_button;
uint32_t tool_buttons;
diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h
index c963de9b..24a6fed4 100644
--- a/include/sway/input/seat.h
+++ b/include/sway/input/seat.h
@@ -28,6 +28,8 @@ struct sway_seat_device {
struct sway_input_device *input_device;
struct sway_keyboard *keyboard;
struct sway_switch *switch_device;
+ struct sway_tablet *tablet;
+ struct sway_tablet_pad *tablet_pad;
struct wl_list link; // sway_seat::devices
};
diff --git a/include/sway/input/tablet.h b/include/sway/input/tablet.h
new file mode 100644
index 00000000..f30e232a
--- /dev/null
+++ b/include/sway/input/tablet.h
@@ -0,0 +1,62 @@
+#ifndef _SWAY_INPUT_TABLET_H
+#define _SWAY_INPUT_TABLET_H
+#include <wlr/types/wlr_layer_shell_v1.h>
+
+struct sway_seat;
+struct wlr_tablet_tool;
+
+struct sway_tablet {
+ struct wl_list link;
+ struct sway_seat_device *seat_device;
+ struct wlr_tablet_v2_tablet *tablet_v2;
+};
+
+struct sway_tablet_tool {
+ struct sway_seat *seat;
+ struct sway_tablet *tablet;
+ struct wlr_tablet_v2_tablet_tool *tablet_v2_tool;
+
+ double tilt_x, tilt_y;
+
+ struct wl_listener set_cursor;
+ struct wl_listener tool_destroy;
+};
+
+struct sway_tablet_pad {
+ struct wl_list link;
+ struct sway_seat_device *seat_device;
+ struct sway_tablet *tablet;
+ struct wlr_tablet_v2_tablet_pad *tablet_v2_pad;
+
+ struct wl_listener attach;
+ struct wl_listener button;
+ struct wl_listener ring;
+ struct wl_listener strip;
+
+ struct wlr_surface *current_surface;
+ struct wl_listener surface_destroy;
+
+ struct wl_listener tablet_destroy;
+};
+
+struct sway_tablet *sway_tablet_create(struct sway_seat *seat,
+ struct sway_seat_device *device);
+
+void sway_configure_tablet(struct sway_tablet *tablet);
+
+void sway_tablet_destroy(struct sway_tablet *tablet);
+
+void sway_tablet_tool_configure(struct sway_tablet *tablet,
+ struct wlr_tablet_tool *wlr_tool);
+
+struct sway_tablet_pad *sway_tablet_pad_create(struct sway_seat *seat,
+ struct sway_seat_device *device);
+
+void sway_configure_tablet_pad(struct sway_tablet_pad *tablet_pad);
+
+void sway_tablet_pad_destroy(struct sway_tablet_pad *tablet_pad);
+
+void sway_tablet_pad_notify_enter(struct sway_tablet_pad *tablet_pad,
+ struct wlr_surface *surface);
+
+#endif
diff --git a/include/sway/server.h b/include/sway/server.h
index a43cbf72..cd411d3b 100644
--- a/include/sway/server.h
+++ b/include/sway/server.h
@@ -44,6 +44,8 @@ struct sway_server {
struct wlr_xdg_shell *xdg_shell;
struct wl_listener xdg_shell_surface;
+ struct wlr_tablet_manager_v2 *tablet_v2;
+
#if HAVE_XWAYLAND
struct sway_xwayland xwayland;
struct wl_listener xwayland_surface;
diff --git a/protocols/meson.build b/protocols/meson.build
index 4ba6f723..f2f90dad 100644
--- a/protocols/meson.build
+++ b/protocols/meson.build
@@ -14,6 +14,7 @@ protocols = [
[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
[wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'],
+ [wl_protocol_dir, 'unstable/tablet/tablet-unstable-v2.xml'],
['wlr-layer-shell-unstable-v1.xml'],
['idle.xml'],
['wlr-input-inhibitor-unstable-v1.xml'],
diff --git a/sway/input/cursor.c b/sway/input/cursor.c
index c6a332b8..574186d7 100644
--- a/sway/input/cursor.c
+++ b/sway/input/cursor.c
@@ -10,6 +10,7 @@
#include <wlr/types/wlr_box.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_idle.h>
+#include <wlr/types/wlr_tablet_v2.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/util/region.h>
#include "list.h"
@@ -20,6 +21,7 @@
#include "sway/desktop/transaction.h"
#include "sway/input/cursor.h"
#include "sway/input/keyboard.h"
+#include "sway/input/tablet.h"
#include "sway/layers.h"
#include "sway/output.h"
#include "sway/tree/arrange.h"
@@ -443,72 +445,224 @@ static void apply_mapping_from_region(struct wlr_input_device *device,
*y = apply_mapping_from_coord(y1, y2, *y);
}
+static void handle_tablet_tool_position(struct sway_cursor *cursor,
+ struct sway_tablet *tablet,
+ struct wlr_tablet_tool *tool,
+ bool change_x, bool change_y,
+ double x, double y, double dx, double dy,
+ int32_t time_msec) {
+ if (!change_x && !change_y) {
+ return;
+ }
+
+ struct sway_input_device *input_device = tablet->seat_device->input_device;
+ struct input_config *ic = input_device_get_config(input_device);
+ if (ic != NULL && ic->mapped_from_region != NULL) {
+ apply_mapping_from_region(input_device->wlr_device,
+ ic->mapped_from_region, &x, &y);
+ }
+
+ switch (tool->type) {
+ case WLR_TABLET_TOOL_TYPE_MOUSE:
+ wlr_cursor_move(cursor->cursor, input_device->wlr_device, dx, dy);
+ break;
+ default:
+ wlr_cursor_warp_absolute(cursor->cursor, input_device->wlr_device,
+ change_x ? x : NAN, change_y ? y : NAN);
+ }
+
+ double sx, sy;
+ struct wlr_surface *surface = NULL;
+ struct sway_seat *seat = cursor->seat;
+ node_at_coords(seat, cursor->cursor->x, cursor->cursor->y,
+ &surface, &sx, &sy);
+ struct sway_tablet_tool *sway_tool = tool->data;
+
+ if (!surface || !wlr_surface_accepts_tablet_v2(tablet->tablet_v2, surface)) {
+ wlr_tablet_v2_tablet_tool_notify_proximity_out(sway_tool->tablet_v2_tool);
+ cursor_motion(cursor, time_msec, input_device->wlr_device, dx, dy, dx, dy);
+ return;
+ }
+
+ wlr_tablet_v2_tablet_tool_notify_proximity_in(sway_tool->tablet_v2_tool,
+ tablet->tablet_v2, surface);
+
+ wlr_tablet_v2_tablet_tool_notify_motion(sway_tool->tablet_v2_tool, sx, sy);
+}
+
static void handle_tool_axis(struct wl_listener *listener, void *data) {
struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_axis);
wlr_idle_notify_activity(server.idle, cursor->seat->wlr_seat);
struct wlr_event_tablet_tool_axis *event = data;
- struct sway_input_device *input_device = event->device->data;
+ struct sway_tablet_tool *sway_tool = event->tool->data;
- double x = NAN, y = NAN;
- if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X)) {
- x = event->x;
+ if (!sway_tool) {
+ sway_log(SWAY_DEBUG, "tool axis before proximity");
+ return;
}
- if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) {
- y = event->y;
+
+ handle_tablet_tool_position(cursor, sway_tool->tablet, event->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, event->time_msec);
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) {
+ wlr_tablet_v2_tablet_tool_notify_pressure(
+ sway_tool->tablet_v2_tool, event->pressure);
}
- struct input_config *ic = input_device_get_config(input_device);
- if (ic != NULL && ic->mapped_from_region != NULL) {
- apply_mapping_from_region(event->device, ic->mapped_from_region, &x, &y);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) {
+ wlr_tablet_v2_tablet_tool_notify_distance(
+ sway_tool->tablet_v2_tool, event->distance);
}
- double lx, ly;
- wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device,
- x, y, &lx, &ly);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X) {
+ sway_tool->tilt_x = event->tilt_x;
+ }
- double dx = lx - cursor->cursor->x;
- double dy = ly - cursor->cursor->y;
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y) {
+ sway_tool->tilt_y = event->tilt_y;
+ }
- cursor_motion(cursor, event->time_msec, event->device, dx, dy, dx, dy);
- wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat);
- transaction_commit_dirty();
+ if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) {
+ wlr_tablet_v2_tablet_tool_notify_tilt(
+ sway_tool->tablet_v2_tool,
+ sway_tool->tilt_x, sway_tool->tilt_y);
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) {
+ wlr_tablet_v2_tablet_tool_notify_rotation(
+ sway_tool->tablet_v2_tool, event->rotation);
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) {
+ wlr_tablet_v2_tablet_tool_notify_slider(
+ sway_tool->tablet_v2_tool, event->slider);
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) {
+ wlr_tablet_v2_tablet_tool_notify_wheel(
+ sway_tool->tablet_v2_tool, event->wheel_delta, 0);
+ }
}
static void handle_tool_tip(struct wl_listener *listener, void *data) {
struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_tip);
wlr_idle_notify_activity(server.idle, cursor->seat->wlr_seat);
struct wlr_event_tablet_tool_tip *event = data;
- dispatch_cursor_button(cursor, event->device, event->time_msec,
- BTN_LEFT, event->state == WLR_TABLET_TOOL_TIP_DOWN ?
- WLR_BUTTON_PRESSED : WLR_BUTTON_RELEASED);
- wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat);
- transaction_commit_dirty();
+ struct sway_tablet_tool *sway_tool = event->tool->data;
+ struct wlr_tablet_v2_tablet *tablet_v2 = sway_tool->tablet->tablet_v2;
+ struct sway_seat *seat = cursor->seat;
+
+ double sx, sy;
+ struct wlr_surface *surface = NULL;
+ node_at_coords(seat, cursor->cursor->x, cursor->cursor->y,
+ &surface, &sx, &sy);
+
+ if (!surface || !wlr_surface_accepts_tablet_v2(tablet_v2, surface)) {
+ dispatch_cursor_button(cursor, event->device, event->time_msec,
+ BTN_LEFT, event->state == WLR_TABLET_TOOL_TIP_DOWN ?
+ WLR_BUTTON_PRESSED : WLR_BUTTON_RELEASED);
+ wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat);
+ transaction_commit_dirty();
+ return;
+ }
+
+ if (event->state == WLR_TABLET_TOOL_TIP_DOWN) {
+ wlr_tablet_v2_tablet_tool_notify_down(sway_tool->tablet_v2_tool);
+ wlr_tablet_tool_v2_start_implicit_grab(sway_tool->tablet_v2_tool);
+ } else {
+ wlr_tablet_v2_tablet_tool_notify_up(sway_tool->tablet_v2_tool);
+ }
+}
+
+static struct sway_tablet *get_tablet_for_device(struct sway_cursor *cursor,
+ struct wlr_input_device *device) {
+ struct sway_tablet *tablet;
+ wl_list_for_each(tablet, &cursor->tablets, link) {
+ if (tablet->seat_device->input_device->wlr_device == device) {
+ return tablet;
+ }
+ }
+ return NULL;
+}
+
+static void handle_tool_proximity(struct wl_listener *listener, void *data) {
+ struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_proximity);
+ wlr_idle_notify_activity(server.idle, cursor->seat->wlr_seat);
+ struct wlr_event_tablet_tool_proximity *event = data;
+
+ struct wlr_tablet_tool *tool = event->tool;
+ if (!tool->data) {
+ struct sway_tablet *tablet = get_tablet_for_device(cursor, event->device);
+ if (!tablet) {
+ sway_log(SWAY_ERROR, "no tablet for tablet tool");
+ return;
+ }
+ sway_tablet_tool_configure(tablet, tool);
+ }
+
+ struct sway_tablet_tool *sway_tool = tool->data;
+ if (!sway_tool) {
+ sway_log(SWAY_ERROR, "tablet tool not initialized");
+ return;
+ }
+
+ if (event->state == WLR_TABLET_TOOL_PROXIMITY_OUT) {
+ wlr_tablet_v2_tablet_tool_notify_proximity_out(sway_tool->tablet_v2_tool);
+ return;
+ }
+
+ handle_tablet_tool_position(cursor, sway_tool->tablet, event->tool,
+ true, true, event->x, event->y, 0, 0, event->time_msec);
}
static void handle_tool_button(struct wl_listener *listener, void *data) {
struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_button);
wlr_idle_notify_activity(server.idle, cursor->seat->wlr_seat);
struct wlr_event_tablet_tool_button *event = data;
- // TODO: the user may want to configure which tool buttons are mapped to
- // which simulated pointer buttons
- switch (event->state) {
- case WLR_BUTTON_PRESSED:
- if (cursor->tool_buttons == 0) {
- dispatch_cursor_button(cursor, event->device,
- event->time_msec, BTN_RIGHT, event->state);
- }
- cursor->tool_buttons++;
- break;
- case WLR_BUTTON_RELEASED:
- if (cursor->tool_buttons == 1) {
- dispatch_cursor_button(cursor, event->device,
- event->time_msec, BTN_RIGHT, event->state);
+ struct sway_tablet_tool *sway_tool = event->tool->data;
+ struct wlr_tablet_v2_tablet *tablet_v2 = sway_tool->tablet->tablet_v2;
+ struct sway_seat *seat = cursor->seat;
+
+ if (!sway_tool) {
+ sway_log(SWAY_DEBUG, "tool button before proximity");
+ return;
+ }
+
+ double sx, sy;
+ struct wlr_surface *surface = NULL;
+
+ node_at_coords(seat, cursor->cursor->x, cursor->cursor->y,
+ &surface, &sx, &sy);
+
+ if (!surface || !wlr_surface_accepts_tablet_v2(tablet_v2, surface)) {
+ // TODO: the user may want to configure which tool buttons are mapped to
+ // which simulated pointer buttons
+ switch (event->state) {
+ case WLR_BUTTON_PRESSED:
+ if (cursor->tool_buttons == 0) {
+ dispatch_cursor_button(cursor, event->device,
+ event->time_msec, BTN_RIGHT, event->state);
+ }
+ cursor->tool_buttons++;
+ break;
+ case WLR_BUTTON_RELEASED:
+ if (cursor->tool_buttons == 1) {
+ dispatch_cursor_button(cursor, event->device,
+ event->time_msec, BTN_RIGHT, event->state);
+ }
+ cursor->tool_buttons--;
+ break;
}
- cursor->tool_buttons--;
- break;
+ wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat);
+ transaction_commit_dirty();
+ return;
}
- wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat);
- transaction_commit_dirty();
+
+ wlr_tablet_v2_tablet_tool_notify_button(sway_tool->tablet_v2_tool,
+ (enum zwp_tablet_pad_v2_button_state)event->button,
+ (enum zwp_tablet_pad_v2_button_state)event->state);
}
static void check_constraint_region(struct sway_cursor *cursor) {
@@ -698,9 +852,6 @@ struct sway_cursor *sway_cursor_create(struct sway_seat *seat) {
&cursor->touch_motion);
cursor->touch_motion.notify = handle_touch_motion;
- // TODO: tablet protocol support
- // Note: We should emulate pointer events for clients that don't support the
- // tablet protocol when the time comes
wl_signal_add(&wlr_cursor->events.tablet_tool_axis,
&cursor->tool_axis);
cursor->tool_axis.notify = handle_tool_axis;
@@ -708,6 +859,9 @@ struct sway_cursor *sway_cursor_create(struct sway_seat *seat) {
wl_signal_add(&wlr_cursor->events.tablet_tool_tip, &cursor->tool_tip);
cursor->tool_tip.notify = handle_tool_tip;
+ wl_signal_add(&wlr_cursor->events.tablet_tool_proximity, &cursor->tool_proximity);
+ cursor->tool_proximity.notify = handle_tool_proximity;
+
wl_signal_add(&wlr_cursor->events.tablet_tool_button, &cursor->tool_button);
cursor->tool_button.notify = handle_tool_button;
@@ -716,6 +870,8 @@ struct sway_cursor *sway_cursor_create(struct sway_seat *seat) {
cursor->request_set_cursor.notify = handle_request_set_cursor;
wl_list_init(&cursor->constraint_commit.link);
+ wl_list_init(&cursor->tablets);
+ wl_list_init(&cursor->tablet_pads);
cursor->cursor = wlr_cursor;
diff --git a/sway/input/seat.c b/sway/input/seat.c
index b2243fe3..ebd40343 100644
--- a/sway/input/seat.c
+++ b/sway/input/seat.c
@@ -8,6 +8,7 @@
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_primary_selection.h>
+#include <wlr/types/wlr_tablet_v2.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include "config.h"
#include "list.h"
@@ -18,6 +19,7 @@
#include "sway/input/keyboard.h"
#include "sway/input/seat.h"
#include "sway/input/switch.h"
+#include "sway/input/tablet.h"
#include "sway/ipc-server.h"
#include "sway/layers.h"
#include "sway/output.h"
@@ -34,6 +36,8 @@ static void seat_device_destroy(struct sway_seat_device *seat_device) {
}
sway_keyboard_destroy(seat_device->keyboard);
+ sway_tablet_destroy(seat_device->tablet);
+ sway_tablet_pad_destroy(seat_device->tablet_pad);
wlr_cursor_detach_input_device(seat_device->sway_seat->cursor->cursor,
seat_device->input_device->wlr_device);
wl_list_remove(&seat_device->link);
@@ -118,6 +122,14 @@ static void seat_keyboard_notify_enter(struct sway_seat *seat,
state->pressed_keycodes, state->npressed, &keyboard->modifiers);
}
+static void seat_tablet_pads_notify_enter(struct sway_seat *seat,
+ struct wlr_surface *surface) {
+ struct sway_seat_device *seat_device;
+ wl_list_for_each(seat_device, &seat->devices, link) {
+ sway_tablet_pad_notify_enter(seat_device->tablet_pad, surface);
+ }
+}
+
/**
* If con is a view, set it as active and enable keyboard input.
* If con is a container, set all child views as active and don't enable
@@ -138,6 +150,7 @@ static void seat_send_focus(struct sway_node *node, struct sway_seat *seat) {
#endif
seat_keyboard_notify_enter(seat, view->surface);
+ seat_tablet_pads_notify_enter(seat, view->surface);
struct wlr_pointer_constraint_v1 *constraint =
wlr_pointer_constraints_v1_constraint_for_surface(
@@ -638,14 +651,23 @@ static void seat_configure_touch(struct sway_seat *seat,
static void seat_configure_tablet_tool(struct sway_seat *seat,
struct sway_seat_device *sway_device) {
- if ((seat->wlr_seat->capabilities & WL_SEAT_CAPABILITY_POINTER) == 0) {
- seat_configure_xcursor(seat);
+ if (!sway_device->tablet) {
+ sway_device->tablet = sway_tablet_create(seat, sway_device);
}
+ sway_configure_tablet(sway_device->tablet);
wlr_cursor_attach_input_device(seat->cursor->cursor,
sway_device->input_device->wlr_device);
seat_apply_input_config(seat, sway_device);
}
+static void seat_configure_tablet_pad(struct sway_seat *seat,
+ struct sway_seat_device *sway_device) {
+ if (!sway_device->tablet) {
+ sway_device->tablet_pad = sway_tablet_pad_create(seat, sway_device);
+ }
+ sway_configure_tablet_pad(sway_device->tablet_pad);
+}
+
static struct sway_seat_device *seat_get_device(struct sway_seat *seat,
struct sway_input_device *input_device) {
struct sway_seat_device *seat_device = NULL;
@@ -682,7 +704,7 @@ void seat_configure_device(struct sway_seat *seat,
seat_configure_tablet_tool(seat, seat_device);
break;
case WLR_INPUT_DEVICE_TABLET_PAD:
- sway_log(SWAY_DEBUG, "TODO: configure tablet pad");
+ seat_configure_tablet_pad(seat, seat_device);
break;
}
}
@@ -1079,6 +1101,7 @@ void seat_set_focus_surface(struct sway_seat *seat,
seat->has_focus = false;
}
seat_keyboard_notify_enter(seat, surface);
+ seat_tablet_pads_notify_enter(seat, surface);
}
void seat_set_focus_layer(struct sway_seat *seat,
diff --git a/sway/input/tablet.c b/sway/input/tablet.c
new file mode 100644
index 00000000..b0d4d0c6
--- /dev/null
+++ b/sway/input/tablet.c
@@ -0,0 +1,345 @@
+#define _POSIX_C_SOURCE 200809L
+#include <stdlib.h>
+#include <wlr/backend/libinput.h>
+#include <wlr/types/wlr_tablet_v2.h>
+#include "log.h"
+#include "sway/input/cursor.h"
+#include "sway/input/seat.h"
+#include "sway/input/tablet.h"
+
+static void handle_pad_tablet_destroy(struct wl_listener *listener, void *data) {
+ struct sway_tablet_pad *pad =
+ wl_container_of(listener, pad, tablet_destroy);
+
+ pad->tablet = NULL;
+
+ wl_list_remove(&pad->tablet_destroy.link);
+ wl_list_init(&pad->tablet_destroy.link);
+}
+
+static void attach_tablet_pad(struct sway_tablet_pad *tablet_pad,
+ struct sway_tablet *tablet) {
+ sway_log(SWAY_DEBUG, "Attaching tablet pad \"%s\" to tablet tool \"%s\"",
+ tablet_pad->seat_device->input_device->wlr_device->name,
+ tablet->seat_device->input_device->wlr_device->name);
+
+ tablet_pad->tablet = tablet;
+
+ wl_list_remove(&tablet_pad->tablet_destroy.link);
+ tablet_pad->tablet_destroy.notify = handle_pad_tablet_destroy;
+ wl_signal_add(&tablet->seat_device->input_device->wlr_device->events.destroy,
+ &tablet_pad->tablet_destroy);
+}
+
+struct sway_tablet *sway_tablet_create(struct sway_seat *seat,
+ struct sway_seat_device *device) {
+ struct sway_tablet *tablet =
+ calloc(1, sizeof(struct sway_tablet));
+ if (!sway_assert(tablet, "could not allocate sway tablet for seat")) {
+ return NULL;
+ }
+
+ wl_list_insert(&seat->cursor->tablets, &tablet->link);
+
+ device->tablet = tablet;
+ tablet->seat_device = device;
+
+ return tablet;
+}
+
+void sway_configure_tablet(struct sway_tablet *tablet) {
+ struct wlr_input_device *device =
+ tablet->seat_device->input_device->wlr_device;
+ struct sway_seat *seat = tablet->seat_device->sway_seat;
+
+ if ((seat->wlr_seat->capabilities & WL_SEAT_CAPABILITY_POINTER) == 0) {
+ seat_configure_xcursor(seat);
+ }
+
+ tablet->tablet_v2 =
+ wlr_tablet_create(server.tablet_v2, seat->wlr_seat, device);
+
+ /* Search for a sibling tablet pad */
+ if (!wlr_input_device_is_libinput(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(device));
+ struct sway_tablet_pad *tablet_pad;
+ wl_list_for_each(tablet_pad, &seat->cursor->tablet_pads, link) {
+ struct wlr_input_device *pad_device =
+ tablet_pad->seat_device->input_device->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;
+ }
+ }
+}
+
+void sway_tablet_destroy(struct sway_tablet *tablet) {
+ if (!tablet) {
+ return;
+ }
+ wl_list_remove(&tablet->link);
+ free(tablet);
+}
+
+static void handle_tablet_tool_set_cursor(struct wl_listener *listener, void *data) {
+ struct sway_tablet_tool *tool =
+ wl_container_of(listener, tool, set_cursor);
+ struct wlr_tablet_v2_event_cursor *event = data;
+
+ struct sway_cursor *cursor = tool->seat->cursor;
+ if (!seatop_allows_set_cursor(cursor->seat)) {
+ return;
+ }
+
+ struct wl_client *focused_client = NULL;
+ struct wlr_surface *focused_surface =
+ cursor->seat->wlr_seat->pointer_state.focused_surface;
+ if (focused_surface != NULL) {
+ focused_client = wl_resource_get_client(focused_surface->resource);
+ }
+
+ // TODO: check cursor mode
+ if (focused_client == NULL ||
+ event->seat_client->client != focused_client) {
+ sway_log(SWAY_DEBUG, "denying request to set cursor from unfocused client");
+ return;
+ }
+
+ cursor_set_image_surface(cursor, event->surface, event->hotspot_x,
+ event->hotspot_y, focused_client);
+}
+
+static void handle_tablet_tool_destroy(struct wl_listener *listener, void *data) {
+ struct sway_tablet_tool *tool =
+ wl_container_of(listener, tool, tool_destroy);
+
+ wl_list_remove(&tool->tool_destroy.link);
+ wl_list_remove(&tool->set_cursor.link);
+
+ free(tool);
+}
+
+void sway_tablet_tool_configure(struct sway_tablet *tablet,
+ struct wlr_tablet_tool *wlr_tool) {
+ struct sway_tablet_tool *tool =
+ calloc(1, sizeof(struct sway_tablet_tool));
+ if (!sway_assert(tool, "could not allocate sway tablet tool for tablet")) {
+ return;
+ }
+
+ tool->seat = tablet->seat_device->sway_seat;
+ tool->tablet = tablet;
+ tool->tablet_v2_tool =
+ wlr_tablet_tool_create(server.tablet_v2,
+ tablet->seat_device->sway_seat->wlr_seat, wlr_tool);
+
+ tool->tool_destroy.notify = handle_tablet_tool_destroy;
+ wl_signal_add(&wlr_tool->events.destroy, &tool->tool_destroy);
+
+ tool->set_cursor.notify = handle_tablet_tool_set_cursor;
+ wl_signal_add(&tool->tablet_v2_tool->events.set_cursor,
+ &tool->set_cursor);
+
+ wlr_tool->data = tool;
+}
+
+static void handle_tablet_pad_attach(struct wl_listener *listener,
+ void *data) {
+ struct sway_tablet_pad *pad = wl_container_of(listener, pad, attach);
+ struct wlr_tablet_tool *wlr_tool = data;
+ struct sway_tablet_tool *tool = wlr_tool->data;
+
+ if (!tool) {
+ return;
+ }
+
+ attach_tablet_pad(pad, tool->tablet);
+}
+
+static void handle_tablet_pad_ring(struct wl_listener *listener, void *data) {
+ struct sway_tablet_pad *pad = wl_container_of(listener, pad, ring);
+ struct wlr_event_tablet_pad_ring *event = data;
+
+ if (!pad->current_surface) {
+ return;
+ }
+
+ wlr_tablet_v2_tablet_pad_notify_ring(pad->tablet_v2_pad,
+ event->ring, event->position,
+ event->source == WLR_TABLET_PAD_RING_SOURCE_FINGER,
+ event->time_msec);
+}
+
+static void handle_tablet_pad_strip(struct wl_listener *listener, void *data) {
+ struct sway_tablet_pad *pad = wl_container_of(listener, pad, strip);
+ struct wlr_event_tablet_pad_strip *event = data;
+
+ if (!pad->current_surface) {
+ return;
+ }
+
+ wlr_tablet_v2_tablet_pad_notify_strip(pad->tablet_v2_pad,
+ event->strip, event->position,
+ event->source == WLR_TABLET_PAD_STRIP_SOURCE_FINGER,
+ event->time_msec);
+}
+
+static void handle_tablet_pad_button(struct wl_listener *listener, void *data) {
+ struct sway_tablet_pad *pad = wl_container_of(listener, pad, button);
+ struct wlr_event_tablet_pad_button *event = data;
+
+ if (!pad->current_surface) {
+ return;
+ }
+
+ wlr_tablet_v2_tablet_pad_notify_mode(pad->tablet_v2_pad,
+ event->group, event->mode, event->time_msec);
+
+ wlr_tablet_v2_tablet_pad_notify_button(pad->tablet_v2_pad,
+ event->button, event->time_msec,
+ (enum zwp_tablet_pad_v2_button_state)event->state);
+}
+
+struct sway_tablet_pad *sway_tablet_pad_create(struct sway_seat *seat,
+ struct sway_seat_device *device) {
+ struct sway_tablet_pad *tablet_pad =
+ calloc(1, sizeof(struct sway_tablet_pad));
+ if (!sway_assert(tablet_pad, "could not allocate sway tablet")) {
+ return NULL;
+ }
+
+ tablet_pad->seat_device = device;
+ wl_list_init(&tablet_pad->attach.link);
+ wl_list_init(&tablet_pad->button.link);
+ wl_list_init(&tablet_pad->strip.link);
+ wl_list_init(&tablet_pad->ring.link);
+ wl_list_init(&tablet_pad->surface_destroy.link);
+ wl_list_init(&tablet_pad->tablet_destroy.link);
+
+ wl_list_insert(&seat->cursor->tablet_pads, &tablet_pad->link);
+
+ return tablet_pad;
+}
+
+void sway_configure_tablet_pad(struct sway_tablet_pad *tablet_pad) {
+ struct wlr_input_device *device =
+ tablet_pad->seat_device->input_device->wlr_device;
+ struct sway_seat *seat = tablet_pad->seat_device->sway_seat;
+
+ tablet_pad->tablet_v2_pad =
+ wlr_tablet_pad_create(server.tablet_v2, seat->wlr_seat, device);
+
+ wl_list_remove(&tablet_pad->attach.link);
+ tablet_pad->attach.notify = handle_tablet_pad_attach;
+ wl_signal_add(&device->tablet_pad->events.attach_tablet,
+ &tablet_pad->attach);
+
+ wl_list_remove(&tablet_pad->button.link);
+ tablet_pad->button.notify = handle_tablet_pad_button;
+ wl_signal_add(&device->tablet_pad->events.button, &tablet_pad->button);
+
+ wl_list_remove(&tablet_pad->strip.link);
+ tablet_pad->strip.notify = handle_tablet_pad_strip;
+ wl_signal_add(&device->tablet_pad->events.strip, &tablet_pad->strip);
+
+ wl_list_remove(&tablet_pad->ring.link);
+ tablet_pad->ring.notify = handle_tablet_pad_ring;
+ wl_signal_add(&device->tablet_pad->events.ring, &tablet_pad->ring);
+
+ /* Search for a sibling tablet */
+ if (!wlr_input_device_is_libinput(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(device));
+ struct sway_tablet *tool;
+ wl_list_for_each(tool, &seat->cursor->tablets, link) {
+ struct wlr_input_device *tablet =
+ tool->seat_device->input_device->wlr_device;
+ if (!wlr_input_device_is_libinput(tablet)) {
+ continue;
+ }
+
+ struct libinput_device_group *tablet_group =
+ libinput_device_get_device_group(wlr_libinput_get_device_handle(tablet));
+
+ if (tablet_group == group) {
+ attach_tablet_pad(tablet_pad, tool);
+ break;
+ }
+ }
+}
+
+void sway_tablet_pad_destroy(struct sway_tablet_pad *tablet_pad) {
+ if (!tablet_pad) {
+ return;
+ }
+
+ wl_list_remove(&tablet_pad->link);
+ wl_list_remove(&tablet_pad->attach.link);
+ wl_list_remove(&tablet_pad->button.link);
+ wl_list_remove(&tablet_pad->strip.link);
+ wl_list_remove(&tablet_pad->ring.link);
+ wl_list_remove(&tablet_pad->surface_destroy.link);
+ wl_list_remove(&tablet_pad->tablet_destroy.link);
+
+ free(tablet_pad);
+}
+
+static void handle_pad_tablet_surface_destroy(struct wl_listener *listener,
+ void *data) {
+ struct sway_tablet_pad *tablet_pad =
+ wl_container_of(listener, tablet_pad, surface_destroy);
+
+ wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad->tablet_v2_pad,
+ tablet_pad->current_surface);
+ wl_list_remove(&tablet_pad->surface_destroy.link);
+ wl_list_init(&tablet_pad->surface_destroy.link);
+ tablet_pad->current_surface = NULL;
+}
+
+void sway_tablet_pad_notify_enter(struct sway_tablet_pad *tablet_pad,
+ struct wlr_surface *surface) {
+ if (!tablet_pad || !tablet_pad->tablet) {
+ return;
+ }
+
+ if (surface == tablet_pad->current_surface) {
+ return;
+ }
+
+ /* Leave current surface */
+ if (tablet_pad->current_surface) {
+ wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad->tablet_v2_pad,
+ tablet_pad->current_surface);
+ wl_list_remove(&tablet_pad->surface_destroy.link);
+ wl_list_init(&tablet_pad->surface_destroy.link);
+ tablet_pad->current_surface = NULL;
+ }
+
+ if (!wlr_surface_accepts_tablet_v2(tablet_pad->tablet->tablet_v2, surface)) {
+ return;
+ }
+
+ wlr_tablet_v2_tablet_pad_notify_enter(tablet_pad->tablet_v2_pad,
+ tablet_pad->tablet->tablet_v2, surface);
+
+ tablet_pad->current_surface = surface;
+ wl_list_remove(&tablet_pad->surface_destroy.link);
+ tablet_pad->surface_destroy.notify = handle_pad_tablet_surface_destroy;
+ wl_signal_add(&surface->events.destroy, &tablet_pad->surface_destroy);
+}
diff --git a/sway/meson.build b/sway/meson.build
index 4783c58f..24628100 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -31,6 +31,7 @@ sway_sources = files(
'input/seatop_resize_floating.c',
'input/seatop_resize_tiling.c',
'input/switch.c',
+ 'input/tablet.c',
'config/bar.c',
'config/output.c',
diff --git a/sway/server.c b/sway/server.c
index aee2cc87..e2d54947 100644
--- a/sway/server.c
+++ b/sway/server.c
@@ -19,6 +19,7 @@
#include <wlr/types/wlr_relative_pointer_v1.h>
#include <wlr/types/wlr_screencopy_v1.h>
#include <wlr/types/wlr_server_decoration.h>
+#include <wlr/types/wlr_tablet_v2.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/types/wlr_xdg_output_v1.h>
@@ -86,6 +87,8 @@ bool server_init(struct sway_server *server) {
&server->xdg_shell_surface);
server->xdg_shell_surface.notify = handle_xdg_shell_surface;
+ server->tablet_v2 = wlr_tablet_v2_create(server->wl_display);
+
server->server_decoration_manager =
wlr_server_decoration_manager_create(server->wl_display);
wlr_server_decoration_manager_set_default_mode(