282 lines
11 KiB
Zig
282 lines
11 KiB
Zig
//! Input device configuration, as declared in config.zig.
|
|
//!
|
|
//! Like action.zig this module depends on nothing but the standard library, so
|
|
//! that config.zig can import it without a cycle back into the window manager.
|
|
//! `src/InputManager.zig` is what puts these values onto river's protocol
|
|
//! objects.
|
|
//!
|
|
//! Every device setting is optional, and null means "leave it alone" — libinput
|
|
//! picks per-device defaults that are usually right, so a rule should say only
|
|
//! what it wants changed.
|
|
|
|
const std = @import("std");
|
|
|
|
/// The kind of device, mirroring `river_input_device_v1.type`.
|
|
///
|
|
/// Note that a touchpad reports `pointer`, not `touch`: libinput models it as a
|
|
/// pointer that happens to support tapping. `touch` is a touchscreen.
|
|
pub const Type = enum { keyboard, pointer, touch, tablet };
|
|
|
|
/// xkb rule names — the RMLVO that `setxkbmap` and every other Wayland
|
|
/// compositor take. att_wm compiles these into a keymap and hands it to every
|
|
/// keyboard river reports.
|
|
///
|
|
/// A null field is left to xkbcommon, which reads the `XKB_DEFAULT_*`
|
|
/// environment variables and otherwise falls back to a plain `us` layout. So
|
|
/// the default of all-null is exactly what you get without this protocol at all.
|
|
pub const Keymap = struct {
|
|
/// Rules file, e.g. "evdev". Rarely worth setting.
|
|
rules: ?[]const u8 = null,
|
|
model: ?[]const u8 = null,
|
|
/// One layout, or several separated by commas: "us,se".
|
|
layout: ?[]const u8 = null,
|
|
/// Variants, positionally matching `layout`: "dvorak," is dvorak for the
|
|
/// first layout and the default variant for the second.
|
|
variant: ?[]const u8 = null,
|
|
/// Comma separated, e.g. "grp:alt_shift_toggle,caps:escape". With more than
|
|
/// one layout configured, a `grp:` option is how you switch between them —
|
|
/// xkb does the switching itself, so att_wm needs no binding for it.
|
|
options: ?[]const u8 = null,
|
|
|
|
/// True when nothing is set, in which case there is no point compiling a
|
|
/// keymap: river's own default is already what we would produce.
|
|
pub fn isDefault(self: Keymap) bool {
|
|
inline for (std.meta.fields(Keymap)) |field| {
|
|
if (@field(self, field.name) != null) return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
/// Key repeat as applied by the compositor to the focused client.
|
|
///
|
|
/// This is not the same thing as `config.binding_repeat_delay` and
|
|
/// `config.binding_repeat_interval`, which govern how fast att_wm re-runs a held-down
|
|
/// *binding*: river reports binding press and release and leaves repeating to
|
|
/// us. These two are what every other application sees.
|
|
/// The defaults are river's own, so a config that says nothing about repeat
|
|
/// leaves keyboards exactly as they would have been.
|
|
pub const Repeat = struct {
|
|
/// Repeats per second. Zero disables key repeat entirely.
|
|
rate: i32 = 40,
|
|
/// Milliseconds a key must be held before repeating starts.
|
|
delay: i32 = 400,
|
|
};
|
|
|
|
pub const ButtonMap = enum {
|
|
/// One finger left, two right, three middle. libinput's default.
|
|
lrm,
|
|
/// One finger left, two middle, three right.
|
|
lmr,
|
|
};
|
|
|
|
pub const DragLock = enum {
|
|
disabled,
|
|
/// Lifting the finger keeps the drag alive for a short timeout.
|
|
timeout,
|
|
/// Lifting the finger keeps the drag alive until the next tap.
|
|
sticky,
|
|
};
|
|
|
|
pub const ThreeFingerDrag = enum { disabled, three_finger, four_finger };
|
|
|
|
pub const ClickMethod = enum {
|
|
none,
|
|
/// Bottom of the touchpad split into left/middle/right zones.
|
|
button_areas,
|
|
/// Number of fingers on the pad decides the button.
|
|
clickfinger,
|
|
};
|
|
|
|
pub const AccelProfile = enum {
|
|
/// No acceleration: movement maps to pointer travel one to one.
|
|
none,
|
|
/// Constant factor, no acceleration.
|
|
flat,
|
|
/// Speed-dependent acceleration. libinput's default for most devices.
|
|
adaptive,
|
|
};
|
|
|
|
pub const ScrollMethod = enum {
|
|
none,
|
|
two_finger,
|
|
edge,
|
|
/// Moving the device while `scroll_button` is held scrolls.
|
|
on_button_down,
|
|
};
|
|
|
|
pub const SendEvents = enum {
|
|
enabled,
|
|
disabled,
|
|
/// Useful for a laptop touchpad that should go quiet when a mouse is
|
|
/// plugged in.
|
|
disabled_on_external_mouse,
|
|
};
|
|
|
|
/// Matched against the name and type of every input device river reports.
|
|
///
|
|
/// att_wm logs one line per device at startup — name and type — which is where
|
|
/// the names come from; there is no `list-inputs` to run because the protocol
|
|
/// only shows devices to the window manager itself.
|
|
pub const Rule = struct {
|
|
/// Device name to match. `*` matches any run of characters, so
|
|
/// `"*Touchpad*"` catches the usual "ELAN0501:00 04F3:3060 Touchpad"
|
|
/// without you having to write it out. Null matches every device.
|
|
name: ?[]const u8 = null,
|
|
/// Restrict the rule to one kind of device. Null matches every kind.
|
|
type: ?Type = null,
|
|
|
|
// ─── Keyboards ───
|
|
|
|
/// Per-device override of `config.repeat`.
|
|
repeat: ?Repeat = null,
|
|
|
|
// ─── Pointers, touchpads, touchscreens ───
|
|
|
|
/// Confine a touchscreen or tablet to one output, named as river names it —
|
|
/// "eDP-1", the same name the IPC `outputs` list uses.
|
|
///
|
|
/// Two reasons to want this. On multiple monitors an unmapped touchscreen
|
|
/// spans the whole output layout, so touching the left of the panel lands on
|
|
/// the wrong screen. And it is what makes touch survive **display
|
|
/// rotation**: a mapped device has the output's transform applied to its
|
|
/// coordinates on every event, so rotating with `wlr-randr` or rot8 rotates
|
|
/// touch along with it, with no rotation hook and no calibration matrix.
|
|
///
|
|
/// Do not combine with an external calibration matrix for rotation — the two
|
|
/// transforms compose, and the result is rotated twice.
|
|
///
|
|
/// Ignored for keyboards, which have no coordinates to map.
|
|
map_to_output: ?[]const u8 = null,
|
|
|
|
/// Multiplier on scroll distance: 0.5 scrolls half as far, 3.0 three times
|
|
/// as far. Applied by river rather than libinput, so it works on any
|
|
/// pointer.
|
|
scroll_factor: ?f64 = null,
|
|
|
|
/// Tap to click.
|
|
tap: ?bool = null,
|
|
/// Which button each finger count taps.
|
|
tap_button_map: ?ButtonMap = null,
|
|
/// Tap and then drag without a second tap.
|
|
drag: ?bool = null,
|
|
/// Whether lifting the finger mid-drag ends it.
|
|
drag_lock: ?DragLock = null,
|
|
/// Hold three (or four) fingers to drag.
|
|
three_finger_drag: ?ThreeFingerDrag = null,
|
|
|
|
/// What a physical click on a touchpad means.
|
|
click_method: ?ClickMethod = null,
|
|
/// Which button each finger count clicks, under `.clickfinger`.
|
|
clickfinger_button_map: ?ButtonMap = null,
|
|
/// Left and right buttons together act as middle click.
|
|
middle_emulation: ?bool = null,
|
|
/// Swap left and right buttons.
|
|
left_handed: ?bool = null,
|
|
|
|
/// Content follows the fingers rather than the viewport, as on a phone.
|
|
natural_scroll: ?bool = null,
|
|
scroll_method: ?ScrollMethod = null,
|
|
/// Linux input event code — `input.btn.middle` and friends. Only meaningful
|
|
/// with `scroll_method = .on_button_down`.
|
|
scroll_button: ?u32 = null,
|
|
/// Whether the scroll button must be held, or toggles.
|
|
scroll_button_lock: ?bool = null,
|
|
|
|
accel_profile: ?AccelProfile = null,
|
|
/// Pointer speed in [-1, 1]; 0 is the device's default.
|
|
accel_speed: ?f64 = null,
|
|
|
|
/// Ignore the touchpad while the keyboard is being typed on.
|
|
disable_while_typing: ?bool = null,
|
|
/// Ignore the touchpad while the trackpoint is in use.
|
|
disable_while_trackpointing: ?bool = null,
|
|
|
|
/// Clockwise rotation in degrees, for a device mounted sideways.
|
|
rotation: ?u32 = null,
|
|
|
|
/// Whether the device sends events at all.
|
|
send_events: ?SendEvents = null,
|
|
|
|
/// Fields that select which devices a rule applies to rather than
|
|
/// configuring them, and so are not merged by `merge`.
|
|
const selectors = .{ "name", "type" };
|
|
|
|
/// Fold `other` on top of `self`: every setting `other` states wins, every
|
|
/// setting it leaves null keeps the value it had.
|
|
///
|
|
/// Rules are applied in the order they are declared, so a broad rule can set
|
|
/// a house style and a later, narrower one can dissent from it — the same
|
|
/// last-one-wins that dwm's window rules have.
|
|
pub fn merge(self: Rule, other: Rule) Rule {
|
|
var out = self;
|
|
inline for (std.meta.fields(Rule)) |field| {
|
|
comptime var is_selector = false;
|
|
inline for (selectors) |name| {
|
|
if (comptime std.mem.eql(u8, field.name, name)) is_selector = true;
|
|
}
|
|
if (!is_selector) {
|
|
if (@field(other, field.name)) |v| @field(out, field.name) = v;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/// True if this rule should apply to a device with the given name and type.
|
|
pub fn matchesDevice(self: Rule, device_name: []const u8, device_type: Type) bool {
|
|
if (self.type) |t| {
|
|
if (t != device_type) return false;
|
|
}
|
|
if (self.name) |pattern| {
|
|
if (!matches(pattern, device_name)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
/// Glob match supporting `*` as "any run of characters, including none".
|
|
///
|
|
/// Deliberately no `?` or character classes: device names are long, noisy and
|
|
/// full of punctuation, and `*` on either end is all anyone needs to pin one
|
|
/// down. Iterative with a backtrack point rather than recursive, so a pattern
|
|
/// like `"*a*a*a*"` cannot blow the stack.
|
|
pub fn matches(pattern: []const u8, name: []const u8) bool {
|
|
var p: usize = 0;
|
|
var n: usize = 0;
|
|
// Where to resume if the run we are in turns out not to match: the `*` that
|
|
// let us in, and how far it had consumed.
|
|
var star: ?usize = null;
|
|
var star_n: usize = 0;
|
|
|
|
while (n < name.len) {
|
|
if (p < pattern.len and pattern[p] == '*') {
|
|
star = p;
|
|
p += 1;
|
|
star_n = n;
|
|
} else if (p < pattern.len and pattern[p] == name[n]) {
|
|
p += 1;
|
|
n += 1;
|
|
} else if (star) |s| {
|
|
// Let the last `*` swallow one more byte and try again.
|
|
p = s + 1;
|
|
star_n += 1;
|
|
n = star_n;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Trailing `*`s can still match the empty remainder.
|
|
while (p < pattern.len and pattern[p] == '*') p += 1;
|
|
return p == pattern.len;
|
|
}
|
|
|
|
/// Linux input event codes for the buttons worth binding to scrolling. The same
|
|
/// values `action.btn` has; duplicated rather than imported so this module keeps
|
|
/// its single dependency on the standard library.
|
|
pub const btn = struct {
|
|
pub const left: u32 = 0x110;
|
|
pub const right: u32 = 0x111;
|
|
pub const middle: u32 = 0x112;
|
|
};
|