aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/meson.build1
-rw-r--r--backend/wayland/backend.c13
-rw-r--r--backend/wayland/tablet_v2.c941
-rw-r--r--backend/wayland/wl_seat.c2
-rw-r--r--include/backend/wayland.h3
-rw-r--r--protocol/meson.build1
6 files changed, 960 insertions, 1 deletions
diff --git a/backend/meson.build b/backend/meson.build
index ca85ad31..574d4f74 100644
--- a/backend/meson.build
+++ b/backend/meson.build
@@ -28,6 +28,7 @@ backend_files = files(
'wayland/backend.c',
'wayland/output.c',
'wayland/wl_seat.c',
+ 'wayland/tablet_v2.c',
)
backend_deps = [
diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c
index c2762c4a..b1d08568 100644
--- a/backend/wayland/backend.c
+++ b/backend/wayland/backend.c
@@ -19,6 +19,11 @@
#include "xdg-decoration-unstable-v1-client-protocol.h"
#include "pointer-gestures-unstable-v1-client-protocol.h"
#include "xdg-shell-client-protocol.h"
+#include "tablet-unstable-v2-client-protocol.h"
+
+struct wlr_wl_tablet_seat *wlr_wl_add_tablet_seat(
+ struct zwp_tablet_manager_v2 *manager,
+ struct wl_seat *seat, struct wlr_wl_backend *backend);
struct wlr_wl_backend *get_wl_backend_from_backend(struct wlr_backend *backend) {
assert(wlr_backend_is_wl(backend));
@@ -81,6 +86,9 @@ static void registry_global(void *data, struct wl_registry *registry,
} else if (strcmp(iface, zwp_pointer_gestures_v1_interface.name) == 0) {
wl->zwp_pointer_gestures_v1 = wl_registry_bind(registry, name,
&zwp_pointer_gestures_v1_interface, 1);
+ } else if (strcmp(iface, zwp_tablet_manager_v2_interface.name) == 0) {
+ wl->tablet_manager = wl_registry_bind(registry, name,
+ &zwp_tablet_manager_v2_interface, 1);
}
}
@@ -109,6 +117,11 @@ static bool backend_start(struct wlr_backend *backend) {
create_wl_keyboard(wl->keyboard, wl);
}
+ if (wl->tablet_manager && wl->seat) {
+ wlr_wl_add_tablet_seat(wl->tablet_manager,
+ wl->seat, wl);
+ }
+
for (size_t i = 0; i < wl->requested_outputs; ++i) {
wlr_wl_output_create(&wl->backend);
}
diff --git a/backend/wayland/tablet_v2.c b/backend/wayland/tablet_v2.c
new file mode 100644
index 00000000..10a6d84d
--- /dev/null
+++ b/backend/wayland/tablet_v2.c
@@ -0,0 +1,941 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+
+#include <wayland-util.h>
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wlr/interfaces/wlr_tablet_pad.h>
+#include <wlr/interfaces/wlr_tablet_tool.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/interfaces/wlr_input_device.h>
+
+#include "util/signal.h"
+#include "wlr/util/log.h"
+#include "tablet-unstable-v2-client-protocol.h"
+
+#include <backend/wayland.h>
+
+struct wlr_wl_tablet_seat {
+ struct zwp_tablet_seat_v2 *tablet_seat;
+ struct wl_list tablets;
+ struct wl_list tools;
+// struct wl_list pads; // wlr_wl_tablet_pad::link
+};
+
+struct wlr_wl_tablet_tool {
+ /* static */
+ struct zwp_tablet_tool_v2 *tool;
+ struct wlr_tablet_tool wlr_tool;
+
+ /* semi-static */
+ struct wlr_wl_input_device *tablet;
+ double pre_x, pre_y;
+
+ /* per frame */
+ double x, y;
+
+ double pressure;
+ double distance;
+ double tilt_x, tilt_y;
+ double rotation;
+ double slider;
+ double wheel_delta;
+
+ bool is_in;
+ bool is_out;
+
+ bool is_up;
+ bool is_down;
+};
+
+struct wlr_wl_tablet_pad_ring {
+ struct wl_list link; // wlr_wl_tablet_pad_group::rings
+ /* static */
+ struct zwp_tablet_pad_ring_v2 *ring;
+ struct wlr_wl_tablet_pad_group *group;
+ size_t index;
+
+ /* per frame */
+ enum wlr_tablet_pad_ring_source source;
+ double angle;
+ bool stopped;
+};
+
+struct wlr_wl_tablet_pad_strip {
+ struct wl_list link; // wlr_wl_tablet_pad_group::strips
+ struct zwp_tablet_pad_strip_v2 *strip;
+ struct wlr_wl_tablet_pad_group *group;
+ size_t index;
+
+ enum wlr_tablet_pad_strip_source source;
+ double position;
+ bool stopped;
+};
+
+struct wlr_wl_tablet_pad_group {
+ struct zwp_tablet_pad_group_v2 *pad_group;
+ struct wlr_tablet_pad *pad;
+ unsigned int mode;
+
+ struct wlr_tablet_pad_group group;
+
+ struct wl_list rings; // wlr_wl_tablet_pad_ring::link
+ struct wl_list strips; // wlr_wl_tablet_pad_strips::link
+};
+
+static uint32_t get_current_time_msec(void) {
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ return now.tv_nsec / (1000 * 1000) + now.tv_sec * 1000;
+}
+
+struct wlr_wl_tablet_seat *wlr_wl_add_tablet_seat(
+ struct zwp_tablet_manager_v2 *manager,
+ struct wl_seat *seat, struct wlr_wl_backend *backend);
+
+static void handle_tablet_pad_ring_source(void *data,
+ struct zwp_tablet_pad_ring_v2 *zwp_tablet_pad_ring_v2,
+ uint32_t source) {
+ struct wlr_wl_tablet_pad_ring *ring = data;
+ ring->source = source;
+}
+
+static void handle_tablet_pad_ring_angle(void *data,
+ struct zwp_tablet_pad_ring_v2 *zwp_tablet_pad_ring_v2,
+ wl_fixed_t degrees) {
+ struct wlr_wl_tablet_pad_ring *ring = data;
+ ring->angle = wl_fixed_to_double(degrees);
+}
+
+static void handle_tablet_pad_ring_stop(void *data,
+ struct zwp_tablet_pad_ring_v2 *zwp_tablet_pad_ring_v2) {
+ struct wlr_wl_tablet_pad_ring *ring = data;
+ ring->stopped = true;
+}
+
+static void handle_tablet_pad_ring_frame(void *data,
+ struct zwp_tablet_pad_ring_v2 *zwp_tablet_pad_ring_v2,
+ uint32_t time) {
+ struct wlr_wl_tablet_pad_ring *ring = data;
+
+ struct wlr_event_tablet_pad_ring evt = {
+ .time_msec = time,
+ .source = ring->source,
+ .ring = ring->index,
+ .position = ring->angle,
+ .mode = ring->group->mode,
+ };
+
+ if (ring->angle >= 0) {
+ wlr_signal_emit_safe(&ring->group->pad->events.ring, &evt);
+ }
+ if (ring->stopped) {
+ evt.position = -1;
+ wlr_signal_emit_safe(&ring->group->pad->events.ring, &evt);
+ }
+
+ ring->angle = -1;
+ ring->stopped = false;
+ ring->source = 0;
+}
+
+static const struct zwp_tablet_pad_ring_v2_listener tablet_pad_ring_listener = {
+ .source = handle_tablet_pad_ring_source,
+ .angle = handle_tablet_pad_ring_angle,
+ .stop = handle_tablet_pad_ring_stop,
+ .frame = handle_tablet_pad_ring_frame,
+};
+
+static void handle_tablet_pad_strip_source(void *data,
+ struct zwp_tablet_pad_strip_v2 *zwp_tablet_pad_strip_v2,
+ uint32_t source) {
+ struct wlr_wl_tablet_pad_strip *strip = data;
+ strip->source = source;
+}
+
+static void handle_tablet_pad_strip_position(void *data,
+ struct zwp_tablet_pad_strip_v2 *zwp_tablet_pad_strip_v2,
+ uint32_t position) {
+ struct wlr_wl_tablet_pad_strip *strip = data;
+ strip->position = (double) position / 65536.0;
+}
+
+static void handle_tablet_pad_strip_stop(void *data,
+ struct zwp_tablet_pad_strip_v2 *zwp_tablet_pad_strip_v2) {
+ struct wlr_wl_tablet_pad_strip *strip = data;
+ strip->stopped = true;
+}
+
+static void handle_tablet_pad_strip_frame(void *data,
+ struct zwp_tablet_pad_strip_v2 *zwp_tablet_pad_strip_v2,
+ uint32_t time) {
+ struct wlr_wl_tablet_pad_strip *strip = data;
+
+ struct wlr_event_tablet_pad_strip evt = {
+ .time_msec = time,
+ .source = strip->source,
+ .strip = strip->index,
+ .position = strip->position,
+ .mode = strip->group->mode,
+ };
+
+ if (strip->position >= 0) {
+ wlr_signal_emit_safe(&strip->group->pad->events.strip, &evt);
+ }
+ if (strip->stopped) {
+ evt.position = -1;
+ wlr_signal_emit_safe(&strip->group->pad->events.strip, &evt);
+ }
+
+ strip->position = -1;
+ strip->stopped = false;
+ strip->source = 0;
+}
+
+static const struct zwp_tablet_pad_strip_v2_listener tablet_pad_strip_listener = {
+ .source = handle_tablet_pad_strip_source,
+ .position = handle_tablet_pad_strip_position,
+ .stop = handle_tablet_pad_strip_stop,
+ .frame = handle_tablet_pad_strip_frame,
+};
+
+static void handle_tablet_pad_group_buttons(void *data,
+ struct zwp_tablet_pad_group_v2 *pad_group,
+ struct wl_array *buttons) {
+ struct wlr_wl_tablet_pad_group *group = data;
+
+ group->group.buttons = calloc(1, buttons->size);
+ if (!group->group.buttons) {
+ // FIXME: Add actual error handling
+ return;
+ }
+
+ group->group.button_count = buttons->size / sizeof(int);
+ memcpy(group->group.buttons, buttons->data, buttons->size);
+}
+
+static void handle_tablet_pad_group_modes(void *data,
+ struct zwp_tablet_pad_group_v2 *pad_group, uint32_t modes) {
+ struct wlr_wl_tablet_pad_group *group = data;
+
+ group->group.mode_count = modes;
+}
+
+static void handle_tablet_pad_group_ring(void *data,
+ struct zwp_tablet_pad_group_v2 *pad_group,
+ struct zwp_tablet_pad_ring_v2 *ring) {
+ struct wlr_wl_tablet_pad_group *group = data;
+ struct wlr_wl_tablet_pad_ring *tablet_ring =
+ calloc(1, sizeof(struct wlr_wl_tablet_pad_ring));
+ if (!tablet_ring) {
+ zwp_tablet_pad_ring_v2_destroy(ring);
+ return;
+ }
+ tablet_ring->index = group->pad->ring_count++;
+ tablet_ring->group = group;
+ zwp_tablet_pad_ring_v2_add_listener(ring, &tablet_pad_ring_listener,
+ tablet_ring);
+
+ group->group.rings = realloc(group->group.rings,
+ ++group->group.ring_count * sizeof(unsigned int));
+ group->group.rings[group->group.ring_count - 1] =
+ tablet_ring->index;
+}
+
+static void handle_tablet_pad_group_strip(void *data,
+ struct zwp_tablet_pad_group_v2 *pad_group,
+ struct zwp_tablet_pad_strip_v2 *strip) {
+ struct wlr_wl_tablet_pad_group *group = data;
+ struct wlr_wl_tablet_pad_strip *tablet_strip =
+ calloc(1, sizeof(struct wlr_wl_tablet_pad_strip));
+ if (!tablet_strip) {
+ zwp_tablet_pad_strip_v2_destroy(strip);
+ return;
+ }
+ tablet_strip->index = group->pad->strip_count++;
+ tablet_strip->group = group;
+ zwp_tablet_pad_strip_v2_add_listener(strip, &tablet_pad_strip_listener,
+ tablet_strip);
+
+ group->group.strips = realloc(group->group.strips,
+ ++group->group.strip_count * sizeof(unsigned int));
+ group->group.strips[group->group.strip_count - 1] =
+ tablet_strip->index;
+}
+
+static void handle_tablet_pad_group_done(void *data,
+ struct zwp_tablet_pad_group_v2 *pad_group) {
+ /* Empty for now */
+}
+
+static void handle_tablet_pad_group_mode_switch(void *data,
+ struct zwp_tablet_pad_group_v2 *pad_group,
+ uint32_t time, uint32_t serial, uint32_t mode) {
+ struct wlr_wl_tablet_pad_group *group = data;
+ group->mode = mode;
+}
+
+/* This isn't in the listener, but keep the naming scheme around since the
+ * other removed functions work like this, and pad sub-resources are just a bit
+ * special */
+static void handle_tablet_pad_group_removed(
+ struct wlr_wl_tablet_pad_group *group) {
+
+ /* No need to remove the ::link on strips rings as long as we do *not*
+ * wl_list_remove on the wl_groups ring/strip attributes here */
+ struct wlr_wl_tablet_pad_ring *ring, *tmp_ring;
+ wl_list_for_each_safe(ring, tmp_ring, &group->rings, link) {
+ zwp_tablet_pad_ring_v2_destroy(ring->ring);
+ free(ring);
+ }
+
+ struct wlr_wl_tablet_pad_strip *strip, *tmp_strip;
+ wl_list_for_each_safe(strip, tmp_strip, &group->strips, link) {
+ zwp_tablet_pad_strip_v2_destroy(strip->strip);
+ free(strip);
+ }
+
+ zwp_tablet_pad_group_v2_destroy(group->pad_group);
+
+ /* While I'm pretty sure we have control over this as well, it's
+ * outside the scope of a single function, so better be safe than
+ * sorry */
+ wl_list_remove(&group->group.link);
+ free(group);
+}
+
+static const struct zwp_tablet_pad_group_v2_listener tablet_pad_group_listener = {
+ .buttons = handle_tablet_pad_group_buttons,
+ .modes = handle_tablet_pad_group_modes,
+ .ring = handle_tablet_pad_group_ring,
+ .strip = handle_tablet_pad_group_strip,
+ .done = handle_tablet_pad_group_done,
+ .mode_switch = handle_tablet_pad_group_mode_switch,
+};
+
+static void handle_tablet_pad_group(void *data,
+ struct zwp_tablet_pad_v2 *zwp_tablet_pad,
+ struct zwp_tablet_pad_group_v2 *pad_group) {
+ struct wlr_wl_input_device *dev = data;
+ struct wlr_tablet_pad *pad = dev->wlr_input_device.tablet_pad;
+
+ struct wlr_wl_tablet_pad_group *group =
+ calloc(1, sizeof(struct wlr_wl_tablet_pad_group));
+ if (!group) {
+ zwp_tablet_pad_group_v2_destroy(pad_group);
+ return;
+ }
+ group->pad_group = pad_group;
+ group->pad = pad;
+
+ wl_list_init(&group->rings);
+ wl_list_init(&group->strips);
+
+ zwp_tablet_pad_group_v2_add_listener(pad_group,
+ &tablet_pad_group_listener, group);
+
+ wl_list_insert(&pad->groups, &group->group.link);
+}
+
+static void handle_tablet_pad_path(void *data,
+ struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2,
+ const char *path) {
+ struct wlr_wl_input_device *dev = data;
+ struct wlr_tablet_pad *tablet_pad = dev->wlr_input_device.tablet_pad;
+
+ wlr_list_push(&tablet_pad->paths, strdup(path));
+}
+
+static void handle_tablet_pad_buttons(void *data,
+ struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2,
+ uint32_t buttons) {
+ struct wlr_wl_input_device *dev = data;
+ struct wlr_tablet_pad *tablet_pad = dev->wlr_input_device.tablet_pad;
+
+ tablet_pad->button_count = buttons;
+}
+
+static void handle_tablet_pad_button(void *data,
+ struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2,
+ uint32_t time, uint32_t button, uint32_t state) {
+ struct wlr_wl_input_device *dev = data;
+ struct wlr_tablet_pad *tablet_pad = dev->wlr_input_device.tablet_pad;
+
+ struct wlr_event_tablet_pad_button evt = {
+ .time_msec = time,
+ .button = button,
+ .state = state,
+ .mode = 0,
+ .group = 0,
+ };
+
+ wlr_signal_emit_safe(&tablet_pad->events.button, &evt);
+}
+
+static void handle_tablet_pad_done(void *data,
+ struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2) {
+ struct wlr_wl_input_device *dev = data;
+
+ wlr_signal_emit_safe(&dev->backend->backend.events.new_input,
+ &dev->wlr_input_device);
+}
+
+static void handle_tablet_pad_enter(void *data,
+ struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2,
+ uint32_t serial, struct zwp_tablet_v2 *tablet_p,
+ struct wl_surface *surface) {
+ struct wlr_wl_input_device *dev = data;
+ struct wlr_tablet_pad *tablet_pad = dev->wlr_input_device.tablet_pad;
+ struct wlr_wl_input_device *tab_dev = zwp_tablet_v2_get_user_data(tablet_p);
+ struct wlr_input_device *tablet = &tab_dev->wlr_input_device;
+ wlr_log(WLR_DEBUG, "Tablet: %p\n", tablet);
+
+ wlr_signal_emit_safe(&tablet_pad->events.attach_tablet, tablet);
+}
+
+static void handle_tablet_pad_leave(void *data,
+ struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2,
+ uint32_t serial, struct wl_surface *surface) {
+ /* Empty. Probably staying that way, unless we want to create/destroy
+ * tablet on enter/leave events (ehh) */
+}
+
+static void handle_tablet_pad_removed(void *data,
+ struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2) {
+ struct wlr_wl_input_device *dev = data;
+ struct wlr_tablet_pad *tablet_pad = dev->wlr_input_device.tablet_pad;
+
+ /* This doesn't free anything, but emits the destroy signal */
+ wlr_input_device_destroy(&dev->wlr_input_device);
+ /* This is a bit ugly, but we need to remove it from our list */
+ wl_list_remove(&dev->wlr_input_device.link);
+
+ struct wlr_wl_tablet_pad_group *group, *it;
+ wl_list_for_each_safe(group, it, &tablet_pad->groups, group.link) {
+ handle_tablet_pad_group_removed(group);
+ }
+
+ /* This frees */
+ wlr_tablet_pad_destroy(tablet_pad);
+ zwp_tablet_pad_v2_destroy(dev->resource);
+ free(dev);
+}
+
+static const struct zwp_tablet_pad_v2_listener tablet_pad_listener = {
+ .group = handle_tablet_pad_group,
+ .path = handle_tablet_pad_path,
+ .buttons = handle_tablet_pad_buttons,
+ .button = handle_tablet_pad_button,
+ .done = handle_tablet_pad_done,
+ .enter = handle_tablet_pad_enter,
+ .leave = handle_tablet_pad_leave,
+ .removed = handle_tablet_pad_removed,
+};
+
+static void handle_pad_added(void *data,
+ struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2,
+ struct zwp_tablet_pad_v2 *id) {
+ wlr_log(WLR_DEBUG, "New tablet pad");
+ struct wlr_wl_backend *backend = data;
+ struct wlr_wl_input_device *dev = create_wl_input_device(
+ backend, WLR_INPUT_DEVICE_TABLET_PAD);
+ if (!dev) {
+ /* This leaks a couple of server-sent resource ids. iirc this
+ * shouldn't ever be a problem, but it isn't exactly nice
+ * either. */
+ zwp_tablet_pad_v2_destroy(id);
+ return;
+ }
+
+ dev->resource = id;
+ struct wlr_input_device *wlr_dev = &dev->wlr_input_device;
+ wlr_dev->tablet_pad = calloc(1, sizeof(*wlr_dev->tablet_pad));
+
+ if (!wlr_dev->tablet_pad) {
+ /* This leaks a couple of server-sent resource ids. iirc this
+ * shouldn't ever be a problem, but it isn't exactly nice
+ * either. */
+ free(dev);
+ zwp_tablet_pad_v2_destroy(id);
+ return;
+ }
+ wlr_tablet_pad_init(wlr_dev->tablet_pad, NULL);
+ zwp_tablet_pad_v2_add_listener(id, &tablet_pad_listener, dev);
+}
+
+static void handle_tablet_tool_done(void *data,
+ struct zwp_tablet_tool_v2 *id) {
+ /* empty */
+}
+static enum wlr_tablet_tool_type tablet_type_to_wlr_type(enum zwp_tablet_tool_v2_type type) {
+
+ switch(type) {
+ case ZWP_TABLET_TOOL_V2_TYPE_PEN:
+ return WLR_TABLET_TOOL_TYPE_PEN;
+ case ZWP_TABLET_TOOL_V2_TYPE_ERASER:
+ return WLR_TABLET_TOOL_TYPE_ERASER;
+ case ZWP_TABLET_TOOL_V2_TYPE_BRUSH:
+ return WLR_TABLET_TOOL_TYPE_BRUSH;
+ case ZWP_TABLET_TOOL_V2_TYPE_PENCIL:
+ return WLR_TABLET_TOOL_TYPE_PENCIL;
+ case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH:
+ return WLR_TABLET_TOOL_TYPE_AIRBRUSH;
+ case ZWP_TABLET_TOOL_V2_TYPE_MOUSE:
+ return WLR_TABLET_TOOL_TYPE_MOUSE;
+ case ZWP_TABLET_TOOL_V2_TYPE_LENS:
+ return WLR_TABLET_TOOL_TYPE_LENS;
+ default:
+ break;
+ }
+
+ assert(false && "Unreachable");
+}
+
+static void handle_tablet_tool_type(void *data,
+ struct zwp_tablet_tool_v2 *id,
+ uint32_t tool_type) {
+ struct wlr_wl_tablet_tool *tool = data;
+
+ tool->wlr_tool.type = tablet_type_to_wlr_type(tool_type);
+}
+
+static void handle_tablet_tool_serial(void *data,
+ struct zwp_tablet_tool_v2 *id,
+ uint32_t high, uint32_t low) {
+ struct wlr_wl_tablet_tool *tool = data;
+
+ tool->wlr_tool.hardware_serial =
+ ((uint64_t) high) << 32 | (uint64_t) low;
+}
+
+static void handle_tablet_tool_id_wacom(void *data,
+ struct zwp_tablet_tool_v2 *id,
+ uint32_t high, uint32_t low) {
+ struct wlr_wl_tablet_tool *tool = data;
+
+ tool->wlr_tool.hardware_wacom =
+ ((uint64_t) high) << 32 | (uint64_t) low;
+}
+
+static void handle_tablet_tool_capability(void *data,
+ struct zwp_tablet_tool_v2 *id,
+ uint32_t capability) {
+ struct wlr_wl_tablet_tool *tool = data;
+
+ enum zwp_tablet_tool_v2_capability cap = capability;
+
+ switch(cap) {
+ case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT:
+ tool->wlr_tool.tilt = true;
+ break;
+ case ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE:
+ tool->wlr_tool.pressure = true;
+ break;
+ case ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE:
+ tool->wlr_tool.distance = true;
+ break;
+ case ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION:
+ tool->wlr_tool.rotation = true;
+ break;
+ case ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER:
+ tool->wlr_tool.slider = true;
+ break;
+ case ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL:
+ tool->wlr_tool.wheel = true;
+ break;
+ }
+}
+
+static void handle_tablet_tool_proximity_in(void *data,
+ struct zwp_tablet_tool_v2 *id, uint32_t serial,
+ struct zwp_tablet_v2 *tablet_id,
+ struct wl_surface *surface) {
+ struct wlr_wl_tablet_tool *tool = data;
+ tool->is_in = true;
+ tool->tablet = zwp_tablet_v2_get_user_data(tablet_id);
+}
+
+static void handle_tablet_tool_proximity_out(void *data,
+ struct zwp_tablet_tool_v2 *id) {
+ struct wlr_wl_tablet_tool *tool = data;
+ tool->is_out = true;
+}
+
+static void handle_tablet_tool_down(void *data,
+ struct zwp_tablet_tool_v2 *id,
+ unsigned int serial) {
+ struct wlr_wl_tablet_tool *tool = data;
+ tool->is_down = true;
+}
+
+static void handle_tablet_tool_up(void *data,
+ struct zwp_tablet_tool_v2 *id) {
+ struct wlr_wl_tablet_tool *tool = data;
+ tool->is_up = true;
+}
+
+static void handle_tablet_tool_motion(void *data,
+ struct zwp_tablet_tool_v2 *id,
+ wl_fixed_t x, wl_fixed_t y) {
+ struct wlr_wl_tablet_tool *tool = data;
+ tool->x = wl_fixed_to_double(x);
+ tool->y = wl_fixed_to_double(y);
+}
+
+static void handle_tablet_tool_pressure(void *data,
+ struct zwp_tablet_tool_v2 *id,
+ uint32_t pressure) {
+ struct wlr_wl_tablet_tool *tool = data;
+ tool->pressure = (double) pressure / 65535.0;
+}
+
+static void handle_tablet_tool_distance(void *data,
+ struct zwp_tablet_tool_v2 *id,
+ uint32_t distance) {
+ struct wlr_wl_tablet_tool *tool = data;
+ tool->distance = (double) distance / 65535.0;
+}
+
+static void handle_tablet_tool_tilt(void *data,
+ struct zwp_tablet_tool_v2 *id,
+ wl_fixed_t x, wl_fixed_t y) {
+ struct wlr_wl_tablet_tool *tool = data;
+ tool->tilt_x = wl_fixed_to_double(x);
+ tool->tilt_y = wl_fixed_to_double(y);
+}
+
+static void handle_tablet_tool_rotation(void *data,
+ struct zwp_tablet_tool_v2 *id,
+ wl_fixed_t rotation) {
+ struct wlr_wl_tablet_tool *tool = data;
+ tool->rotation = wl_fixed_to_double(rotation);
+}
+
+static void handle_tablet_tool_slider(void *data,
+ struct zwp_tablet_tool_v2 *id,
+ int slider) {
+ struct wlr_wl_tablet_tool *tool = data;
+ tool->slider = (double) slider / 65535.0;;
+}
+
+// TODO: This looks wrong :/
+static void handle_tablet_tool_wheel(void *data,
+ struct zwp_tablet_tool_v2 *id,
+ wl_fixed_t degree, int clicks) {
+ struct wlr_wl_tablet_tool *tool = data;
+ tool->wheel_delta = wl_fixed_to_double(degree);
+}
+
+static void handle_tablet_tool_button(void *data,
+ struct zwp_tablet_tool_v2 *id,
+ uint32_t serial, uint32_t button, uint32_t state) {
+ struct wlr_wl_tablet_tool *tool = data;
+ struct wlr_tablet *tablet = tool->tablet->wlr_input_device.tablet;
+
+ struct wlr_event_tablet_tool_button evt = {
+ .device = &tool->tablet->wlr_input_device,
+ .tool = &tool->wlr_tool,
+ .time_msec = get_current_time_msec(),
+ .button = button,
+ .state = state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_RELEASED ?
+ WLR_BUTTON_RELEASED : WLR_BUTTON_PRESSED,
+ };
+
+ wlr_signal_emit_safe(&tablet->events.button, &evt);
+}
+
+static void clear_tablet_tool_values(struct wlr_wl_tablet_tool *tool) {
+ tool->is_out = tool->is_in = false;
+ tool->is_up = tool->is_down = false;
+ tool->x = tool->y = NAN;
+ tool->pressure = NAN;
+ tool->distance = NAN;
+ tool->tilt_x = tool->tilt_y = NAN;
+ tool->rotation = NAN;
+ tool->slider = NAN;
+ tool->wheel_delta = NAN;
+}
+
+static void handle_tablet_tool_frame(void *data,
+ struct zwp_tablet_tool_v2 *id,
+ uint32_t time) {
+ struct wlr_wl_tablet_tool *tool = data;
+ if (tool->is_out && tool->is_in) {
+ /* we got a tablet tool coming in and out of proximity before
+ * we could process it. Just ignore anything it did */
+ goto clear_values;
+ }
+ struct wlr_tablet *tablet = tool->tablet->wlr_input_device.tablet;
+
+ if (tool->is_in) {
+ struct wlr_event_tablet_tool_proximity evt = {
+ .device = &tool->tablet->wlr_input_device,
+ .tool = &tool->wlr_tool,
+ .time_msec = time,
+ .x = tool->x,
+ .y = tool->y,
+ .state = WLR_TABLET_TOOL_PROXIMITY_IN,
+ };
+
+ wlr_signal_emit_safe(&tablet->events.proximity, &evt);
+ }
+
+ {
+ struct wlr_event_tablet_tool_axis evt = {
+ .device = &tool->tablet->wlr_input_device,
+ .tool = &tool->wlr_tool,
+ .time_msec = time,
+ .updated_axes = 0,
+ };
+
+ if (!isnan(tool->x) && !tool->is_in) {
+ evt.updated_axes |= WLR_TABLET_TOOL_AXIS_X;
+ evt.x = tool->x;
+ }
+
+ if (!isnan(tool->y) && !tool->is_in) {
+ evt.updated_axes |= WLR_TABLET_TOOL_AXIS_Y;
+ evt.y = tool->y;
+ }
+
+ if (!isnan(tool->pressure)) {
+ evt.updated_axes |= WLR_TABLET_TOOL_AXIS_PRESSURE;
+ evt.pressure = tool->pressure;
+ }
+
+ if (!isnan(tool->distance)) {
+ evt.updated_axes |= WLR_TABLET_TOOL_AXIS_DISTANCE;
+ evt.distance = tool->distance;
+ }
+
+ if (!isnan(tool->tilt_x)) {
+ evt.updated_axes |= WLR_TABLET_TOOL_AXIS_TILT_X;
+ evt.tilt_x = tool->tilt_x;
+ }
+
+ if (!isnan(tool->tilt_y)) {
+ evt.updated_axes |= WLR_TABLET_TOOL_AXIS_TILT_Y;
+ evt.tilt_y = tool->tilt_y;
+ }
+
+ if (!isnan(tool->rotation)) {
+ evt.updated_axes |= WLR_TABLET_TOOL_AXIS_ROTATION;
+ evt.rotation = tool->rotation;
+ }
+
+ if (!isnan(tool->slider)) {
+ evt.updated_axes |= WLR_TABLET_TOOL_AXIS_SLIDER;
+ evt.slider = tool->slider;
+ }
+
+ if (!isnan(tool->wheel_delta)) {
+ evt.updated_axes |= WLR_TABLET_TOOL_AXIS_WHEEL;
+ evt.wheel_delta = tool->wheel_delta;
+ }
+
+ if (evt.updated_axes) {
+ wlr_signal_emit_safe(&tablet->events.proximity, &evt);
+ }
+ }
+
+ /* This will always send down then up if we got both.
+ * Maybe we should send them right away, in case we get up then both in
+ * series?
+ * Downside: Here we have the frame time, if we sent right away, we
+ * need to generate the time */
+ if (tool->is_down) {
+ struct wlr_event_tablet_tool_tip evt = {
+ .device = &tool->tablet->wlr_input_device,
+ .tool = &tool->wlr_tool,
+ .time_msec = time,
+ .x = tool->x,
+ .y = tool->y,
+ .state = WLR_TABLET_TOOL_TIP_DOWN,
+ };
+
+ wlr_signal_emit_safe(&tablet->events.tip, &evt);
+ }
+
+ if (tool->is_up) {
+ struct wlr_event_tablet_tool_tip evt = {
+ .device = &tool->tablet->wlr_input_device,
+ .tool = &tool->wlr_tool,
+ .time_msec = time,
+ .x = tool->x,
+ .y = tool->y,
+ .state = WLR_TABLET_TOOL_TIP_UP,
+ };
+
+ wlr_signal_emit_safe(&tablet->events.tip, &evt);
+ }
+
+ if (tool->is_out) {
+ struct wlr_event_tablet_tool_proximity evt = {
+ .device = &tool->tablet->wlr_input_device,
+ .tool = &tool->wlr_tool,
+ .time_msec = time,
+ .x = tool->x,
+ .y = tool->y,
+ .state = WLR_TABLET_TOOL_PROXIMITY_OUT,
+ };
+
+ wlr_signal_emit_safe(&tablet->events.proximity, &evt);
+ }
+
+clear_values:
+ clear_tablet_tool_values(tool);
+}
+
+static void handle_tablet_tool_removed(void *data,
+ struct zwp_tablet_tool_v2 *id) {
+ struct wlr_wl_tablet_tool *tool = data;
+
+ zwp_tablet_tool_v2_destroy(tool->tool);
+ wlr_signal_emit_safe(&tool->wlr_tool.events.destroy, &tool->wlr_tool);
+ free(tool);
+}
+
+static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = {
+ .removed = handle_tablet_tool_removed,
+ .done = handle_tablet_tool_done,
+ .type = handle_tablet_tool_type,
+ .hardware_serial = handle_tablet_tool_serial,
+ .hardware_id_wacom = handle_tablet_tool_id_wacom,
+ .capability = handle_tablet_tool_capability,
+
+ .proximity_in = handle_tablet_tool_proximity_in,
+ .proximity_out = handle_tablet_tool_proximity_out,
+ .down = handle_tablet_tool_down,
+ .up = handle_tablet_tool_up,
+
+ .motion = handle_tablet_tool_motion,
+ .pressure = handle_tablet_tool_pressure,
+ .distance = handle_tablet_tool_distance,
+ .tilt = handle_tablet_tool_tilt,
+ .rotation = handle_tablet_tool_rotation,
+ .slider = handle_tablet_tool_slider,
+ .wheel = handle_tablet_tool_wheel,
+ .button = handle_tablet_tool_button,
+ .frame = handle_tablet_tool_frame,
+};
+
+static void handle_tool_added(void *data,
+ struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2,
+ struct zwp_tablet_tool_v2 *id) {
+ wlr_log(WLR_DEBUG, "New tablet tool");
+ struct wlr_wl_tablet_tool *tool = calloc(1, sizeof(*tool));
+ if (!tool) {
+ zwp_tablet_tool_v2_destroy(id);
+ return;
+ }
+ tool->tool = id;
+ clear_tablet_tool_values(tool);
+ wl_signal_init(&tool->wlr_tool.events.destroy);
+ zwp_tablet_tool_v2_add_listener(id, &tablet_tool_listener, tool);
+}
+
+static void handle_tablet_name(void *data, struct zwp_tablet_v2 *zwp_tablet_v2,
+ const char *name) {
+ struct wlr_wl_input_device *dev = data;
+ struct wlr_tablet *tablet = dev->wlr_input_device.tablet;
+ tablet->name = strdup(name);
+}
+
+static void handle_tablet_id(void *data, struct zwp_tablet_v2 *zwp_tablet_v2,
+ uint32_t vid, uint32_t pid) {
+ struct wlr_wl_input_device *dev = data;
+ dev->wlr_input_device.vendor = vid;
+ dev->wlr_input_device.product = pid;
+}
+
+static void handle_tablet_path(void *data, struct zwp_tablet_v2 *zwp_tablet_v2,
+ const char *path) {
+ struct wlr_wl_input_device *dev = data;
+ struct wlr_tablet *tablet = dev->wlr_input_device.tablet;
+
+ wlr_list_push(&tablet->paths, strdup(path));
+}
+
+static void handle_tablet_done(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) {
+ struct wlr_wl_input_device *dev = data;
+
+ wlr_signal_emit_safe(&dev->backend->backend.events.new_input,
+ &dev->wlr_input_device);
+}
+
+static void handle_tablet_removed(void *data,
+ struct zwp_tablet_v2 *zwp_tablet_v2) {
+ struct wlr_wl_input_device *dev = data;
+
+ /* This doesn't free anything, but emits the destroy signal */
+ wlr_input_device_destroy(&dev->wlr_input_device);
+ /* This is a bit ugly, but we need to remove it from our list */
+ wl_list_remove(&dev->wlr_input_device.link);
+
+ zwp_tablet_v2_destroy(dev->resource);
+ free(dev);
+}
+
+static const struct zwp_tablet_v2_listener tablet_listener = {
+ .name = handle_tablet_name,
+ .id = handle_tablet_id,
+ .path = handle_tablet_path,
+ .done = handle_tablet_done,
+ .removed = handle_tablet_removed,
+};
+
+static void handle_tab_added(void *data,
+ struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2,
+ struct zwp_tablet_v2 *id) {
+ wlr_log(WLR_DEBUG, "New tablet");
+ struct wlr_wl_backend *backend = data;
+ struct wlr_wl_input_device *dev = create_wl_input_device(
+ backend, WLR_INPUT_DEVICE_TABLET_TOOL);
+ dev->resource = id;
+
+ if (!dev) {
+ zwp_tablet_v2_destroy(id);
+ return;
+ }
+ struct wlr_input_device *wlr_dev = &dev->wlr_input_device;
+ wlr_dev->tablet = calloc(1, sizeof(*wlr_dev->tablet));
+
+ if (!wlr_dev->tablet) {
+ zwp_tablet_v2_destroy(id);
+ return;
+ }
+ zwp_tablet_v2_set_user_data(id, wlr_dev->tablet);
+ wlr_tablet_init(wlr_dev->tablet, NULL);
+ zwp_tablet_v2_add_listener(id, &tablet_listener, dev);
+}
+
+static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = {
+ .tablet_added = handle_tab_added,
+ .tool_added = handle_tool_added,
+ .pad_added = handle_pad_added,
+};
+
+struct wlr_wl_tablet_seat *wlr_wl_add_tablet_seat(
+ struct zwp_tablet_manager_v2 *manager,
+ struct wl_seat *seat, struct wlr_wl_backend *backend) {
+ struct wlr_wl_tablet_seat *ret =
+ calloc(1, sizeof(struct wlr_wl_tablet_seat));
+
+ if (!(ret->tablet_seat =
+ zwp_tablet_manager_v2_get_tablet_seat(manager, seat))) {
+ free(ret);
+ return NULL;
+ }
+
+ zwp_tablet_seat_v2_add_listener(ret->tablet_seat,
+ &tablet_seat_listener, backend);
+
+ wl_list_init(&ret->tablets);
+ wl_list_init(&ret->tools);
+ //wl_list_init(&ret->pads);
+
+ return ret;
+}
diff --git a/backend/wayland/wl_seat.c b/backend/wayland/wl_seat.c
index 96c083ed..dd498153 100644
--- a/backend/wayland/wl_seat.c
+++ b/backend/wayland/wl_seat.c
@@ -303,7 +303,7 @@ bool wlr_input_device_is_wl(struct wlr_input_device *dev) {
return dev->impl == &input_device_impl;
}
-static struct wlr_wl_input_device *create_wl_input_device(
+struct wlr_wl_input_device *create_wl_input_device(
struct wlr_wl_backend *backend, enum wlr_input_device_type type) {
struct wlr_wl_input_device *dev =
calloc(1, sizeof(struct wlr_wl_input_device));
diff --git a/include/backend/wayland.h b/include/backend/wayland.h
index e25aba43..f46e6c75 100644
--- a/include/backend/wayland.h
+++ b/include/backend/wayland.h
@@ -38,6 +38,7 @@ struct wlr_wl_backend {
struct wl_pointer *pointer;
struct wl_keyboard *keyboard;
struct wlr_wl_pointer *current_pointer;
+ struct zwp_tablet_manager_v2 *tablet_manager;
char *seat_name;
};
@@ -92,6 +93,8 @@ void update_wl_output_cursor(struct wlr_wl_output *output);
struct wlr_wl_pointer *pointer_get_wl(struct wlr_pointer *wlr_pointer);
void create_wl_pointer(struct wl_pointer *wl_pointer, struct wlr_wl_output *output);
void create_wl_keyboard(struct wl_keyboard *wl_keyboard, struct wlr_wl_backend *wl);
+struct wlr_wl_input_device *create_wl_input_device(
+ struct wlr_wl_backend *backend, enum wlr_input_device_type type);
extern const struct wl_seat_listener seat_listener;
diff --git a/protocol/meson.build b/protocol/meson.build
index b7a0241e..25694010 100644
--- a/protocol/meson.build
+++ b/protocol/meson.build
@@ -46,6 +46,7 @@ client_protocols = [
[wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'],
[wl_protocol_dir, 'unstable/pointer-gestures/pointer-gestures-unstable-v1.xml'],
[wl_protocol_dir, 'unstable/relative-pointer/relative-pointer-unstable-v1.xml'],
+ [wl_protocol_dir, 'unstable/tablet/tablet-unstable-v2.xml'],
[wl_protocol_dir, 'unstable/text-input/text-input-unstable-v3.xml'],
[wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'],
[wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'],