{ config, lib, pkgs, ... }: with lib; let cfg = config.programs.aerospace; in { options.programs.aerospace = { enable = mkEnableOption "AeroSpace tiling window manager for macOS"; leader = mkOption { type = types.str; default = "cmd"; description = "Leader key for aerospace shortcuts (e.g., 'cmd', 'ctrl', 'alt')"; example = "ctrl"; }; launchd.enable = mkOption { type = types.bool; default = true; description = "Whether to enable launchd agent for auto-starting aerospace"; }; userSettings = mkOption { type = types.attrs; default = {}; description = '' Additional aerospace configuration settings to merge with defaults. Use this to override or extend the default configuration on a per-machine basis. ''; example = literalExpression '' { mode.main.binding."''${leader}-custom" = "custom-command"; } ''; }; autoraise = { enable = mkOption { type = types.bool; default = true; description = "Whether to enable autoraise (auto-focus window on hover)"; }; pollMillis = mkOption { type = types.int; default = 50; description = "Polling interval in milliseconds"; }; delay = mkOption { type = types.int; default = 2; description = "Delay before raising window"; }; focusDelay = mkOption { type = types.int; default = 2; description = "Delay before focusing window"; }; }; }; config = mkIf cfg.enable { # Only apply on Darwin systems assertions = [ { assertion = pkgs.stdenv.isDarwin; message = "Aerospace module is only supported on macOS (Darwin) systems"; } ]; # Install aerospace package and autoraise if enabled home.packages = [ pkgs.aerospace ] ++ optionals cfg.autoraise.enable [ pkgs.autoraise ]; # Configure aerospace with user settings programs.aerospace.userSettings = mkMerge [ # Default configuration with leader key substitution { mode.main.binding = { "${cfg.leader}-slash" = "layout tiles horizontal vertical"; "${cfg.leader}-comma" = "layout accordion horizontal vertical"; "${cfg.leader}-shift-q" = "close"; "${cfg.leader}-shift-f" = "fullscreen"; "${cfg.leader}-h" = "focus left"; "${cfg.leader}-j" = "focus down"; "${cfg.leader}-k" = "focus up"; "${cfg.leader}-l" = "focus right"; "${cfg.leader}-shift-h" = "move left"; "${cfg.leader}-shift-j" = "move down"; "${cfg.leader}-shift-k" = "move up"; "${cfg.leader}-shift-l" = "move right"; "${cfg.leader}-minus" = "resize smart -50"; "${cfg.leader}-equal" = "resize smart +50"; "${cfg.leader}-1" = "workspace 1"; "${cfg.leader}-2" = "workspace 2"; "${cfg.leader}-3" = "workspace 3"; "${cfg.leader}-4" = "workspace 4"; "${cfg.leader}-5" = "workspace 5"; "${cfg.leader}-6" = "workspace 6"; "${cfg.leader}-7" = "workspace 7"; "${cfg.leader}-8" = "workspace 8"; "${cfg.leader}-9" = "workspace 9"; "${cfg.leader}-0" = "workspace 10"; "${cfg.leader}-shift-1" = "move-node-to-workspace 1"; "${cfg.leader}-shift-2" = "move-node-to-workspace 2"; "${cfg.leader}-shift-3" = "move-node-to-workspace 3"; "${cfg.leader}-shift-4" = "move-node-to-workspace 4"; "${cfg.leader}-shift-5" = "move-node-to-workspace 5"; "${cfg.leader}-shift-6" = "move-node-to-workspace 6"; "${cfg.leader}-shift-7" = "move-node-to-workspace 7"; "${cfg.leader}-shift-8" = "move-node-to-workspace 8"; "${cfg.leader}-shift-9" = "move-node-to-workspace 9"; "${cfg.leader}-shift-0" = "move-node-to-workspace 10"; "${cfg.leader}-tab" = "workspace-back-and-forth"; "${cfg.leader}-shift-tab" = "move-workspace-to-monitor --wrap-around next"; "${cfg.leader}-enter" = '' exec-and-forget osascript <<'APPLESCRIPT' tell application "Ghostty" activate tell application "System Events" keystroke "n" using {command down} end tell end tell APPLESCRIPT ''; "${cfg.leader}-shift-enter" = '' exec-and-forget osascript <<'APPLESCRIPT' tell application "Google Chrome" set newWindow to make new window activate tell newWindow to set index to 1 end tell APPLESCRIPT ''; "${cfg.leader}-shift-e" = "exec-and-forget zsh --login -c \"emacsclient -c -n\""; # Service mode: Deliberate aerospace window management "${cfg.leader}-i" = "mode service"; # Passthrough mode: Temporarily disable aerospace to use macOS shortcuts "${cfg.leader}-p" = "mode passthrough"; }; # Service mode: For deliberate aerospace window management operations mode.service.binding = { esc = ["reload-config" "mode main"]; r = ["flatten-workspace-tree" "mode main"]; # reset layout f = ["layout floating tiling" "mode main"]; # Toggle between floating and tiling layout backspace = ["close-all-windows-but-current" "mode main"]; "${cfg.leader}-shift-h" = ["join-with left" "mode main"]; "${cfg.leader}-shift-j" = ["join-with down" "mode main"]; "${cfg.leader}-shift-k" = ["join-with up" "mode main"]; "${cfg.leader}-shift-l" = ["join-with right" "mode main"]; }; # Passthrough mode: All shortcuts pass through to macOS mode.passthrough.binding = { esc = "mode main"; "${cfg.leader}-p" = "mode main"; }; } cfg.userSettings ]; # Launchd agent for auto-starting aerospace launchd.agents.aerospace = mkIf cfg.launchd.enable { enable = true; config = { Program = "${pkgs.aerospace}/Applications/AeroSpace.app/Contents/MacOS/AeroSpace"; RunAtLoad = true; KeepAlive = true; StandardOutPath = "/tmp/aerospace.log"; StandardErrorPath = "/tmp/aerospace.err.log"; }; }; # Launchd agent for autoraise launchd.agents.autoraise = mkIf cfg.autoraise.enable { enable = true; config = { ProgramArguments = [ "${pkgs.autoraise}/bin/AutoRaise" "-pollMillis" (toString cfg.autoraise.pollMillis) "-delay" (toString cfg.autoraise.delay) "-focusDelay" (toString cfg.autoraise.focusDelay) ]; RunAtLoad = true; KeepAlive = true; }; }; }; }