initial commit

This commit is contained in:
2026-07-26 20:47:19 +02:00
commit fff3a5e734
37 changed files with 10781 additions and 0 deletions
+281
View File
@@ -0,0 +1,281 @@
//! 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;
};