Add per-tool settings and gesture customization

This commit is contained in:
ajp_anton
2026-09-05 05:02:34 +00:00
parent d6f30c5735
commit 38142b9499
26 changed files with 1383 additions and 83 deletions
@@ -5,6 +5,8 @@ local function usage(event_type, code)
end
evdev = {
BTN_LEFT = usage(1, 272),
BTN_RIGHT = usage(1, 273),
BTN_MIDDLE = usage(1, 274),
BTN_TOUCH = usage(1, 330),
BTN_TOOL_FINGER = usage(1, 325),
@@ -20,8 +22,19 @@ evdev = {
ABS_MT_POSITION_Y = usage(3, 54),
ABS_MT_TOOL_Y = usage(3, 61),
ABS_MT_TRACKING_ID = usage(3, 57),
KEY_F13 = usage(1, 183),
}
local output_event = arg[2] or "BTN_MIDDLE"
local allow_moving_anchor = arg[3] == "allow-moving-anchor"
local expected_output = evdev[output_event]
if arg[2] or allow_moving_anchor then
TOUCHPAD_HOLD_TAP_SETTINGS = {
output_event = output_event,
minimum_pause_ms = allow_moving_anchor and 0 or nil,
}
end
local plugin_callbacks = {}
libinput = {
register = function(_, versions)
@@ -66,6 +79,8 @@ local function new_device(device_type)
return { ID_INPUT_TOUCHPAD = true }
elseif device_type == "mouse" then
return { ID_INPUT_MOUSE = true }
elseif device_type == "keyboard" then
return { ID_INPUT_KEYBOARD = true }
end
return {}
end
@@ -159,12 +174,13 @@ local function up(device, timestamp, slot, count_after)
return feed(device, timestamp, frame)
end
local button_device = new_device("mouse")
local output_device = new_device(output_event:sub(1, 4) == "KEY_" and
"keyboard" or "mouse")
local function click_count()
local function action_count()
local count = 0
for _, frame in ipairs(button_device.appended) do
if contains(frame, evdev.BTN_MIDDLE, 1) then
for _, frame in ipairs(output_device.appended) do
if contains(frame, expected_output, 1) then
count = count + 1
end
end
@@ -181,7 +197,7 @@ function tests.hold_tap_emits_middle_and_hides_tapper()
assert(not contains(second_down, evdev.BTN_TOOL_DOUBLETAP, 1))
local second_up = up(device, 260000, 1, 1)
assert(not contains(second_up, evdev.ABS_MT_TRACKING_ID, -1))
assert(click_count() == 1)
assert(action_count() == 1)
assert(#device.appended == 0)
up(device, 440000, 0, 0)
end
@@ -193,7 +209,7 @@ function tests.hold_doubletap_emits_two_middle_clicks()
up(device, 250000, 1, 1)
down(device, 360000, 1, 22, 1400, 1000, 2)
up(device, 420000, 1, 1)
assert(click_count() == 2)
assert(action_count() == 2)
up(device, 600000, 0, 0)
end
@@ -205,7 +221,7 @@ function tests.ordinary_two_finger_tap_passes_through()
assert(contains(second, evdev.BTN_TOOL_DOUBLETAP, 1))
up(device, 70000, 1, 1)
up(device, 80000, 0, 0)
assert(click_count() == 0)
assert(action_count() == 0)
end
function tests.long_stationary_tap_is_consumed_without_click()
@@ -214,10 +230,19 @@ function tests.long_stationary_tap_is_consumed_without_click()
down(device, 200000, 1, 41, 1400, 1000, 2)
local second_up = up(device, 360001, 1, 1)
assert(not contains(second_up, evdev.ABS_MT_TRACKING_ID, -1))
assert(click_count() == 0)
assert(action_count() == 0)
up(device, 500000, 0, 0)
end
function tests.implausibly_short_contact_is_consumed_without_click()
local device = new_device("touchpad")
down(device, 0, 0, 45, 1000, 1000, 1)
down(device, 200000, 1, 46, 1400, 1000, 2)
up(device, 201000, 1, 1)
assert(action_count() == 0)
up(device, 400000, 0, 0)
end
function tests.tapper_movement_restores_normal_input()
local device = new_device("touchpad")
down(device, 0, 0, 50, 1000, 1000, 1)
@@ -231,7 +256,7 @@ function tests.tapper_movement_restores_normal_input()
assert(not contains(moved, evdev.ABS_MT_POSITION_X, 1430))
up(device, 260000, 1, 1)
up(device, 400000, 0, 0)
assert(click_count() == 0)
assert(action_count() == 0)
end
function tests.anchor_movement_prevents_recognition()
@@ -239,10 +264,11 @@ function tests.anchor_movement_prevents_recognition()
down(device, 0, 0, 60, 1000, 1000, 1)
feed(device, 150000, { event(evdev.ABS_MT_POSITION_X, 1040) })
local second = down(device, 200000, 1, 61, 1400, 1000, 2)
assert(contains(second, evdev.ABS_MT_TRACKING_ID, 61))
assert(contains(second, evdev.ABS_MT_TRACKING_ID, 61) ~=
allow_moving_anchor)
up(device, 250000, 1, 1)
up(device, 400000, 0, 0)
assert(click_count() == 0)
assert(action_count() == (allow_moving_anchor and 1 or 0))
end
function tests.anchor_movement_during_tap_restores_normal_input()
@@ -253,12 +279,54 @@ function tests.anchor_movement_during_tap_restores_normal_input()
event(evdev.ABS_MT_SLOT, 0),
event(evdev.ABS_MT_POSITION_X, 1040),
})
assert(#device.prepended == 1)
assert(contains(device.prepended[1], evdev.ABS_MT_TRACKING_ID, 63))
assert(#device.prepended == (allow_moving_anchor and 0 or 1))
if not allow_moving_anchor then
assert(contains(device.prepended[1], evdev.ABS_MT_TRACKING_ID, 63))
end
assert(contains(moved, evdev.ABS_MT_POSITION_X, 1040))
up(device, 260000, 1, 1)
up(device, 400000, 0, 0)
assert(click_count() == 0)
assert(action_count() == (allow_moving_anchor and 1 or 0))
end
function tests.rejected_tap_does_not_poison_anchor()
local device = new_device("touchpad")
down(device, 0, 0, 64, 1000, 1000, 1)
-- This near-simultaneous contact is passed through as an ordinary
-- two-finger interaction.
local early = down(device, 10000, 1, 65, 1400, 1000, 2)
assert(contains(early, evdev.ABS_MT_TRACKING_ID, 65))
up(device, 60000, 1, 1)
-- The same anchor can recognize a later tap without being lifted first.
local later = down(device, 200000, 1, 66, 1400, 1000, 2)
assert(not contains(later, evdev.ABS_MT_TRACKING_ID, 66))
up(device, 250000, 1, 1)
assert(action_count() == 1)
up(device, 300000, 0, 0)
end
function tests.tap_while_moving_does_not_poison_anchor()
if allow_moving_anchor then
return
end
local device = new_device("touchpad")
down(device, 0, 0, 67, 1000, 1000, 1)
feed(device, 150000, {
event(evdev.ABS_MT_SLOT, 0),
event(evdev.ABS_MT_POSITION_X, 1040),
})
local moving_tap = down(device, 160000, 1, 68, 1400, 1000, 2)
assert(contains(moving_tap, evdev.ABS_MT_TRACKING_ID, 68))
up(device, 210000, 1, 1)
local later = down(device, 320000, 1, 69, 1400, 1000, 2)
assert(not contains(later, evdev.ABS_MT_TRACKING_ID, 69))
up(device, 370000, 1, 1)
assert(action_count() == 1)
up(device, 400000, 0, 0)
end
function tests.anchor_can_move_pause_and_tap_repeatedly()
@@ -272,7 +340,7 @@ function tests.anchor_can_move_pause_and_tap_repeatedly()
})
down(device, 650000, 1, 66, 1500, 1100, 2)
up(device, 700000, 1, 1)
assert(click_count() == 1)
assert(action_count() == 1)
feed(device, 800000, {
event(evdev.ABS_MT_SLOT, 0),
@@ -281,7 +349,7 @@ function tests.anchor_can_move_pause_and_tap_repeatedly()
})
down(device, 950000, 1, 67, 2000, 1200, 2)
up(device, 1000000, 1, 1)
assert(click_count() == 2)
assert(action_count() == 2)
up(device, 1100000, 0, 0)
end
@@ -297,7 +365,7 @@ function tests.third_contact_restores_candidate()
up(device, 260000, 2, 2)
up(device, 280000, 1, 1)
up(device, 400000, 0, 0)
assert(click_count() == 0)
assert(action_count() == 0)
end
function tests.two_finger_hold_tap_remains_passthrough()
@@ -309,7 +377,7 @@ function tests.two_finger_hold_tap_remains_passthrough()
up(device, 360000, 2, 2)
up(device, 500000, 1, 1)
up(device, 510000, 0, 0)
assert(click_count() == 0)
assert(action_count() == 0)
assert(#device.prepended == 0)
end
@@ -320,8 +388,8 @@ function tests.non_touchpad_is_ignored()
end
function tests.pointer_device_is_used_for_button_output()
assert(button_device.enabled[evdev.BTN_MIDDLE])
assert(button_device.callbacks["evdev-frame"] == nil)
assert(output_device.enabled[expected_output])
assert(output_device.callbacks["evdev-frame"] == nil)
end
local names = {}
@@ -330,7 +398,7 @@ for name in pairs(tests) do
end
table.sort(names)
for _, name in ipairs(names) do
button_device.appended = {}
output_device.appended = {}
tests[name]()
print("ok - " .. name)
end