tablet-v2: implement tablet tool support
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
This commit is contained in:
parent
ac655593f3
commit
49a779b24d
@ -95,6 +95,7 @@ pub fn build(b: *Build) !void {
|
|||||||
scanner.addSystemProtocol("unstable/pointer-gestures/pointer-gestures-unstable-v1.xml");
|
scanner.addSystemProtocol("unstable/pointer-gestures/pointer-gestures-unstable-v1.xml");
|
||||||
scanner.addSystemProtocol("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml");
|
scanner.addSystemProtocol("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml");
|
||||||
scanner.addSystemProtocol("unstable/xdg-decoration/xdg-decoration-unstable-v1.xml");
|
scanner.addSystemProtocol("unstable/xdg-decoration/xdg-decoration-unstable-v1.xml");
|
||||||
|
scanner.addSystemProtocol("unstable/tablet/tablet-unstable-v2.xml");
|
||||||
scanner.addSystemProtocol("staging/cursor-shape/cursor-shape-v1.xml");
|
scanner.addSystemProtocol("staging/cursor-shape/cursor-shape-v1.xml");
|
||||||
|
|
||||||
scanner.addCustomProtocol("protocol/river-control-unstable-v1.xml");
|
scanner.addCustomProtocol("protocol/river-control-unstable-v1.xml");
|
||||||
@ -118,6 +119,7 @@ pub fn build(b: *Build) !void {
|
|||||||
scanner.generate("xdg_wm_base", 2);
|
scanner.generate("xdg_wm_base", 2);
|
||||||
scanner.generate("zwp_pointer_gestures_v1", 3);
|
scanner.generate("zwp_pointer_gestures_v1", 3);
|
||||||
scanner.generate("zwp_pointer_constraints_v1", 1);
|
scanner.generate("zwp_pointer_constraints_v1", 1);
|
||||||
|
scanner.generate("zwp_tablet_manager_v2", 1);
|
||||||
scanner.generate("zxdg_decoration_manager_v1", 1);
|
scanner.generate("zxdg_decoration_manager_v1", 1);
|
||||||
scanner.generate("ext_session_lock_manager_v1", 1);
|
scanner.generate("ext_session_lock_manager_v1", 1);
|
||||||
scanner.generate("wp_cursor_shape_manager_v1", 1);
|
scanner.generate("wp_cursor_shape_manager_v1", 1);
|
||||||
|
2
deps/zig-wlroots
vendored
2
deps/zig-wlroots
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 4094b86f6ec705e2f3974406601ec986364e87a3
|
Subproject commit 67f9979e93129d1c2a882b8eab7bee1832ae041c
|
@ -32,12 +32,15 @@ const util = @import("util.zig");
|
|||||||
|
|
||||||
const Config = @import("Config.zig");
|
const Config = @import("Config.zig");
|
||||||
const DragIcon = @import("DragIcon.zig");
|
const DragIcon = @import("DragIcon.zig");
|
||||||
|
const InputDevice = @import("InputDevice.zig");
|
||||||
const LayerSurface = @import("LayerSurface.zig");
|
const LayerSurface = @import("LayerSurface.zig");
|
||||||
const LockSurface = @import("LockSurface.zig");
|
const LockSurface = @import("LockSurface.zig");
|
||||||
const Output = @import("Output.zig");
|
const Output = @import("Output.zig");
|
||||||
const PointerConstraint = @import("PointerConstraint.zig");
|
const PointerConstraint = @import("PointerConstraint.zig");
|
||||||
const Root = @import("Root.zig");
|
const Root = @import("Root.zig");
|
||||||
const Seat = @import("Seat.zig");
|
const Seat = @import("Seat.zig");
|
||||||
|
const Tablet = @import("Tablet.zig");
|
||||||
|
const TabletTool = @import("TabletTool.zig");
|
||||||
const View = @import("View.zig");
|
const View = @import("View.zig");
|
||||||
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
||||||
|
|
||||||
@ -177,6 +180,15 @@ touch_motion: wl.Listener(*wlr.Touch.event.Motion) =
|
|||||||
wl.Listener(*wlr.Touch.event.Motion).init(handleTouchMotion),
|
wl.Listener(*wlr.Touch.event.Motion).init(handleTouchMotion),
|
||||||
touch_frame: wl.Listener(void) = wl.Listener(void).init(handleTouchFrame),
|
touch_frame: wl.Listener(void) = wl.Listener(void).init(handleTouchFrame),
|
||||||
|
|
||||||
|
tablet_tool_axis: wl.Listener(*wlr.Tablet.event.Axis) =
|
||||||
|
wl.Listener(*wlr.Tablet.event.Axis).init(handleTabletToolAxis),
|
||||||
|
tablet_tool_proximity: wl.Listener(*wlr.Tablet.event.Proximity) =
|
||||||
|
wl.Listener(*wlr.Tablet.event.Proximity).init(handleTabletToolProximity),
|
||||||
|
tablet_tool_tip: wl.Listener(*wlr.Tablet.event.Tip) =
|
||||||
|
wl.Listener(*wlr.Tablet.event.Tip).init(handleTabletToolTip),
|
||||||
|
tablet_tool_button: wl.Listener(*wlr.Tablet.event.Button) =
|
||||||
|
wl.Listener(*wlr.Tablet.event.Button).init(handleTabletToolButton),
|
||||||
|
|
||||||
pub fn init(self: *Self, seat: *Seat) !void {
|
pub fn init(self: *Self, seat: *Seat) !void {
|
||||||
const wlr_cursor = try wlr.Cursor.create();
|
const wlr_cursor = try wlr.Cursor.create();
|
||||||
errdefer wlr_cursor.destroy();
|
errdefer wlr_cursor.destroy();
|
||||||
@ -204,8 +216,7 @@ pub fn init(self: *Self, seat: *Seat) !void {
|
|||||||
// when the pointer moves. However, we can attach input devices to it, and
|
// when the pointer moves. However, we can attach input devices to it, and
|
||||||
// it will generate aggregate events for all of them. In these events, we
|
// it will generate aggregate events for all of them. In these events, we
|
||||||
// can choose how we want to process them, forwarding them to clients and
|
// can choose how we want to process them, forwarding them to clients and
|
||||||
// moving the cursor around. See following post for more detail:
|
// moving the cursor around.
|
||||||
// https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html
|
|
||||||
wlr_cursor.events.axis.add(&self.axis);
|
wlr_cursor.events.axis.add(&self.axis);
|
||||||
wlr_cursor.events.button.add(&self.button);
|
wlr_cursor.events.button.add(&self.button);
|
||||||
wlr_cursor.events.frame.add(&self.frame);
|
wlr_cursor.events.frame.add(&self.frame);
|
||||||
@ -223,6 +234,11 @@ pub fn init(self: *Self, seat: *Seat) !void {
|
|||||||
wlr_cursor.events.touch_down.add(&self.touch_down);
|
wlr_cursor.events.touch_down.add(&self.touch_down);
|
||||||
wlr_cursor.events.touch_motion.add(&self.touch_motion);
|
wlr_cursor.events.touch_motion.add(&self.touch_motion);
|
||||||
wlr_cursor.events.touch_frame.add(&self.touch_frame);
|
wlr_cursor.events.touch_frame.add(&self.touch_frame);
|
||||||
|
|
||||||
|
wlr_cursor.events.tablet_tool_axis.add(&self.tablet_tool_axis);
|
||||||
|
wlr_cursor.events.tablet_tool_proximity.add(&self.tablet_tool_proximity);
|
||||||
|
wlr_cursor.events.tablet_tool_tip.add(&self.tablet_tool_tip);
|
||||||
|
wlr_cursor.events.tablet_tool_button.add(&self.tablet_tool_button);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
@ -251,7 +267,7 @@ pub fn setTheme(self: *Self, theme: ?[*:0]const u8, _size: ?u32) !void {
|
|||||||
if (build_options.xwayland) {
|
if (build_options.xwayland) {
|
||||||
if (server.xwayland) |xwayland| {
|
if (server.xwayland) |xwayland| {
|
||||||
try xcursor_manager.load(1);
|
try xcursor_manager.load(1);
|
||||||
const wlr_xcursor = xcursor_manager.getXcursor("left_ptr", 1).?;
|
const wlr_xcursor = xcursor_manager.getXcursor("default", 1).?;
|
||||||
const image = wlr_xcursor.images[0];
|
const image = wlr_xcursor.images[0];
|
||||||
xwayland.setCursor(
|
xwayland.setCursor(
|
||||||
image.buffer,
|
image.buffer,
|
||||||
@ -280,7 +296,7 @@ pub fn setXcursor(self: *Self, name: [*:0]const u8) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn clearFocus(self: *Self) void {
|
fn clearFocus(self: *Self) void {
|
||||||
self.setXcursor("left_ptr");
|
self.setXcursor("default");
|
||||||
self.seat.wlr_seat.pointerNotifyClearFocus();
|
self.seat.wlr_seat.pointerNotifyClearFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -557,6 +573,62 @@ fn handleTouchFrame(listener: *wl.Listener(void)) void {
|
|||||||
self.seat.wlr_seat.touchNotifyFrame();
|
self.seat.wlr_seat.touchNotifyFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handleTabletToolAxis(
|
||||||
|
_: *wl.Listener(*wlr.Tablet.event.Axis),
|
||||||
|
event: *wlr.Tablet.event.Axis,
|
||||||
|
) void {
|
||||||
|
const device: *InputDevice = @ptrFromInt(event.device.data);
|
||||||
|
const tablet = @fieldParentPtr(Tablet, "device", device);
|
||||||
|
|
||||||
|
device.seat.handleActivity();
|
||||||
|
|
||||||
|
const tool = TabletTool.get(device.seat.wlr_seat, event.tool) catch return;
|
||||||
|
|
||||||
|
tool.axis(tablet, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleTabletToolProximity(
|
||||||
|
_: *wl.Listener(*wlr.Tablet.event.Proximity),
|
||||||
|
event: *wlr.Tablet.event.Proximity,
|
||||||
|
) void {
|
||||||
|
const device: *InputDevice = @ptrFromInt(event.device.data);
|
||||||
|
const tablet = @fieldParentPtr(Tablet, "device", device);
|
||||||
|
|
||||||
|
device.seat.handleActivity();
|
||||||
|
|
||||||
|
const tool = TabletTool.get(device.seat.wlr_seat, event.tool) catch return;
|
||||||
|
|
||||||
|
tool.proximity(tablet, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleTabletToolTip(
|
||||||
|
_: *wl.Listener(*wlr.Tablet.event.Tip),
|
||||||
|
event: *wlr.Tablet.event.Tip,
|
||||||
|
) void {
|
||||||
|
const device: *InputDevice = @ptrFromInt(event.device.data);
|
||||||
|
const tablet = @fieldParentPtr(Tablet, "device", device);
|
||||||
|
|
||||||
|
device.seat.handleActivity();
|
||||||
|
|
||||||
|
const tool = TabletTool.get(device.seat.wlr_seat, event.tool) catch return;
|
||||||
|
|
||||||
|
tool.tip(tablet, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleTabletToolButton(
|
||||||
|
_: *wl.Listener(*wlr.Tablet.event.Button),
|
||||||
|
event: *wlr.Tablet.event.Button,
|
||||||
|
) void {
|
||||||
|
const device: *InputDevice = @ptrFromInt(event.device.data);
|
||||||
|
const tablet = @fieldParentPtr(Tablet, "device", device);
|
||||||
|
|
||||||
|
device.seat.handleActivity();
|
||||||
|
|
||||||
|
const tool = TabletTool.get(device.seat.wlr_seat, event.tool) catch return;
|
||||||
|
|
||||||
|
tool.button(tablet, event);
|
||||||
|
}
|
||||||
|
|
||||||
/// Handle the mapping for the passed button if any. Returns true if there
|
/// Handle the mapping for the passed button if any. Returns true if there
|
||||||
/// was a mapping and the button was handled.
|
/// was a mapping and the button was handled.
|
||||||
fn handlePointerMapping(self: *Self, event: *wlr.Pointer.event.Button, view: *View) bool {
|
fn handlePointerMapping(self: *Self, event: *wlr.Pointer.event.Button, view: *View) bool {
|
||||||
|
@ -31,6 +31,7 @@ const server = &@import("main.zig").server;
|
|||||||
const util = @import("util.zig");
|
const util = @import("util.zig");
|
||||||
|
|
||||||
const InputDevice = @import("InputDevice.zig");
|
const InputDevice = @import("InputDevice.zig");
|
||||||
|
const Tablet = @import("Tablet.zig");
|
||||||
|
|
||||||
pub const EventState = enum {
|
pub const EventState = enum {
|
||||||
enabled,
|
enabled,
|
||||||
@ -238,6 +239,11 @@ pub const MapToOutput = struct {
|
|||||||
});
|
});
|
||||||
|
|
||||||
device.seat.cursor.wlr_cursor.mapInputToOutput(device.wlr_device, wlr_output);
|
device.seat.cursor.wlr_cursor.mapInputToOutput(device.wlr_device, wlr_output);
|
||||||
|
|
||||||
|
if (device.wlr_device.type == .tablet_tool) {
|
||||||
|
const tablet = @fieldParentPtr(Tablet, "device", device);
|
||||||
|
tablet.output_mapping = wlr_output;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// These devices do not support being mapped to outputs.
|
// These devices do not support being mapped to outputs.
|
||||||
|
@ -30,6 +30,7 @@ const util = @import("util.zig");
|
|||||||
const Seat = @import("Seat.zig");
|
const Seat = @import("Seat.zig");
|
||||||
const Keyboard = @import("Keyboard.zig");
|
const Keyboard = @import("Keyboard.zig");
|
||||||
const Switch = @import("Switch.zig");
|
const Switch = @import("Switch.zig");
|
||||||
|
const Tablet = @import("Tablet.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.input_manager);
|
const log = std.log.scoped(.input_manager);
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ link: wl.list.Link,
|
|||||||
pub fn init(device: *InputDevice, seat: *Seat, wlr_device: *wlr.InputDevice) !void {
|
pub fn init(device: *InputDevice, seat: *Seat, wlr_device: *wlr.InputDevice) !void {
|
||||||
const device_type: []const u8 = switch (wlr_device.type) {
|
const device_type: []const u8 = switch (wlr_device.type) {
|
||||||
.switch_device => "switch",
|
.switch_device => "switch",
|
||||||
|
.tablet_tool => "tablet",
|
||||||
else => @tagName(wlr_device.type),
|
else => @tagName(wlr_device.type),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -77,6 +79,8 @@ pub fn init(device: *InputDevice, seat: *Seat, wlr_device: *wlr.InputDevice) !vo
|
|||||||
.link = undefined,
|
.link = undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
wlr_device.data = @intFromPtr(device);
|
||||||
|
|
||||||
wlr_device.events.destroy.add(&device.destroy);
|
wlr_device.events.destroy.add(&device.destroy);
|
||||||
|
|
||||||
// Keyboard groups are implemented as "virtual" input devices which we don't want to expose
|
// Keyboard groups are implemented as "virtual" input devices which we don't want to expose
|
||||||
@ -106,6 +110,8 @@ pub fn deinit(device: *InputDevice) void {
|
|||||||
device.seat.updateCapabilities();
|
device.seat.updateCapabilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
device.wlr_device.data = 0;
|
||||||
|
|
||||||
device.* = undefined;
|
device.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,11 +135,15 @@ fn handleDestroy(listener: *wl.Listener(*wlr.InputDevice), _: *wlr.InputDevice)
|
|||||||
device.deinit();
|
device.deinit();
|
||||||
util.gpa.destroy(device);
|
util.gpa.destroy(device);
|
||||||
},
|
},
|
||||||
|
.tablet_tool => {
|
||||||
|
const tablet = @fieldParentPtr(Tablet, "device", device);
|
||||||
|
tablet.destroy();
|
||||||
|
},
|
||||||
.switch_device => {
|
.switch_device => {
|
||||||
const switch_device = @fieldParentPtr(Switch, "device", device);
|
const switch_device = @fieldParentPtr(Switch, "device", device);
|
||||||
switch_device.deinit();
|
switch_device.deinit();
|
||||||
util.gpa.destroy(switch_device);
|
util.gpa.destroy(switch_device);
|
||||||
},
|
},
|
||||||
.tablet_tool, .tablet_pad => unreachable,
|
.tablet_pad => unreachable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ virtual_keyboard_manager: *wlr.VirtualKeyboardManagerV1,
|
|||||||
pointer_constraints: *wlr.PointerConstraintsV1,
|
pointer_constraints: *wlr.PointerConstraintsV1,
|
||||||
input_method_manager: *wlr.InputMethodManagerV2,
|
input_method_manager: *wlr.InputMethodManagerV2,
|
||||||
text_input_manager: *wlr.TextInputManagerV3,
|
text_input_manager: *wlr.TextInputManagerV3,
|
||||||
|
tablet_manager: *wlr.TabletManagerV2,
|
||||||
|
|
||||||
/// List of input device configurations. Ordered by glob generality, with
|
/// List of input device configurations. Ordered by glob generality, with
|
||||||
/// the most general towards the start and the most specific towards the end.
|
/// the most general towards the start and the most specific towards the end.
|
||||||
@ -84,6 +85,7 @@ pub fn init(self: *Self) !void {
|
|||||||
.pointer_constraints = try wlr.PointerConstraintsV1.create(server.wl_server),
|
.pointer_constraints = try wlr.PointerConstraintsV1.create(server.wl_server),
|
||||||
.input_method_manager = try wlr.InputMethodManagerV2.create(server.wl_server),
|
.input_method_manager = try wlr.InputMethodManagerV2.create(server.wl_server),
|
||||||
.text_input_manager = try wlr.TextInputManagerV3.create(server.wl_server),
|
.text_input_manager = try wlr.TextInputManagerV3.create(server.wl_server),
|
||||||
|
.tablet_manager = try wlr.TabletManagerV2.create(server.wl_server),
|
||||||
.configs = std.ArrayList(InputConfig).init(util.gpa),
|
.configs = std.ArrayList(InputConfig).init(util.gpa),
|
||||||
|
|
||||||
.devices = undefined,
|
.devices = undefined,
|
||||||
|
@ -41,6 +41,7 @@ const Output = @import("Output.zig");
|
|||||||
const PointerConstraint = @import("PointerConstraint.zig");
|
const PointerConstraint = @import("PointerConstraint.zig");
|
||||||
const SeatStatus = @import("SeatStatus.zig");
|
const SeatStatus = @import("SeatStatus.zig");
|
||||||
const Switch = @import("Switch.zig");
|
const Switch = @import("Switch.zig");
|
||||||
|
const Tablet = @import("Tablet.zig");
|
||||||
const View = @import("View.zig");
|
const View = @import("View.zig");
|
||||||
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
||||||
|
|
||||||
@ -500,6 +501,10 @@ fn tryAddDevice(self: *Self, wlr_device: *wlr.InputDevice) !void {
|
|||||||
|
|
||||||
self.cursor.wlr_cursor.attachInputDevice(wlr_device);
|
self.cursor.wlr_cursor.attachInputDevice(wlr_device);
|
||||||
},
|
},
|
||||||
|
.tablet_tool => {
|
||||||
|
try Tablet.create(self, wlr_device);
|
||||||
|
self.cursor.wlr_cursor.attachInputDevice(wlr_device);
|
||||||
|
},
|
||||||
.switch_device => {
|
.switch_device => {
|
||||||
const switch_device = try util.gpa.create(Switch);
|
const switch_device = try util.gpa.create(Switch);
|
||||||
errdefer util.gpa.destroy(switch_device);
|
errdefer util.gpa.destroy(switch_device);
|
||||||
@ -508,7 +513,7 @@ fn tryAddDevice(self: *Self, wlr_device: *wlr.InputDevice) !void {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// TODO Support these types of input devices.
|
// TODO Support these types of input devices.
|
||||||
.tablet_tool, .tablet_pad => return,
|
.tablet_pad => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -523,8 +528,8 @@ pub fn updateCapabilities(self: *Self) void {
|
|||||||
switch (device.wlr_device.type) {
|
switch (device.wlr_device.type) {
|
||||||
.keyboard => capabilities.keyboard = true,
|
.keyboard => capabilities.keyboard = true,
|
||||||
.touch => capabilities.touch = true,
|
.touch => capabilities.touch = true,
|
||||||
.pointer, .switch_device => {},
|
.pointer, .switch_device, .tablet_tool => {},
|
||||||
.tablet_tool, .tablet_pad => unreachable,
|
.tablet_pad => unreachable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -347,6 +347,10 @@ fn handleRequestSetCursorShape(
|
|||||||
_: *wl.Listener(*wlr.CursorShapeManagerV1.event.RequestSetShape),
|
_: *wl.Listener(*wlr.CursorShapeManagerV1.event.RequestSetShape),
|
||||||
event: *wlr.CursorShapeManagerV1.event.RequestSetShape,
|
event: *wlr.CursorShapeManagerV1.event.RequestSetShape,
|
||||||
) void {
|
) void {
|
||||||
|
// Ignore requests to set a tablet tool's cursor shape for now
|
||||||
|
// TODO(wlroots): https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3821
|
||||||
|
if (event.device_type == .tablet_tool) return;
|
||||||
|
|
||||||
const focused_client = event.seat_client.seat.pointer_state.focused_client;
|
const focused_client = event.seat_client.seat.pointer_state.focused_client;
|
||||||
|
|
||||||
// This can be sent by any client, so we check to make sure this one is
|
// This can be sent by any client, so we check to make sure this one is
|
||||||
|
54
river/Tablet.zig
Normal file
54
river/Tablet.zig
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// 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 Tablet = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const wlr = @import("wlroots");
|
||||||
|
|
||||||
|
const server = &@import("main.zig").server;
|
||||||
|
const util = @import("util.zig");
|
||||||
|
|
||||||
|
const InputDevice = @import("InputDevice.zig");
|
||||||
|
const Seat = @import("Seat.zig");
|
||||||
|
const TabletTool = @import("TabletTool.zig");
|
||||||
|
|
||||||
|
device: InputDevice,
|
||||||
|
wp_tablet: *wlr.TabletV2Tablet,
|
||||||
|
|
||||||
|
output_mapping: ?*wlr.Output = null,
|
||||||
|
|
||||||
|
pub fn create(seat: *Seat, wlr_device: *wlr.InputDevice) !void {
|
||||||
|
assert(wlr_device.type == .tablet_tool);
|
||||||
|
|
||||||
|
const tablet = try util.gpa.create(Tablet);
|
||||||
|
errdefer util.gpa.destroy(tablet);
|
||||||
|
|
||||||
|
const tablet_manager = server.input_manager.tablet_manager;
|
||||||
|
|
||||||
|
tablet.* = .{
|
||||||
|
.device = undefined,
|
||||||
|
.wp_tablet = try tablet_manager.createTabletV2Tablet(seat.wlr_seat, wlr_device),
|
||||||
|
};
|
||||||
|
try tablet.device.init(seat, wlr_device);
|
||||||
|
errdefer tablet.device.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(tablet: *Tablet) void {
|
||||||
|
tablet.device.deinit();
|
||||||
|
util.gpa.destroy(tablet);
|
||||||
|
}
|
265
river/TabletTool.zig
Normal file
265
river/TabletTool.zig
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
// 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();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user