mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-02-04 00:00:28 +01:00
This plugin controls a mouse/pointer from a tablet device. This effectively hides stylus interactions and sends pointer events instead. In other words: mouse emulation for tablets, implemented by (remote) controlling a mouse device. This allows using a tablet stylus as a mouse replacement without tablet limitations from compositors or clients. Note that axis usually needed for drawing (like pressure, tilt or distance) are no longer emitted when this plugin is active and a mouse is connected. When no mouse is connected, this plugin doesn't change tablet events, thus the stylus works like a normal stylus. Follow up from https://gitlab.freedesktop.org/libinput/libinput/-/issues/1195 Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1412>
121 lines
4.3 KiB
Lua
121 lines
4.3 KiB
Lua
-- SPDX-License-Identifier: MIT
|
|
--
|
|
-- This is an example libinput plugin
|
|
--
|
|
-- This plugin controls a mouse/pointer from a tablet device. This
|
|
-- effectively hides stylus interactions and sends pointer events
|
|
-- instead. In other words: mouse emulation for tablets, implemented
|
|
-- by (remote) controlling a mouse device. This allows using a
|
|
-- tablet stylus as a mouse replacement without tablet limitations
|
|
-- from compositors or clients. Note that axis usually needed for
|
|
-- drawing (like pressure, tilt or distance) are no longer emitted
|
|
-- when this plugin is active and a mouse is connected. When no
|
|
-- mouse is connected, this plugin doesn't change tablet events,
|
|
-- thus the stylus works like a normal stylus.
|
|
|
|
-- UNCOMMENT THIS LINE TO ACTIVATE THE PLUGIN
|
|
-- libinput:register({1})
|
|
|
|
-- globals
|
|
pointer_device = nil
|
|
tablet_device = nil
|
|
maximum_x = nil
|
|
maximum_y = nil
|
|
|
|
function adjust_for_aspect_ratio(y)
|
|
-- adjust y to match monitor 21:9 aspect ratio
|
|
local adj_maximum_y = maximum_x * 1440 / 3440
|
|
return math.floor(math.min(y * maximum_y / adj_maximum_y, maximum_y + 1))
|
|
end
|
|
|
|
function on_tablet_frame(device, frame, time_in_microseconds)
|
|
-- emit tablet frame when there is no pointer device
|
|
if not pointer_device then return nil end
|
|
|
|
-- map tablet frame to pointer frame
|
|
local events = {}
|
|
for _, v in ipairs(frame) do
|
|
if v.usage == evdev.ABS_MISC then
|
|
-- save a few cycles on Wacom tablets by discarding a
|
|
-- proximity in / out frame early, non-Wacom tablets should
|
|
-- use BTN_TOOL_PEN/RUBBER/... instead
|
|
return {}
|
|
elseif v.usage == evdev.ABS_X then
|
|
table.insert(events, { usage = evdev.ABS_X, value = v.value })
|
|
elseif v.usage == evdev.ABS_Y then
|
|
-- uncomment the next two lines and comment the original line
|
|
-- for configuring aspect correction, see
|
|
-- adjust_for_aspect_ratio() for details and configuration
|
|
|
|
-- local adj_value = adjust_for_aspect_ratio(v.value)
|
|
-- table.insert(events, { usage = evdev.ABS_Y, value = adj_value })
|
|
table.insert(events, { usage = evdev.ABS_Y, value = v.value })
|
|
elseif v.usage == evdev.BTN_TOUCH then
|
|
table.insert(events, { usage = evdev.BTN_LEFT, value = v.value })
|
|
elseif v.usage == evdev.BTN_STYLUS then
|
|
table.insert(events, { usage = evdev.BTN_RIGHT, value = v.value })
|
|
elseif v.usage == evdev.BTN_STYLUS2 then
|
|
table.insert(events, { usage = evdev.BTN_MIDDLE, value = v.value })
|
|
end
|
|
end
|
|
|
|
-- emit pointer frame, if any
|
|
if #events > 0 then pointer_device:append_frame(events) end
|
|
|
|
-- discard tablet frame
|
|
return {}
|
|
end
|
|
|
|
function on_tablet_removed(device)
|
|
libinput:log_info("Remove tablet device")
|
|
tablet_device = nil
|
|
end
|
|
|
|
function on_pointer_removed(device)
|
|
libinput:log_info("Remove pointer device")
|
|
pointer_device = nil
|
|
end
|
|
|
|
function setup()
|
|
if not pointer_device or not tablet_device then return end
|
|
|
|
libinput:log_info("Controlling '" .. pointer_device:name() .. "' with '" .. tablet_device:name() .. "'")
|
|
|
|
-- fetch absinfos from tablet
|
|
local absinfo_x = {}
|
|
local absinfo_y = {}
|
|
for a, b in pairs(tablet_device:absinfos()) do
|
|
if a == evdev.ABS_X then absinfo_x = b end
|
|
if a == evdev.ABS_Y then absinfo_y = b end
|
|
end
|
|
|
|
-- copy max values for aspect ratio correction later on
|
|
maximum_x = absinfo_x.maximum
|
|
maximum_y = absinfo_y.maximum
|
|
|
|
-- copy absinfos to pointer device
|
|
pointer_device:set_absinfo(evdev.ABS_X, absinfo_x)
|
|
pointer_device:set_absinfo(evdev.ABS_Y, absinfo_y)
|
|
|
|
-- setup listeners
|
|
pointer_device:connect("device-removed", on_pointer_removed)
|
|
tablet_device:connect("device-removed", on_tablet_removed)
|
|
tablet_device:connect("evdev-frame", on_tablet_frame)
|
|
end
|
|
|
|
function on_new_device(device)
|
|
local udev = device:udev_properties()
|
|
if udev["ID_INPUT_TABLET"] and not udev["ID_INPUT_TABLET_PAD"] then
|
|
libinput:log_info("Found tablet device")
|
|
tablet_device = device
|
|
setup()
|
|
end
|
|
if udev["ID_INPUT_MOUSE"] then
|
|
libinput:log_info("Found pointer device")
|
|
pointer_device = device
|
|
setup()
|
|
end
|
|
end
|
|
|
|
-- setup listener
|
|
libinput:connect("new-evdev-device", on_new_device)
|