diff --git a/.gitignore b/.gitignore index 6a19512..1b24980 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .crush .DS_Store +result diff --git a/dots/skhdrc b/dots/skhdrc new file mode 100644 index 0000000..50e4586 --- /dev/null +++ b/dots/skhdrc @@ -0,0 +1,49 @@ +# skhd configuration — replicates ~/.aerospace.toml key bindings + +# Window focus (vim/hjkl) +alt - h : yabai -m window --focus west +alt - j : yabai -m window --focus south +alt - k : yabai -m window --focus north +alt - l : yabai -m window --focus east + +# Move windows +alt + shift - h : yabai -m window --swap west +alt + shift - j : yabai -m window --swap south +alt + shift - k : yabai -m window --swap north +alt + shift - l : yabai -m window --swap east + +# Resize windows +alt + cmd - h : yabai -m window --resize left:-50:0 +alt + cmd - j : yabai -m window --resize bottom:0:50 +alt + cmd - k : yabai -m window --resize top:0:-50 +alt + cmd - l : yabai -m window --resize right:50:0 + +# Switch workspaces +alt - 1 : yabai -m space --focus 1 +alt - 2 : yabai -m space --focus 2 +alt - 3 : yabai -m space --focus 3 +alt - 4 : yabai -m space --focus 4 +alt - 5 : yabai -m space --focus 5 +alt - 6 : yabai -m space --focus 6 +alt - 7 : yabai -m space --focus 7 +alt - 8 : yabai -m space --focus 8 +alt - 9 : yabai -m space --focus 9 +alt - 0 : yabai -m space --focus 10 + +# Move window to workspace +alt + shift - 1 : yabai -m window --space 1 +alt + shift - 2 : yabai -m window --space 2 +alt + shift - 3 : yabai -m window --space 3 +alt + shift - 4 : yabai -m window --space 4 +alt + shift - 5 : yabai -m window --space 5 +alt + shift - 6 : yabai -m window --space 6 +alt + shift - 7 : yabai -m window --space 7 +alt + shift - 8 : yabai -m window --space 8 +alt + shift - 9 : yabai -m window --space 9 +alt + shift - 0 : yabai -m window --space 10 + +# Launch Ghostty terminal +alt - return : osascript ~/.ghostty-launch.osa + +# Close focused window +shift + cmd - q : yabai -m window --close diff --git a/dots/yabairc b/dots/yabairc new file mode 100644 index 0000000..0d53949 --- /dev/null +++ b/dots/yabairc @@ -0,0 +1,46 @@ +#!/usr/bin/env sh + +# yabai configuration — replicates ~/.aerospace.toml + +# Global settings +yabai -m config mouse_follows_focus on +yabai -m config focus_follows_mouse autofocus +yabai -m config window_placement second_child +yabai -m config split_ratio 0.50 +yabai -m config auto_balance off + +# Mouse modifier (alt + drag = move, alt + right drag = resize) +yabai -m config mouse_modifier alt +yabai -m config mouse_action1 move +yabai -m config mouse_action2 resize + +# Layout defaults (global) +yabai -m config layout bsp +yabai -m config split_type auto + +# Apply to all existing spaces +for SPACE in $(yabai -m query --spaces | jq -r '.[].index'); do + yabai -m config --space "$SPACE" layout bsp + yabai -m config --space "$SPACE" split_type auto + yabai -m config --space "$SPACE" top_padding 8 + yabai -m config --space "$SPACE" bottom_padding 8 + yabai -m config --space "$SPACE" left_padding 8 + yabai -m config --space "$SPACE" right_padding 8 + yabai -m config --space "$SPACE" window_gap 6 +done + +# Rules: assign apps to specific spaces +yabai -m rule --add app="^Dia$" space=1 +yabai -m rule --add app="^Slack$" space=8 +yabai -m rule --add app="^Vesktop$" space=7 +yabai -m rule --add app="^Notion$" space=9 + +# Rules: float these apps +yabai -m rule --add app="^UTM$" manage=off +yabai -m rule --add app="^VMware Fusion$" manage=off +yabai -m rule --add app="^FaceTime$" manage=off +yabai -m rule --add app="^Screen Sharing$" manage=off +yabai -m rule --add app="^Antinote$" manage=off +yabai -m rule --add app="^hw05$" manage=off + +echo "yabai config loaded" diff --git a/machines/atalanta/default.nix b/machines/atalanta/default.nix index 864781d..7e20eb9 100644 --- a/machines/atalanta/default.nix +++ b/machines/atalanta/default.nix @@ -140,6 +140,7 @@ persistent-apps = [ ]; tilesize = 47; + mru-spaces = false; show-recents = false; autohide = true; showhidden = true; @@ -149,6 +150,14 @@ largesize = 52; }; + # Regenerate sudoers entry with correct hash after every rebuild + system.activationScripts.yabai-sudoers.text = '' + YABAI="${pkgs.yabai}/bin/yabai" + HASH=$(shasum -a 256 "$YABAI" | cut -d ' ' -f 1) + echo "kierank ALL=(root) NOPASSWD: sha256:$HASH $YABAI --load-sa" > /private/etc/sudoers.d/yabai + chmod 440 /private/etc/sudoers.d/yabai + ''; + power.sleep = { computer = 1; display = 1; diff --git a/machines/atalanta/home/default.nix b/machines/atalanta/home/default.nix index 2c8eb36..387067b 100644 --- a/machines/atalanta/home/default.nix +++ b/machines/atalanta/home/default.nix @@ -18,7 +18,32 @@ sessionPath = [ "$HOME/.embedder/bin" ]; - }; + }; + + atelier.wm.yabai = { + enable = true; + config = { + global = { + mouse_follows_focus = true; + focus_follows_mouse = "autofocus"; + }; + rules = [ + "app=\"^Dia$\" space=1" + "app=\"^Slack$\" space=8" + "app=\"^Vesktop$\" space=7" + "app=\"^Notion$\" space=9" + "app=\"^UTM$\" manage=off" + "app=\"^VMware Fusion$\" manage=off" + "app=\"^FaceTime$\" manage=off" + "app=\"^Screen Sharing$\" manage=off" + "app=\"^Antinote$\" manage=off" + "app=\"^hw05$\" manage=off" + "app=\"^Finder$\" subrole=\"AXDialog\" manage=off" + "app=\"^Finder$\" title=\"^Copy$\" manage=off" + ]; + }; + skhdConfig = builtins.readFile ../../../dots/skhdrc; + }; atelier = { shell = { diff --git a/modules/home/wm/yabai/default.nix b/modules/home/wm/yabai/default.nix new file mode 100644 index 0000000..14af52f --- /dev/null +++ b/modules/home/wm/yabai/default.nix @@ -0,0 +1,226 @@ +{ + lib, + pkgs, + config, + ... +}: +let + cfg = config.atelier.wm.yabai; + yabaiExe = lib.getExe cfg.package; + + mkConfigLine = + setting: value: + if value == true then "yabai -m config ${setting} on" + else if value == false then "yabai -m config ${setting} off" + else "yabai -m config ${setting} ${toString value}"; + + globalSettings = lib.filterAttrs (_: v: v != null) cfg.config.global; + spaceSettings = lib.filterAttrs (_: v: v != null) cfg.config.space; + + globalLines = lib.mapAttrsToList mkConfigLine globalSettings; + spaceLines = lib.mapAttrsToList (setting: value: '' + yabai -m config --space $SPACE ${setting} ${toString value} + '') spaceSettings; + + ruleLines = map (r: "yabai -m rule --add ${r}") cfg.config.rules; + + scriptContent = '' + #!/usr/bin/env sh + + # Re-load scripting addition after dock restarts + yabai -m signal --add event=dock_did_restart action="sudo ${yabaiExe} --load-sa" + sudo ${yabaiExe} --load-sa + + # Global settings + ${lib.concatStringsSep "\n" globalLines} + + # Space settings (only for spaces that exist) + SPACE_COUNT=$(yabai -m query --spaces 2>/dev/null | ${lib.getExe pkgs.jq} 'length' 2>/dev/null) + if [ -z "$SPACE_COUNT" ] || [ "$SPACE_COUNT" -lt 1 ]; then + SPACE_COUNT=1 + fi + for SPACE in $(seq 1 "$SPACE_COUNT"); do + ${lib.concatStringsSep "\n " spaceLines} + done + + # Rules + ${lib.concatStringsSep "\n" ruleLines} + + ${cfg.extraConfig} + ''; + + skhdrcContent = cfg.skhdConfig + "\n" + cfg.extraSkhdConfig; +in +{ + options.atelier.wm.yabai = { + enable = lib.mkEnableOption "Enable yabai window manager and skhd hotkey daemon"; + + package = lib.mkPackageOption pkgs "yabai" { }; + + skhdPackage = lib.mkPackageOption pkgs "skhd" { }; + + config = { + global = lib.mkOption { + type = lib.types.submodule { + freeformType = with lib.types; attrsOf (oneOf [ bool int float str ]); + options = { + mouse_follows_focus = lib.mkOption { + type = lib.types.nullOr lib.types.bool; + default = true; + }; + focus_follows_mouse = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "autofocus"; + }; + window_placement = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "second_child"; + }; + layout = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "bsp"; + }; + split_type = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "auto"; + }; + split_ratio = lib.mkOption { + type = lib.types.nullOr (lib.types.oneOf [ lib.types.float lib.types.str ]); + default = 0.50; + }; + auto_balance = lib.mkOption { + type = lib.types.nullOr lib.types.bool; + default = false; + }; + mouse_modifier = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "alt"; + }; + mouse_action1 = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "move"; + }; + mouse_action2 = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "resize"; + }; + }; + }; + default = { }; + description = "Global yabai settings (mouse, layout, etc.)"; + }; + + space = lib.mkOption { + type = lib.types.submodule { + freeformType = with lib.types; attrsOf (oneOf [ bool int float str ]); + options = { + layout = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "bsp"; + }; + split_type = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "auto"; + }; + top_padding = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = 8; + }; + bottom_padding = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = 8; + }; + left_padding = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = 8; + }; + right_padding = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = 8; + }; + window_gap = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = 6; + }; + }; + }; + default = { }; + description = "Per-space yabai settings (padding, gap, layout)"; + }; + + rules = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "yabai rules (e.g. app=\"^Dia$\" space=1, app=\"^UTM$\" manage=off)"; + }; + }; + + extraConfig = lib.mkOption { + type = lib.types.lines; + default = ""; + description = "Extra raw lines appended to ~/.yabairc"; + }; + + skhdConfig = lib.mkOption { + type = lib.types.lines; + default = ""; + description = "skhd configuration (same format as ~/.skhdrc)"; + }; + + extraSkhdConfig = lib.mkOption { + type = lib.types.lines; + default = ""; + description = "Extra raw lines appended to skhdrc"; + }; + + borders = lib.mkOption { + type = with lib.types; attrsOf anything; + default = { + style = "round"; + width = 5.0; + hidpi = "on"; + active_color = "0xff394E9A"; + inactive_color = "0x33394E9A"; + }; + description = "JankyBorders settings (passed directly to bordersrc)"; + }; + }; + + config = lib.mkIf cfg.enable { + home.packages = [ cfg.package ]; + + home.file.".yabairc" = { + executable = true; + text = scriptContent; + }; + + home.activation.restartYabai = lib.hm.dag.entryAfter [ "writeBoundary" ] '' + $DRY_RUN_CMD /bin/launchctl kickstart -k gui/$UID/org.nix-community.home.yabai + ''; + + launchd.agents.yabai = { + enable = true; + config = { + ProgramArguments = [ + yabaiExe + "-c" + "${config.home.homeDirectory}/.yabairc" + ]; + ProcessType = "Interactive"; + KeepAlive = true; + RunAtLoad = true; + }; + }; + + services.skhd = { + enable = true; + package = cfg.skhdPackage; + config = skhdrcContent; + }; + + services.jankyborders = { + enable = true; + settings = cfg.borders; + }; + }; +}