From ade60ba5ece07259c972cbe454f8ab75f60a3855 Mon Sep 17 00:00:00 2001 From: John Ogle Date: Wed, 3 Dec 2025 10:30:39 -0800 Subject: [PATCH] Add macOS Ctrl keyboard shortcuts with terminal-aware overrides Implement Linux-style Ctrl shortcuts (Ctrl+C/V/X/Z for clipboard, Ctrl+N/T/W for navigation, etc.) while preserving terminal behavior where Ctrl+C sends SIGINT. Uses per-app NSUserKeyEquivalents to remap Ghostty back to Cmd for clipboard operations. Also consolidate aerospace configuration by moving spans-displays preference from system-level module to home-manager role, allowing full aerospace configuration to live in home-manager for better modularity. --- home/home-darwin-work.nix | 1 + home/roles/aerospace/default.nix | 107 ++++++++++++++++++-- machines/johno-macbookpro/configuration.nix | 11 -- modules/aerospace.nix | 30 ------ 4 files changed, 100 insertions(+), 49 deletions(-) delete mode 100644 modules/aerospace.nix diff --git a/home/home-darwin-work.nix b/home/home-darwin-work.nix index 03fc633..8d0e379 100644 --- a/home/home-darwin-work.nix +++ b/home/home-darwin-work.nix @@ -107,6 +107,7 @@ aerospace = { enable = true; leader = "cmd"; + ctrlShortcuts.enable = true; sketchybar.enable = true; # Optional: Add per-machine userSettings overrides # userSettings = { diff --git a/home/roles/aerospace/default.nix b/home/roles/aerospace/default.nix index 4e0d728..b638cea 100644 --- a/home/roles/aerospace/default.nix +++ b/home/roles/aerospace/default.nix @@ -62,6 +62,44 @@ in }; }; + enableSpansDisplays = mkOption { + type = types.bool; + default = true; + description = '' + Configure macOS Spaces to span displays (required for aerospace multi-monitor support). + Sets com.apple.spaces.spans-displays to true. + + NOTE: This was previously set at the system level in modules/aerospace.nix, + but has been moved to home-manager for better modularity. + ''; + }; + + ctrlShortcuts = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Remap common macOS Cmd shortcuts to Ctrl equivalents for all operations. + This makes macOS behave more like Linux. + + Shortcuts remapped globally: + - Ctrl+N: New Window + - Ctrl+T: New Tab + - Ctrl+W: Close Tab + - Ctrl+S: Save / Save As + - Ctrl+O: Open + - Ctrl+F: Find + - Ctrl+H: Find and Replace + - Ctrl+P: Print + - Ctrl+C/V/X: Copy/Paste/Cut + - Ctrl+Z: Undo + + NOTE: Terminal emulators like Ghostty require per-app overrides (configured separately) + to preserve Ctrl+C as SIGINT instead of Copy. + ''; + }; + }; + sketchybar = { enable = mkOption { type = types.bool; @@ -80,14 +118,67 @@ in } ]; - # Hide macOS menu bar when SketchyBar is enabled - # Note: Requires logout/login to take effect - targets.darwin.defaults = mkIf cfg.sketchybar.enable { - NSGlobalDomain = { - _HIHideMenuBar = true; - AppleMenuBarVisibleInFullscreen = false; - }; - }; + # Configure macOS preferences via targets.darwin.defaults + targets.darwin.defaults = mkMerge [ + # Spaces span displays (required for multi-monitor aerospace) + (mkIf cfg.enableSpansDisplays { + "com.apple.spaces" = { + spans-displays = true; + }; + }) + + # Ctrl shortcuts to make macOS behave more like Linux + (mkIf cfg.ctrlShortcuts.enable { + NSGlobalDomain.NSUserKeyEquivalents = { + # Window/Tab operations + "New Window" = "^n"; + "New Tab" = "^t"; + "Close Tab" = "^w"; + # File operations + "Save" = "^s"; + "Save As…" = "^$s"; # Ctrl+Shift+S + "Open" = "^o"; + "Open…" = "^o"; + # Find operations + "Find" = "^f"; + "Find…" = "^f"; + "Find and Replace" = "^h"; + "Find and Replace…" = "^h"; + # Print + "Print" = "^p"; + "Print…" = "^p"; + # Clipboard operations + "Copy" = "^c"; + "Paste" = "^v"; + "Cut" = "^x"; + # Undo/Redo + "Undo" = "^z"; + "Redo" = "^$z"; # Ctrl+Shift+Z + }; + }) + + # Ghostty-specific overrides to preserve terminal behavior + # Remap clipboard operations back to Cmd (macOS default) so Ctrl+C remains SIGINT + (mkIf cfg.ctrlShortcuts.enable { + "com.mitchellh.ghostty".NSUserKeyEquivalents = { + # Remap back to Cmd for clipboard operations + "Copy" = "@c"; # Cmd+C + "Paste" = "@v"; # Cmd+V + "Cut" = "@x"; # Cmd+X + "Undo" = "@z"; # Cmd+Z + "Redo" = "@$z"; # Cmd+Shift+Z + }; + }) + + # Hide macOS menu bar when SketchyBar is enabled + # Note: Requires logout/login to take effect + (mkIf cfg.sketchybar.enable { + NSGlobalDomain = { + _HIHideMenuBar = true; + AppleMenuBarVisibleInFullscreen = false; + }; + }) + ]; # Install aerospace package and optional tools if enabled home.packages = [ pkgs.aerospace ] diff --git a/machines/johno-macbookpro/configuration.nix b/machines/johno-macbookpro/configuration.nix index 56672d3..a1c7c95 100644 --- a/machines/johno-macbookpro/configuration.nix +++ b/machines/johno-macbookpro/configuration.nix @@ -1,10 +1,6 @@ { config, lib, pkgs, ... }: { - imports = [ - ../../modules/aerospace.nix - ]; - # Basic system configuration for macOS work laptop system.stateVersion = 6; @@ -18,13 +14,6 @@ NSGlobalDomain.AppleShowAllExtensions = true; }; - # Enable our custom aerospace system configuration - # (separate from nix-darwin's services.aerospace which manages the service) - roles.aerospace = { - enable = true; - enableSpansDisplays = true; - }; - # TODO: Find a way to not duplicate this launchd.user.envVariables = { # DOOM Emacs environment variables diff --git a/modules/aerospace.nix b/modules/aerospace.nix deleted file mode 100644 index f800a91..0000000 --- a/modules/aerospace.nix +++ /dev/null @@ -1,30 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - cfg = config.roles.aerospace; -in -{ - options.roles.aerospace = { - enable = mkEnableOption "AeroSpace window manager system configuration"; - - enableSpansDisplays = mkOption { - type = types.bool; - default = true; - description = '' - Configure macOS Spaces to span displays (required for aerospace multi-monitor support). - When enabled, sets com.apple.spaces.spans-displays to true. - ''; - }; - }; - - config = mkIf cfg.enable { - # Configure spaces to span displays (required for aerospace multi-monitor support) - system.defaults.CustomUserPreferences = mkIf cfg.enableSpansDisplays { - "com.apple.spaces" = { - spans-displays = true; - }; - }; - }; -}