labwc/src/input/tablet.c

430 lines
12 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-only
#include <assert.h>
#include <stdlib.h>
#include <linux/input-event-codes.h>
#include <wlr/types/wlr_tablet_pad.h>
#include <wlr/types/wlr_tablet_tool.h>
#include <wlr/util/log.h>
#include <wlr/types/wlr_scene.h>
#include "common/macros.h"
#include "common/mem.h"
#include "common/scene-helpers.h"
#include "config/rcxml.h"
#include "config/mousebind.h"
#include "input/cursor.h"
2023-12-29 10:10:41 +01:00
#include "input/tablet.h"
#include "input/tablet-tool.h"
2024-04-20 11:40:23 +02:00
#include "labwc.h"
#include "idle.h"
#include "action.h"
static bool
tool_supports_absolute_motion(struct wlr_tablet_tool *tool)
{
switch (tool->type) {
case WLR_TABLET_TOOL_TYPE_MOUSE:
case WLR_TABLET_TOOL_TYPE_LENS:
return false;
default:
return true;
}
}
static void
adjust_for_tablet_area(double tablet_width, double tablet_height,
struct wlr_fbox box, double *x, double *y)
{
if ((!box.x && !box.y && !box.width && !box.height)
|| !tablet_width || !tablet_height) {
return;
}
if (!box.width) {
box.width = tablet_width - box.x;
}
if (!box.height) {
box.height = tablet_height - box.y;
}
if (box.x + box.width <= tablet_width) {
const double max_x = 1;
double width_offset = max_x * box.x / tablet_width;
*x = (*x - width_offset) * tablet_width / box.width;
}
if (box.y + box.height <= tablet_height) {
const double max_y = 1;
double height_offset = max_y * box.y / tablet_height;
*y = (*y - height_offset) * tablet_height / box.height;
}
}
static void
adjust_for_rotation(enum rotation rotation, double *x, double *y)
{
double tmp;
switch (rotation) {
case LAB_ROTATE_NONE:
break;
case LAB_ROTATE_90:
tmp = *x;
*x = 1.0 - *y;
*y = tmp;
break;
case LAB_ROTATE_180:
*x = 1.0 - *x;
*y = 1.0 - *y;
break;
case LAB_ROTATE_270:
tmp = *x;
*x = *y;
*y = 1.0 - tmp;
break;
}
}
static struct wlr_surface*
tablet_get_coords(struct drawing_tablet *tablet, double *x, double *y)
{
*x = tablet->x;
*y = tablet->y;
adjust_for_tablet_area(tablet->tablet->width_mm, tablet->tablet->height_mm,
rc.tablet.box, x, y);
adjust_for_rotation(rc.tablet.rotation, x, y);
if (rc.tablet.force_mouse_emulation
|| !tablet->tablet_v2) {
return NULL;
}
/* Convert coordinates: first [0, 1] => layout, then layout => surface */
double lx, ly;
wlr_cursor_absolute_to_layout_coords(tablet->seat->cursor,
tablet->wlr_input_device, *x, *y, &lx, &ly);
double sx, sy;
struct wlr_scene_node *node =
wlr_scene_node_at(&tablet->seat->server->scene->tree.node, lx, ly, &sx, &sy);
/* Find the surface and return it if it accepts tablet events */
struct wlr_surface *surface = lab_wlr_surface_from_node(node);
if (surface && !wlr_surface_accepts_tablet_v2(tablet->tablet_v2, surface)) {
return NULL;
}
return surface;
}
static void
notify_motion(struct drawing_tablet *tablet, struct drawing_tablet_tool *tool,
struct wlr_surface *surface, double x, double y, uint32_t time)
{
idle_manager_notify_activity(tool->seat->seat);
if (surface != tool->tool_v2->focused_surface) {
wlr_tablet_v2_tablet_tool_notify_proximity_in(tool->tool_v2,
tablet->tablet_v2, surface);
}
wlr_cursor_warp_absolute(tablet->seat->cursor,
tablet->wlr_input_device, x, y);
double sx, sy;
bool notify = cursor_process_motion(tablet->seat->server, time, &sx, &sy);
if (notify) {
wlr_tablet_v2_tablet_tool_notify_motion(tool->tool_v2, sx, sy);
}
}
static void
handle_proximity(struct wl_listener *listener, void *data)
{
struct wlr_tablet_tool_proximity_event *ev = data;
struct drawing_tablet *tablet = ev->tablet->data;
struct drawing_tablet_tool *tool = ev->tool->data;
if (!tool_supports_absolute_motion(ev->tool)) {
if (ev->state == WLR_TABLET_TOOL_PROXIMITY_IN) {
wlr_log(WLR_INFO, "ignoring not supporting tablet tool");
}
return;
}
tablet->x = ev->x;
tablet->y = ev->y;
double x, y;
struct wlr_surface *surface = tablet_get_coords(tablet, &x, &y);
if (!rc.tablet.force_mouse_emulation
&& tablet->seat->server->tablet_manager && !tool) {
/*
* Unfortunately `wlr_tool` is only present in the events, so
* use proximity for creating a `wlr_tablet_v2_tablet_tool`.
*/
tablet_tool_init(tablet->seat, ev->tool);
}
if (tool && surface) {
if (tool->tool_v2 && ev->state == WLR_TABLET_TOOL_PROXIMITY_IN) {
notify_motion(tablet, tool, surface, x, y, ev->time_msec);
}
if (tool->tool_v2 && ev->state == WLR_TABLET_TOOL_PROXIMITY_OUT) {
wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tool_v2);
}
}
}
static void
handle_axis(struct wl_listener *listener, void *data)
{
struct wlr_tablet_tool_axis_event *ev = data;
struct drawing_tablet *tablet = ev->tablet->data;
struct drawing_tablet_tool *tool = ev->tool->data;
if (!tool_supports_absolute_motion(ev->tool)) {
return;
}
if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_X) {
tablet->x = ev->x;
}
if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_Y) {
tablet->y = ev->y;
}
if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X) {
tablet->tilt_x = ev->tilt_x;
}
if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y) {
tablet->tilt_y = ev->tilt_y;
}
double x, y;
struct wlr_surface *surface = tablet_get_coords(tablet, &x, &y);
if (tool && ((surface
&& tablet->seat->server->input_mode == LAB_INPUT_STATE_PASSTHROUGH)
|| wlr_tablet_tool_v2_has_implicit_grab(tool->tool_v2))) {
if (ev->updated_axes & (WLR_TABLET_TOOL_AXIS_X | WLR_TABLET_TOOL_AXIS_Y)) {
notify_motion(tablet, tool, surface, x, y, ev->time_msec);
}
if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) {
wlr_tablet_v2_tablet_tool_notify_distance(tool->tool_v2,
ev->distance);
}
if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) {
wlr_tablet_v2_tablet_tool_notify_pressure(tool->tool_v2,
ev->pressure);
}
if (ev->updated_axes
& (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) {
/*
* From https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/stable/tablet/tablet-v2.xml
* "Other extra axes are in physical units as specified in the protocol.
* The current extra axes with physical units are tilt, rotation and
* wheel rotation.
* Sent whenever one or both of the tilt axes on a tool change. Each tilt
* value is in degrees, relative to the z-axis of the tablet.
* The angle is positive when the top of a tool tilts along the
* positive x or y axis."
* Based on that we only need to apply rotation but no area transformation.
*/
double tilt_x = tablet->tilt_x;
double tilt_y = tablet->tilt_y;
adjust_for_rotation(rc.tablet.rotation, &tilt_x, &tilt_y);
wlr_tablet_v2_tablet_tool_notify_tilt(tool->tool_v2,
tilt_x, tilt_y);
}
if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) {
wlr_tablet_v2_tablet_tool_notify_rotation(tool->tool_v2,
ev->rotation);
}
if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) {
wlr_tablet_v2_tablet_tool_notify_slider(tool->tool_v2,
ev->slider);
}
if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) {
wlr_tablet_v2_tablet_tool_notify_wheel(tool->tool_v2,
ev->wheel_delta, 0);
}
} else {
if (ev->updated_axes & (WLR_TABLET_TOOL_AXIS_X | WLR_TABLET_TOOL_AXIS_Y)) {
if (tool && tool->tool_v2->focused_surface) {
wlr_tablet_v2_tablet_tool_notify_proximity_out(
tool->tool_v2);
}
cursor_emulate_move_absolute(tablet->seat, &ev->tablet->base,
x, y, ev->time_msec);
}
}
}
static uint32_t
to_stylus_button(uint32_t button)
{
/*
* Use the mapping that is used by XWayland and GTK, even
* if it isn't the order one would expect. This is also
* consistent with the mapping for mouse emulation
*/
switch (button) {
case BTN_LEFT:
return BTN_TOOL_PEN;
case BTN_RIGHT:
return BTN_STYLUS2;
case BTN_MIDDLE:
return BTN_STYLUS;
case BTN_SIDE:
return BTN_STYLUS3;
default:
return 0;
}
}
static void
handle_tip(struct wl_listener *listener, void *data)
{
struct wlr_tablet_tool_tip_event *ev = data;
struct drawing_tablet *tablet = ev->tablet->data;
struct drawing_tablet_tool *tool = ev->tool->data;
double x, y;
struct wlr_surface *surface = tablet_get_coords(tablet, &x, &y);
uint32_t button = tablet_get_mapped_button(BTN_TOOL_PEN);
if (tool && (surface || wlr_tablet_tool_v2_has_implicit_grab(tool->tool_v2))) {
idle_manager_notify_activity(tool->seat->seat);
uint32_t stylus_button = to_stylus_button(button);
if (stylus_button != BTN_TOOL_PEN) {
wlr_log(WLR_INFO, "ignoring stylus tool pen mapping for tablet mode");
}
if (ev->state == WLR_TABLET_TOOL_TIP_DOWN) {
bool notify = cursor_process_button_press(tool->seat, BTN_LEFT,
ev->time_msec);
if (notify) {
wlr_tablet_v2_tablet_tool_notify_down(tool->tool_v2);
wlr_tablet_tool_v2_start_implicit_grab(tool->tool_v2);
}
} else if (ev->state == WLR_TABLET_TOOL_TIP_UP) {
bool notify = cursor_process_button_release(tool->seat, BTN_LEFT,
ev->time_msec);
if (notify) {
wlr_tablet_v2_tablet_tool_notify_up(tool->tool_v2);
}
bool exit_interactive = cursor_finish_button_release(tool->seat);
if (exit_interactive && tool->tool_v2->focused_surface) {
/*
* Re-enter the surface after a resize/move to ensure
* being back in tablet mode.
*/
wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tool_v2);
wlr_tablet_v2_tablet_tool_notify_proximity_in(tool->tool_v2,
tablet->tablet_v2, surface);
}
if (!surface) {
/* Out-of-surface movement ended on the desktop */
wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tool_v2);
}
}
} else {
if (button) {
cursor_emulate_button(tablet->seat,
button,
ev->state == WLR_TABLET_TOOL_TIP_DOWN
? WLR_BUTTON_PRESSED
: WLR_BUTTON_RELEASED,
ev->time_msec);
}
}
}
static void
handle_button(struct wl_listener *listener, void *data)
{
struct wlr_tablet_tool_button_event *ev = data;
struct drawing_tablet *tablet = ev->tablet->data;
struct drawing_tablet_tool *tool = ev->tool->data;
double x, y;
struct wlr_surface *surface = tablet_get_coords(tablet, &x, &y);
uint32_t button = tablet_get_mapped_button(ev->button);
if (tool && surface) {
idle_manager_notify_activity(tool->seat->seat);
if (button) {
struct view *view = view_from_wlr_surface(surface);
struct mousebind *mousebind;
wl_list_for_each(mousebind, &rc.mousebinds, link) {
if (mousebind->mouse_event == MOUSE_ACTION_PRESS
&& mousebind->button == button
&& mousebind->context == LAB_SSD_CLIENT) {
actions_run(view, tool->seat->server,
&mousebind->actions, 0);
}
}
}
uint32_t stylus_button = to_stylus_button(button);
if (stylus_button && stylus_button != BTN_TOOL_PEN) {
wlr_tablet_v2_tablet_tool_notify_button(tool->tool_v2,
stylus_button,
ev->state == WLR_BUTTON_PRESSED
? ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED
: ZWP_TABLET_PAD_V2_BUTTON_STATE_RELEASED);
} else {
wlr_log(WLR_INFO, "invalid stylus button mapping for tablet mode");
}
} else {
if (button) {
cursor_emulate_button(tablet->seat, button, ev->state, ev->time_msec);
}
}
}
static void
handle_destroy(struct wl_listener *listener, void *data)
{
struct drawing_tablet *tablet =
wl_container_of(listener, tablet, handlers.destroy);
wl_list_remove(&tablet->handlers.tip.link);
wl_list_remove(&tablet->handlers.button.link);
wl_list_remove(&tablet->handlers.proximity.link);
wl_list_remove(&tablet->handlers.axis.link);
wl_list_remove(&tablet->handlers.destroy.link);
free(tablet);
}
void
tablet_init(struct seat *seat, struct wlr_input_device *wlr_device)
{
wlr_log(WLR_DEBUG, "setting up tablet");
struct drawing_tablet *tablet = znew(*tablet);
tablet->seat = seat;
2024-04-20 11:40:23 +02:00
tablet->wlr_input_device = wlr_device;
tablet->tablet = wlr_tablet_from_input_device(wlr_device);
tablet->tablet->data = tablet;
2024-04-20 11:40:23 +02:00
if (seat->server->tablet_manager) {
tablet->tablet_v2 = wlr_tablet_create(
seat->server->tablet_manager, seat->seat, wlr_device);
}
tablet->x = 0.0;
tablet->y = 0.0;
tablet->tilt_x = 0.0;
tablet->tilt_y = 0.0;
2023-12-30 19:14:11 +01:00
wlr_log(WLR_INFO, "tablet dimensions: %.2fmm x %.2fmm",
tablet->tablet->width_mm, tablet->tablet->height_mm);
CONNECT_SIGNAL(tablet->tablet, &tablet->handlers, axis);
CONNECT_SIGNAL(tablet->tablet, &tablet->handlers, proximity);
CONNECT_SIGNAL(tablet->tablet, &tablet->handlers, tip);
CONNECT_SIGNAL(tablet->tablet, &tablet->handlers, button);
CONNECT_SIGNAL(wlr_device, &tablet->handlers, destroy);
}