//! 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; };