Files
att_wm/src/config.zig
T

237 lines
11 KiB
Zig

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