From 7e420cb6e4a334dea7296060820de12a768b76da Mon Sep 17 00:00:00 2001 From: John Chadwick Date: Tue, 17 Sep 2019 21:46:29 -0700 Subject: [PATCH] input: Add support for tablet protocol. Sway has basic support for drawing tablets, but does not expose properties such as pressure sensitivity. This implements the wlr tablet v2 protocol, providing tablet events to Wayland clients. --- include/sway/input/cursor.h | 3 + include/sway/input/seat.h | 2 + include/sway/input/tablet.h | 62 +++++++ include/sway/server.h | 2 + protocols/meson.build | 1 + sway/input/cursor.c | 242 ++++++++++++++++++++----- sway/input/seat.c | 29 ++- sway/input/tablet.c | 345 ++++++++++++++++++++++++++++++++++++ sway/meson.build | 1 + sway/server.c | 3 + 10 files changed, 644 insertions(+), 46 deletions(-) create mode 100644 include/sway/input/tablet.h create mode 100644 sway/input/tablet.c 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 + +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 #include #include +#include #include #include #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 #include #include +#include #include #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 +#include +#include +#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 #include #include +#include #include #include #include @@ -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(