{ config, pkgs, lib, ... }: { # === macOS-specific configuration === home.packages = with pkgs; [ # macOS window manager aerospace # Keyboard remapping (GUI app — download separately or install via brew) # brew install --cask karabiner-elements ]; # ============================================================================= # KEYBOARD GOAL: Consistent finger positions across keyboards # # ThinkPad T490 (Linux): Ctrl | Super(Win) | Alt | Space # Keychron V10M (macOS): Ctrl | Alt(⌥) | Cmd | Space ← remapped # After remap (macOS): Ctrl | Super(Cmd) | Alt | Space # # Muscle memory: # Middle key → Super/Cmd = WM actions (workspaces, focus, move) # Next to space → Alt/Option = app shortcuts # # Remap: Physical Alt(⌥) → Cmd(⌘), Physical Cmd(⌘) → Alt(⌥) # Applied via Karabiner-Elements, targeted to Keychron only. # # To find your Keychron's vendor/product IDs: # 1. Open Karabiner-Elements → EventViewer tab # 2. Press a key on the Keychron # 3. Note the "VendorID" and "ProductID" values # 4. Update them in the JSON below # # ============================================================================= home.file.".config/karabiner/karabiner.json" = { force = true; text = builtins.toJSON { global = { check_for_updates_on_startup = false; show_in_menu_bar = false; show_profile_name_in_menu_bar = false; }; profiles = [ { name = "Default"; selected = true; simple_modifications = []; fn_function_keys = []; virtual_hid_keyboard = { keyboard_type = "ansi"; caps_lock_delay_microseconds = 0; }; devices = []; complex_modifications = { parameters = { "basic.simultaneous_threshold_milliseconds" = 50; "basic.to_if_held_down_threshold_milliseconds" = 500; "basic.to_delayed_action_delay_milliseconds" = 500; "basic.to_if_alone_timeout_milliseconds" = 1000; }; rules = [ { description = "Keychron V10M: swap Option ↔ Command (match ThinkPad bottom row)"; manipulators = [ { # Physical Alt(⌥) → sends Cmd(⌘) = Super/WM key type = "basic"; from = { key_code = "left_alt"; modifiers = { optional = [ "any" ]; }; }; to = [ { key_code = "left_gui"; } ]; conditions = [ { type = "device_if"; identifiers = [ { # ⚠ UPDATE THESE with your Keychron's IDs vendor_id = 0; product_id = 0; description = "Keychron V10M (Alice 75)"; } ]; } ]; } { # Physical Cmd(⌘) → sends Alt(⌥) = Alt key type = "basic"; from = { key_code = "left_gui"; modifiers = { optional = [ "any" ]; }; }; to = [ { key_code = "left_alt"; } ]; conditions = [ { type = "device_if"; identifiers = [ { vendor_id = 0; product_id = 0; } ]; } ]; } ]; } ]; }; } ]; }; }; # ============================================================================= # AEROSPACE — macOS Tiling Window Manager # # Keybindings use `cmd` (Super after remap) to match Hyprland's SUPER: # Middle key + number → switch workspace # Middle key + h/j/k/l → focus direction # Middle key + tab → focus next window (cycle through all workspaces) # Middle key + shift → move window # # macOS-native Cmd+Tab (now Alt+Tab after remap) still cycles apps. # ============================================================================= home.file.".config/aerospace/aerospace.toml" = { force = true; text = '' # ============================================================================= # Aerospace — macOS Tiling Window Manager # # Keybinding conventions (after Keychron remap matches ThinkPad): # cmd (middle key) = SUPER — WM actions # alt (next to space) = ALT — app shortcuts # ============================================================================= # --- General --- [general] border = true gaps = { inner = { px = 6, vertical = 8, horizontal = 8 }, outer = { px = 4, px-monitor-workspace = 4, bottom = 6 }, } # ============================================================================= # WORKSPACE SWITCHING — cmd = middle key # ============================================================================= [mode.main.binding] # Switch to workspace cmd-1 = "workspace 1" cmd-2 = "workspace 2" cmd-3 = "workspace 3" cmd-4 = "workspace 4" cmd-5 = "workspace 5" cmd-6 = "workspace 6" cmd-7 = "workspace 7" cmd-8 = "workspace 8" cmd-9 = "workspace 9" # Move window to workspace cmd-shift-1 = "move-node-to-workspace 1" cmd-shift-2 = "move-node-to-workspace 2" cmd-shift-3 = "move-node-to-workspace 3" cmd-shift-4 = "move-node-to-workspace 4" cmd-shift-5 = "move-node-to-workspace 5" cmd-shift-6 = "move-node-to-workspace 6" cmd-shift-7 = "move-node-to-workspace 7" cmd-shift-8 = "move-node-to-workspace 8" cmd-shift-9 = "move-node-to-workspace 9" # ============================================================================= # FOCUS NAVIGATION — Vim-style with cmd (middle key) # ============================================================================= cmd-h = "focus left" cmd-j = "focus down" cmd-k = "focus up" cmd-l = "focus right" # Tab through windows across all workspaces cmd-tab = "focus next" # ============================================================================= # WINDOW MANAGEMENT # ============================================================================= cmd-f = "fullscreen" cmd-w = "close" cmd-t = "floating toggle" cmd-d = "layout tiles horizontal vertical" cmd-shift-d = "layout tiles vertical horizontal" # ============================================================================= # LAYOUT MANAGEMENT # ============================================================================= cmd-r = "mode resize" cmd-shift-h = "move left" cmd-shift-j = "move down" cmd-shift-k = "move up" cmd-shift-l = "move right" # ============================================================================= # RESIZE MODE # ============================================================================= [mode.resize.binding] cmd-h = "resize -50 0" cmd-j = "resize 0 50" cmd-k = "resize 0 -50" cmd-l = "resize 50 0" escape = "mode main" enter = "mode main" # ============================================================================= # MISC # ============================================================================= cmd-shift-period = "reload-config" cmd-backslash = "layout v_accordion" cmd-shift-slash = "layout h_accordion" # ============================================================================= # APP CYCLING # # macOS natively handles app cycling via Cmd+Tab. # After Karabiner remap, the physical key next to spacebar now sends Alt, # so what was Cmd+Tab is now available as a distinct chord. # We use cmd+tab (middle key + tab) for Aerospace's "focus next" above, # and the macOS native cycle is still accessible via alt+tab (next-to-space + tab). # ============================================================================= ''; }; # ============================================================================= # HELPER: Find Keychron keyboard IDs for Karabiner # ============================================================================= home.file.".local/bin/find-keychron-id" = { force = true; executable = true; text = '' #!/usr/bin/env bash # Find your Keychron's vendor/product IDs for Karabiner-Elements config. # # Usage: ./find-keychron-id # # If Karabiner EventViewer is running, check its log for recent key events. LOG="$HOME/.local/share/karabiner/eventviewer.log" if [ -f "$LOG" ]; then echo "Checking Karabiner EventViewer log..." grep -i "keychron\|vendor_id\|product_id" "$LOG" 2>/dev/null | tail -10 fi # Alternative: use ioreg to list HID devices echo "" echo "All HID keyboard devices (look for Keychron):" ioreg -p IOUSB -l -w 0 | grep -A 10 -i "keyboard\|keychron" 2>/dev/null || \\ ioreg -r -c IOUSBDevice -l -w 0 | grep -A 15 -i "keychron\|keyboard" | grep -E "vendor|product|idProduct|idVendor|USB Product Name" | head -20 echo "" echo "Or press a key on your Keychron and run:" echo " hidutil list 2>/dev/null | grep -i keychron" echo "" echo "Then update vendor_id and product_id (as DECIMAL numbers) in:" echo " ~/.config/karabiner/karabiner.json" echo "" echo " e.g. if vendor_id=0x3434, product_id=0x0408" echo " then in JSON: vendor_id=13364, product_id=1032" ''; }; }