From c589cef163fddefcca977f130319f6b5e3657621 Mon Sep 17 00:00:00 2001 From: Ryan Schanzenbacher Date: Sat, 29 Aug 2026 11:32:16 -0400 Subject: Added vorsaint and settings file, refactor yabai --- modules/darwin/yabai/default.nix | 237 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 modules/darwin/yabai/default.nix (limited to 'modules') diff --git a/modules/darwin/yabai/default.nix b/modules/darwin/yabai/default.nix new file mode 100644 index 0000000..8e64dd1 --- /dev/null +++ b/modules/darwin/yabai/default.nix @@ -0,0 +1,237 @@ +# modules/darwin/yabai/default.nix +{ config, lib, pkgs, ... }: + +let + cfg = config.local.yabai; + + # symbolic hotkey IDs 118..126 == "Switch to Desktop 1".."Switch to Desktop 9" + spaceKeycodes = [ 18 19 20 21 22 23 25 26 28 ]; + + mkSpaceHotkey = i: { + name = toString (117 + i); + value = { + enabled = true; + value = { + type = "standard"; + parameters = [ + (48 + i) # ASCII '1'..'9' + (builtins.elemAt spaceKeycodes (i - 1)) # virtual keycode + cfg.spaceHotkeys.modifierMask + ]; + }; + }; + }; + + spaceHotkeys = builtins.listToAttrs + (map mkSpaceHotkey (lib.range 1 cfg.spaceHotkeys.count)); + + padArms = lib.concatStringsSep "\n" (lib.mapAttrsToList + (uuid: pad: " ${uuid}) pad=${toString pad} ;;") + cfg.displayTopPadding); + + displayPaddingScript = pkgs.writeShellScript "yabai-display-padding" '' + set -eu + yabai="${cfg.package}/bin/yabai" + + "$yabai" -m query --displays \ + | ${lib.getExe pkgs.jq} -r '.[] | "\(.uuid) \(.spaces | map(tostring) | join(" "))"' \ + | while read -r uuid spaces; do + case "$uuid" in +${padArms} + *) pad=${toString cfg.defaultPadding} ;; + esac + for s in $spaces; do + for k in top_padding; do + "$yabai" -m config --space "$s" "$k" "$pad" || true + done + done + done + ''; + + yabaiBin = "${cfg.package}/bin/yabai"; + + directionBinds = lib.concatStringsSep "\n" (map (d: '' + alt - ${d.key} : ${yabaiBin} -m window --focus ${d.dir} || ${yabaiBin} -m display --focus ${d.dir} + shift + alt - ${d.key} : ${yabaiBin} -m window --swap ${d.dir} || ${yabaiBin} -m window --display ${d.dir} + '') [ + { key = "up"; dir = "north"; } + { key = "down"; dir = "south"; } + { key = "right"; dir = "east"; } + { key = "left"; dir = "west"; } + ]); + + spaceMoveBinds = lib.concatStringsSep "\n" + (map (i: "shift + alt - ${toString i} : ${yabaiBin} -m window --space ${toString i}") + (lib.range 1 cfg.spaceHotkeys.count)); + +in { + options.local.yabai = { + enable = lib.mkEnableOption "yabai tiling window manager setup"; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.yabai; + description = "yabai package. Referenced by keybindings and the padding script."; + }; + + enableScriptingAddition = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Only gates space creation/destruction and moving windows between + spaces. Window geometry and padding work without it. + ''; + }; + + defaultPadding = lib.mkOption { + type = lib.types.int; + default = 10; + description = '' + Padding and window_gap for any display not listed in displayTopPadding. + Sized for Retina (2x) panels. + ''; + }; + + defaultTopPadding = lib.mkOption { + type = lib.types.int; + default = 10; + description = '' + Padding for top edge of screen + ''; + }; + + displayTopPadding = lib.mkOption { + type = lib.types.attrsOf lib.types.int; + default = { }; + example = lib.literalExpression '' + { "37D8832A-2D66-02CA-B9F7-8F30A301B230" = 40; } + ''; + description = '' + Display UUID -> padding value. The value is applied to all four + paddings and to window_gap for every space on that display. + + Get UUIDs with: + yabai -m query --displays | jq -r '.[] | "\(.uuid) \(.frame)"' + + Do not key off frame dimensions: yabai reports points, not pixels, + so a scaled 1080p panel will not report 1920x1080. + ''; + }; + + manageKeybindings = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Emit yabai skhd bindings into services.skhd.skhdConfig."; + }; + + spaceHotkeys = { + enable = lib.mkEnableOption "opt+ macOS space-switching hotkeys" // { + default = true; + }; + + count = lib.mkOption { + type = lib.types.ints.between 1 9; + default = 9; + description = "Number of spaces to bind. macOS exposes 9 symbolic hotkeys."; + }; + + modifierMask = lib.mkOption { + type = lib.types.int; + default = 524288; # option + description = "Carbon modifier mask. 524288 = option, 1048576 = command."; + }; + }; + + extraSymbolicHotkeys = lib.mkOption { + type = lib.types.attrs; + default = { }; + description = '' + Merged into com.apple.symbolichotkeys alongside the space hotkeys. + Put non-yabai symbolic hotkeys here rather than defining + CustomUserPreferences."com.apple.symbolichotkeys" in the host config -- + CustomUserPreferences is types.attrs and merges shallowly, so a second + definition of that domain silently clobbers this one. + ''; + }; + + extraSettings = lib.mkOption { + type = lib.types.attrs; + default = { }; + description = "Extra keys merged into services.yabai.config."; + }; + + extraConfig = lib.mkOption { + type = lib.types.lines; + default = ""; + description = "Extra shell appended to yabairc (rules, signals)."; + }; + }; + + config = lib.mkIf cfg.enable { + services.yabai = { + enable = true; + inherit (cfg) package enableScriptingAddition; + + config = { + mouse_follows_focus = "on"; + layout = "bsp"; + window_placement = "second_child"; + top_padding = toString cfg.defaultTopPadding; + bottom_padding = toString cfg.defaultPadding; + left_padding = toString cfg.defaultPadding; + right_padding = toString cfg.defaultPadding; + window_gap = toString cfg.defaultPadding; + mouse_modifier = "alt"; + mouse_drop_action = "swap"; + mouse_action1 = "move"; + mouse_action2 = "resize"; + focus_follows_mouse = "autofocus"; + } // cfg.extraSettings; + + extraConfig = '' + yabai -m rule --add app='System Settings' manage=off + yabai -m rule --add app="^Zen$" title="^Picture-in-Picture$" manage=off + '' + + lib.optionalString (cfg.displayTopPadding != { }) '' + + # Per-display padding. space_created is required, not redundant: + # display_added fires when yabai sees the display, which can precede + # macOS finishing space creation on it, so the first pass may find + # zero or one space. display_removed is required because yabai + # reindexes spaces on disconnect and the overrides are keyed by index. + yabai -m signal --add event=display_added action="${displayPaddingScript}" label="per-display padding (added)" + yabai -m signal --add event=display_removed action="${displayPaddingScript}" label="per-display padding (removed)" + yabai -m signal --add event=space_created action="${displayPaddingScript}" label="per-display padding (space)" + ${displayPaddingScript} + '' + + cfg.extraConfig; + }; + + services.skhd = lib.mkIf cfg.manageKeybindings { + enable = true; + skhdConfig = '' + shift + alt - q : ${yabaiBin} -m window --close + + ${directionBinds} + + shift + alt - space : ${yabaiBin} -m window --toggle float + shift + alt - return : ${yabaiBin} -m window --toggle sticky + alt - f : ${yabaiBin} -m window --toggle zoom-fullscreen + shift + alt - f : ${yabaiBin} -m window --toggle native-fullscreen + + ${spaceMoveBinds} + ''; + }; + + # Prerequisites, not preferences: yabai requires per-display spaces, and + # macOS's own tiling fights it for window placement. + system.defaults = { + spaces."spans-displays" = false; + WindowManager.EnableTilingOptionAccelerator = false; + + CustomUserPreferences."com.apple.symbolichotkeys".AppleSymbolicHotKeys = + cfg.extraSymbolicHotkeys + // (lib.optionalAttrs cfg.spaceHotkeys.enable spaceHotkeys); + }; + }; +} -- cgit v1.3.1