laptop-config/home/macos.nix
2026-05-21 11:07:40 -07:00

270 lines
10 KiB
Nix

{ 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"
'';
};
}