49a779b24d
There is not any pointer emulation for tablet tool input. This means that only clients implementing the tablet-v2 protocol will be able to process tablet tool input. Tablet pad support is TODO
266 lines
8.7 KiB
Zig
266 lines
8.7 KiB
Zig
// This file is part of river, a dynamic tiling wayland compositor.
|
|
//
|
|
// Copyright 2024 The River Developers
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 3.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
const TabletTool = @This();
|
|
|
|
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const math = std.math;
|
|
const wlr = @import("wlroots");
|
|
const wayland = @import("wayland");
|
|
const wl = wayland.server.wl;
|
|
|
|
const server = &@import("main.zig").server;
|
|
const util = @import("util.zig");
|
|
|
|
const Tablet = @import("Tablet.zig");
|
|
|
|
const log = std.log.scoped(.tablet_tool);
|
|
|
|
const Mode = union(enum) {
|
|
passthrough,
|
|
down: struct {
|
|
// Initial cursor position in layout coordinates
|
|
lx: f64,
|
|
ly: f64,
|
|
// Initial cursor position in surface-local coordinates
|
|
sx: f64,
|
|
sy: f64,
|
|
},
|
|
};
|
|
|
|
wp_tool: *wlr.TabletV2TabletTool,
|
|
|
|
wlr_cursor: *wlr.Cursor,
|
|
|
|
mode: Mode = .passthrough,
|
|
|
|
// A wlroots event may notify us of a change on one of these axes but not
|
|
// include the value of the other. We must always send both values to the
|
|
// client, which means we need to track this state.
|
|
tilt_x: f64 = 0,
|
|
tilt_y: f64 = 0,
|
|
|
|
destroy: wl.Listener(*wlr.TabletTool) = wl.Listener(*wlr.TabletTool).init(handleDestroy),
|
|
set_cursor: wl.Listener(*wlr.TabletV2TabletTool.event.SetCursor) =
|
|
wl.Listener(*wlr.TabletV2TabletTool.event.SetCursor).init(handleSetCursor),
|
|
|
|
pub fn get(wlr_seat: *wlr.Seat, wlr_tool: *wlr.TabletTool) error{OutOfMemory}!*TabletTool {
|
|
if (@as(?*TabletTool, @ptrFromInt(wlr_tool.data))) |tool| {
|
|
return tool;
|
|
} else {
|
|
return TabletTool.create(wlr_seat, wlr_tool);
|
|
}
|
|
}
|
|
|
|
fn create(wlr_seat: *wlr.Seat, wlr_tool: *wlr.TabletTool) error{OutOfMemory}!*TabletTool {
|
|
const tool = try util.gpa.create(TabletTool);
|
|
errdefer util.gpa.destroy(tool);
|
|
|
|
const wlr_cursor = try wlr.Cursor.create();
|
|
errdefer wlr_cursor.destroy();
|
|
|
|
wlr_cursor.attachOutputLayout(server.root.output_layout);
|
|
|
|
const tablet_manager = server.input_manager.tablet_manager;
|
|
tool.* = .{
|
|
.wp_tool = try tablet_manager.createTabletV2TabletTool(wlr_seat, wlr_tool),
|
|
.wlr_cursor = wlr_cursor,
|
|
};
|
|
|
|
wlr_tool.data = @intFromPtr(tool);
|
|
|
|
wlr_tool.events.destroy.add(&tool.destroy);
|
|
tool.wp_tool.events.set_cursor.add(&tool.set_cursor);
|
|
|
|
return tool;
|
|
}
|
|
|
|
fn handleDestroy(listener: *wl.Listener(*wlr.TabletTool), _: *wlr.TabletTool) void {
|
|
const tool = @fieldParentPtr(TabletTool, "destroy", listener);
|
|
|
|
tool.wp_tool.wlr_tool.data = 0;
|
|
|
|
tool.wlr_cursor.destroy();
|
|
|
|
tool.destroy.link.remove();
|
|
tool.set_cursor.link.remove();
|
|
|
|
util.gpa.destroy(tool);
|
|
}
|
|
|
|
fn handleSetCursor(
|
|
listener: *wl.Listener(*wlr.TabletV2TabletTool.event.SetCursor),
|
|
event: *wlr.TabletV2TabletTool.event.SetCursor,
|
|
) void {
|
|
const tool = @fieldParentPtr(TabletTool, "set_cursor", listener);
|
|
|
|
if (tool.wp_tool.focused_surface == null or
|
|
tool.wp_tool.focused_surface.?.resource.getClient() != event.seat_client.client)
|
|
{
|
|
log.debug("client tried to set cursor without focus", .{});
|
|
return;
|
|
}
|
|
if (event.serial != tool.wp_tool.proximity_serial) {
|
|
log.debug("focused client tried to set cursor with incorrect serial", .{});
|
|
return;
|
|
}
|
|
|
|
tool.wlr_cursor.setSurface(event.surface, event.hotspot_x, event.hotspot_y);
|
|
}
|
|
|
|
pub fn axis(tool: *TabletTool, tablet: *Tablet, event: *wlr.Tablet.event.Axis) void {
|
|
tool.wlr_cursor.attachInputDevice(tablet.device.wlr_device);
|
|
tool.wlr_cursor.mapInputToOutput(tablet.device.wlr_device, tablet.output_mapping);
|
|
|
|
if (event.updated_axes.x or event.updated_axes.y) {
|
|
// I don't own all these different types of tablet tools to test that this
|
|
// is correct for each, this is best effort from reading code/docs.
|
|
// The same goes for all the different axes events.
|
|
switch (tool.wp_tool.wlr_tool.type) {
|
|
.pen, .eraser, .brush, .pencil, .airbrush, .totem => {
|
|
tool.wlr_cursor.warpAbsolute(
|
|
tablet.device.wlr_device,
|
|
if (event.updated_axes.x) event.x else math.nan(f64),
|
|
if (event.updated_axes.y) event.y else math.nan(f64),
|
|
);
|
|
},
|
|
.lens, .mouse => {
|
|
tool.wlr_cursor.move(tablet.device.wlr_device, event.dx, event.dy);
|
|
},
|
|
}
|
|
|
|
switch (tool.mode) {
|
|
.passthrough => {
|
|
tool.passthrough(tablet);
|
|
},
|
|
.down => |data| {
|
|
tool.wp_tool.notifyMotion(
|
|
data.sx + (tool.wlr_cursor.x - data.lx),
|
|
data.sy + (tool.wlr_cursor.y - data.ly),
|
|
);
|
|
},
|
|
}
|
|
}
|
|
if (event.updated_axes.distance) {
|
|
tool.wp_tool.notifyDistance(event.distance);
|
|
}
|
|
if (event.updated_axes.pressure) {
|
|
tool.wp_tool.notifyPressure(event.pressure);
|
|
}
|
|
if (event.updated_axes.tilt_x or event.updated_axes.tilt_y) {
|
|
if (event.updated_axes.tilt_x) tool.tilt_x = event.tilt_x;
|
|
if (event.updated_axes.tilt_y) tool.tilt_y = event.tilt_y;
|
|
|
|
tool.wp_tool.notifyTilt(tool.tilt_x, tool.tilt_y);
|
|
}
|
|
if (event.updated_axes.rotation) {
|
|
tool.wp_tool.notifyRotation(event.rotation);
|
|
}
|
|
if (event.updated_axes.slider) {
|
|
tool.wp_tool.notifySlider(event.slider);
|
|
}
|
|
if (event.updated_axes.wheel) {
|
|
tool.wp_tool.notifyWheel(event.wheel_delta, 0);
|
|
}
|
|
}
|
|
|
|
pub fn proximity(tool: *TabletTool, tablet: *Tablet, event: *wlr.Tablet.event.Proximity) void {
|
|
switch (event.state) {
|
|
.in => {
|
|
tool.wlr_cursor.attachInputDevice(tablet.device.wlr_device);
|
|
tool.wlr_cursor.mapInputToOutput(tablet.device.wlr_device, tablet.output_mapping);
|
|
|
|
tool.wlr_cursor.warpAbsolute(tablet.device.wlr_device, event.x, event.y);
|
|
|
|
tool.wlr_cursor.setXcursor(tablet.device.seat.cursor.xcursor_manager, "default");
|
|
|
|
tool.passthrough(tablet);
|
|
},
|
|
.out => {
|
|
tool.wp_tool.notifyProximityOut();
|
|
tool.wlr_cursor.unsetImage();
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn tip(tool: *TabletTool, tablet: *Tablet, event: *wlr.Tablet.event.Tip) void {
|
|
switch (event.state) {
|
|
.down => {
|
|
assert(!tool.wp_tool.is_down);
|
|
|
|
tool.wp_tool.notifyDown();
|
|
|
|
if (server.root.at(tool.wlr_cursor.x, tool.wlr_cursor.y)) |result| {
|
|
if (result.surface != null) {
|
|
tool.mode = .{
|
|
.down = .{
|
|
.lx = tool.wlr_cursor.x,
|
|
.ly = tool.wlr_cursor.y,
|
|
.sx = result.sx,
|
|
.sy = result.sy,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
},
|
|
.up => {
|
|
assert(tool.wp_tool.is_down);
|
|
|
|
tool.wp_tool.notifyUp();
|
|
tool.maybeExitDown(tablet);
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn button(tool: *TabletTool, tablet: *Tablet, event: *wlr.Tablet.event.Button) void {
|
|
tool.wp_tool.notifyButton(event.button, event.state);
|
|
|
|
tool.maybeExitDown(tablet);
|
|
}
|
|
|
|
/// Exit down mode if the tool is up and there are no buttons pressed.
|
|
fn maybeExitDown(tool: *TabletTool, tablet: *Tablet) void {
|
|
if (tool.mode != .down or tool.wp_tool.is_down or tool.wp_tool.num_buttons > 0) {
|
|
return;
|
|
}
|
|
|
|
tool.mode = .passthrough;
|
|
tool.passthrough(tablet);
|
|
}
|
|
|
|
/// Send a motion event for the surface under the tablet tool's cursor if any.
|
|
/// Send a proximity_in event first if needed.
|
|
/// If there is no surface under the cursor or the surface under the cursor
|
|
/// does not support the tablet v2 protocol, send a proximity_out event.
|
|
fn passthrough(tool: *TabletTool, tablet: *Tablet) void {
|
|
if (server.root.at(tool.wlr_cursor.x, tool.wlr_cursor.y)) |result| {
|
|
if (result.data == .lock_surface) {
|
|
assert(server.lock_manager.state != .unlocked);
|
|
} else {
|
|
assert(server.lock_manager.state != .locked);
|
|
}
|
|
|
|
if (result.surface) |surface| {
|
|
tool.wp_tool.notifyProximityIn(tablet.wp_tablet, surface);
|
|
tool.wp_tool.notifyMotion(result.sx, result.sy);
|
|
return;
|
|
}
|
|
}
|
|
|
|
tool.wp_tool.notifyProximityOut();
|
|
}
|