initial commit
This commit is contained in:
+281
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user