//! att_wm configuration, in the spirit of dwm's config.h: edit and rebuild. //! //! Nix users need not patch the source tree — pass a replacement path instead: //! //! zig build -Dconfig=/path/to/my-config.zig //! att_wm.override { config = ./my-config.zig; } const action = @import("action"); const input = @import("input"); const xkb = @import("xkbcommon"); const Key = action.Key; const Button = action.Button; const Mods = action.Mods; const btn = action.btn; /// The dwm "MODKEY". Alt, as dwm ships it; use `Mods.super` if you would /// rather not compete with applications that bind Alt themselves. pub const mod = Mods.alt; // ─── Appearance ────────────────────────────────────────────────────────────── pub const border_width: i32 = 2; /// Gap between adjacent windows. Zero is dwm-faithful. pub const gap: i32 = 0; /// Gap between windows and the edge of the usable area. pub const outer_gap: i32 = 0; /// Colours are 0xRRGGBBAA, straight-alpha; they are premultiplied on the way /// to the protocol. pub const border_focused: u32 = 0x5294e2ff; pub const border_normal: u32 = 0x444444ff; /// Height of the tab bar drawn in the tabbed layout. Set to 0 to let a bar /// such as quickshell draw the tabs instead, using the IPC `windows` list. pub const tabbar_height: i32 = 22; pub const tab_focused: u32 = 0x5294e2ff; pub const tab_normal: u32 = 0x2c2c2cff; /// Drawn as a 1px line between adjacent tabs. pub const tab_separator: u32 = 0x1a1a1aff; // ─── Layout ────────────────────────────────────────────────────────────────── pub const default_layout = action.Layout.master; /// Windows in the master area. pub const nmaster: i32 = 1; /// Fraction of the output width given to the master area. pub const mfact: f32 = 0.55; pub const mfact_min: f32 = 0.05; pub const mfact_max: f32 = 0.95; /// Tags visible on a newly connected output. pub const default_tags: u32 = 1; /// Names exported over IPC for bars to label tags with. pub const tag_names = [action.tag_count][]const u8{ "1", "2", "3", "4", "5", "6", "7", "8", "9", }; // ─── Behaviour ─────────────────────────────────────────────────────────────── /// dwm's sloppy focus: moving the pointer over a window focuses it. pub const focus_follows_mouse = false; /// Warp the pointer to the centre of a window when focus moves there by /// keyboard. dwm does not do this; it is handy on multi-head setups. pub const warp_cursor = false; /// Windows with a parent (dialogs, file pickers) start floating, as in dwm. pub const float_children = true; /// How fast a held-down *binding* re-fires, in milliseconds. river reports key /// press and release and leaves repeating to att_wm, so this is what governs /// `Mod+j` held down — not what applications see, which is `repeat` below. pub const binding_repeat_delay: u32 = 300; pub const binding_repeat_interval: u32 = 40; pub const cursor_theme: ?[]const u8 = null; pub const cursor_size: u32 = 24; /// Commands run once at startup, after the connection to river is up. pub const autostart = [_][]const []const u8{ // .{ "quickshell", "-c", "att_wm" }, }; pub const terminal = [_][]const u8{"foot"}; pub const menu = [_][]const u8{ "wmenu-run", "-f", "monospace 10" }; // ─── Input ─────────────────────────────────────────────────────────────────── /// The xkb layout every keyboard gets, as `setxkbmap` takes it. All-null — the /// default — leaves river's own choice alone, which honours the `XKB_DEFAULT_*` /// environment variables and otherwise gives you `us`. pub const keymap: input.Keymap = .{ // .layout = "us,se", // .options = "grp:alt_shift_toggle,caps:escape", }; /// Key repeat as applications see it — not to be confused with /// `binding_repeat_delay` above, which is how fast a held-down att_wm binding /// re-fires. Per-device overrides go in `input_rules`. /// /// These are river's own defaults, so leaving them alone changes nothing. pub const repeat: input.Repeat = .{ // Repeats per second. Zero turns key repeat off. .rate = 70, // Milliseconds a key is held before repeating starts. .delay = 150, }; /// Per-device settings, matched on name and type. `name` is a glob, so `*` does /// the work of writing out "ELAN0501:00 04F3:3060 Touchpad" in full. /// /// att_wm logs one line per device as it appears — name and type — which is where /// to find the names; there is no `list-inputs` command because the protocol /// shows input devices to the window manager alone. /// /// Every setting defaults to null, meaning "leave libinput's own default". Rules /// are applied in order and a later one overrides an earlier one field by field. pub const input_rules = [_]input.Rule{ // A laptop touchpad. Tap to click and ignoring the pad mid-keystroke are // near-universally wanted; scroll direction and click method are matters of // taste, so they are left to you. .{ .name = "*Touchpad*", .tap = true, .disable_while_typing = true, // .natural_scroll = true, // .click_method = .clickfinger, }, // A per-device key repeat, faster than the default above. // .{ .type = .keyboard, .repeat = .{ .rate = 50, .delay = 250 } }, // Confining a touchscreen or pen to one output is what makes touch follow // display rotation: mapped devices have the output's transform applied to // every event, so `wlr-randr --transform` or rot8 rotates touch with the // screen — no rotation hook, no calibration matrix. It is also what stops a // touchscreen spanning both monitors on a multi-head setup. // // The glob catches both halves of the panel — "Wacom HID 5380 Finger" is the // touchscreen and "... Pen" the stylus, which river reports as `touch` and // `tablet` respectively. `.{ .type = .touch, ... }` and a second rule for // `.tablet` would do the same job without naming the hardware. .{ .name = "Wacom HID 5380*", .map_to_output = "eDP-1" }, }; // ─── Rules ─────────────────────────────────────────────────────────────────── /// Matched against a window's app_id and title. A null field matches anything. pub const Rule = struct { app_id: ?[]const u8 = null, title: ?[]const u8 = null, tags: ?u32 = null, floating: ?bool = null, }; pub const rules = [_]Rule{ .{ .app_id = "pavucontrol", .floating = true }, .{ .app_id = "org.pulseaudio.pavucontrol", .floating = true }, .{ .title = "Picture-in-Picture", .floating = true }, }; // ─── Key bindings ──────────────────────────────────────────────────────────── pub const keys = tagKeys() ++ [_]Key{ .{ .mods = mod | Mods.shift, .keysym = xkb.Keysym.Return, .action = .{ .spawn = &terminal } }, .{ .mods = mod, .keysym = xkb.Keysym.p, .action = .{ .spawn = &menu } }, .{ .mods = mod | Mods.shift, .keysym = xkb.Keysym.c, .action = .close }, .{ .mods = mod | Mods.shift, .keysym = xkb.Keysym.q, .action = .quit }, .{ .mods = mod | Mods.ctrl | Mods.shift, .keysym = xkb.Keysym.q, .action = .exit_session }, // Focus and arrangement. .{ .mods = mod, .keysym = xkb.Keysym.j, .action = .{ .focus = .next } }, .{ .mods = mod, .keysym = xkb.Keysym.k, .action = .{ .focus = .prev } }, .{ .mods = mod | Mods.shift, .keysym = xkb.Keysym.j, .action = .{ .swap = .next } }, .{ .mods = mod | Mods.shift, .keysym = xkb.Keysym.k, .action = .{ .swap = .prev } }, .{ .mods = mod, .keysym = xkb.Keysym.Return, .action = .zoom }, // Master area. .{ .mods = mod, .keysym = xkb.Keysym.h, .action = .{ .mfact = .{ .relative = -0.05 } } }, .{ .mods = mod, .keysym = xkb.Keysym.l, .action = .{ .mfact = .{ .relative = 0.05 } } }, .{ .mods = mod, .keysym = xkb.Keysym.i, .action = .{ .nmaster = .{ .relative = 1 } } }, .{ .mods = mod, .keysym = xkb.Keysym.d, .action = .{ .nmaster = .{ .relative = -1 } } }, // Layouts. .{ .mods = mod, .keysym = xkb.Keysym.t, .action = .{ .set_layout = .master } }, .{ .mods = mod, .keysym = xkb.Keysym.m, .action = .{ .set_layout = .monocle } }, .{ .mods = mod, .keysym = xkb.Keysym.u, .action = .{ .set_layout = .tabbed } }, .{ .mods = mod, .keysym = xkb.Keysym.space, .action = .toggle_layout }, .{ .mods = mod | Mods.shift, .keysym = xkb.Keysym.space, .action = .toggle_float }, .{ .mods = mod, .keysym = xkb.Keysym.f, .action = .toggle_fullscreen }, // Tags. .{ .mods = mod, .keysym = xkb.Keysym.@"0", .action = .{ .view = action.all_tags } }, .{ .mods = mod | Mods.shift, .keysym = xkb.Keysym.@"0", .action = .{ .tag = action.all_tags } }, .{ .mods = mod, .keysym = xkb.Keysym.Tab, .action = .view_prev }, .{ .mods = mod, .keysym = xkb.Keysym.Escape, .action = .view_prev }, // Outputs. .{ .mods = mod, .keysym = xkb.Keysym.comma, .action = .{ .focus_output = .prev } }, .{ .mods = mod, .keysym = xkb.Keysym.period, .action = .{ .focus_output = .next } }, .{ .mods = mod | Mods.shift, .keysym = xkb.Keysym.comma, .action = .{ .send_to_output = .prev } }, .{ .mods = mod | Mods.shift, .keysym = xkb.Keysym.period, .action = .{ .send_to_output = .next } }, }; /// dwm's TAGKEYS macro: Mod+N views, Mod+Shift+N tags, Mod+Ctrl+N toggles the /// view, Mod+Ctrl+Shift+N toggles the window's tag. fn tagKeys() [action.tag_count * 4]Key { // Evaluated at comptime, so the loop costs nothing at runtime. @setEvalBranchQuota(10_000); var out: [action.tag_count * 4]Key = undefined; for (0..action.tag_count) |i| { const mask: u32 = @as(u32, 1) << @intCast(i); const sym: xkb.Keysym = @enumFromInt(@intFromEnum(xkb.Keysym.@"1") + i); out[i * 4 + 0] = .{ .mods = mod, .keysym = sym, .action = .{ .view = mask } }; out[i * 4 + 1] = .{ .mods = mod | Mods.shift, .keysym = sym, .action = .{ .tag = mask } }; out[i * 4 + 2] = .{ .mods = mod | Mods.ctrl, .keysym = sym, .action = .{ .toggle_view = mask } }; out[i * 4 + 3] = .{ .mods = mod | Mods.ctrl | Mods.shift, .keysym = sym, .action = .{ .toggle_tag = mask }, }; } return out; } // ─── Pointer bindings ──────────────────────────────────────────────────────── pub const buttons = [_]Button{ .{ .mods = mod, .button = btn.left, .action = .move }, .{ .mods = mod, .button = btn.right, .action = .resize }, };