Compare commits

...

11 commits

Author SHA1 Message Date
0cda38f25b Merge branch 'main' of codeberg.org:muon/home 2026-03-08 15:55:05 +00:00
c9be88c1b6 Fix ud and ssh 2026-03-08 15:55:01 +00:00
e085afcf6d Add wezterm to muho 2026-03-08 15:54:36 +00:00
fb8b27ef3c Add murmur mumble server 2026-03-08 15:45:42 +00:00
5518f1ca35 Add atuin to nushell 2026-03-08 15:31:23 +00:00
5e84e6aba8 Add stacked panes 2026-03-08 15:19:42 +00:00
9433fc31ea Add window prefix 2026-03-08 14:41:32 +00:00
6187422574 Fix titles 2026-03-08 14:29:05 +00:00
8485d8007e Add wezterm zmenu 2026-03-08 14:16:19 +00:00
8e57979d3c Add wezterm bind 2026-03-08 11:43:20 +00:00
3a4353b990 Add wezterm binds 2026-03-08 11:39:06 +00:00
10 changed files with 627 additions and 200 deletions

View file

@ -46,48 +46,31 @@
}; };
utils = import ./utils.nix {inherit inputs system sources;}; utils = import ./utils.nix {inherit inputs system sources;};
# Discover hosts: all subdirectories of hosts/
hosts = builtins.attrNames (nixpkgs.lib.filterAttrs
(_: type: type == "directory")
(builtins.readDir ./hosts));
nixosConfigs = builtins.listToAttrs (map (host: {
name = host;
value = utils.mkHost ./hosts/${host}/configuration.nix;
}) hosts);
in { in {
nixosConfigurations = { nixosConfigurations = nixosConfigs;
# desktop
muon = utils.mkHost ./hosts/muon/configuration.nix;
# laptop
muop = utils.mkHost ./hosts/muop/configuration.nix;
# server
muho = utils.mkHost ./hosts/muho/configuration.nix;
# vps
mups = utils.mkHost ./hosts/mups/configuration.nix;
# vm
muvm = utils.mkHost ./hosts/muvm/configuration.nix;
# work
murk = utils.mkHost ./hosts/murk/configuration.nix;
# work desktop
musk = utils.mkHost ./hosts/musk/configuration.nix;
# lenovo
muvo = utils.mkHost ./hosts/muvo/configuration.nix;
# installer
muin = utils.mkHost ./hosts/muin/configuration.nix;
};
homeManagerModules.default = ./modules/home; homeManagerModules.default = ./modules/home;
# Expose each host's HM activation package so `home-manager switch --flake .#muon@<host>` # Standalone HM configurations — one per host.
# works without a full NixOS rebuild. Extracted from the already-evaluated # osConfig is injected so all modules using it continue to work.
# nixosConfiguration, so osConfig remains fully populated. # Use: home-manager switch --flake '.#muon@<host>'
homeConfigurations = nixpkgs.lib.mapAttrs' (host: nixos: homeConfigurations = builtins.listToAttrs (map (host: {
nixpkgs.lib.nameValuePair "muon@${host}" { name = "muon@${host}";
activationPackage = nixos.config.home-manager.users.muon.home.activationPackage; value = utils.mkHome {
} hostConfig = nixosConfigs.${host};
) (nixpkgs.lib.filterAttrs homeFile = ./hosts/${host}/home.nix;
(_: nixos: nixos.config.home-manager.users ? muon) };
inputs.self.outputs.nixosConfigurations); }) hosts);
colmena = { colmena = {
meta = { meta = {

View file

@ -50,6 +50,7 @@ in {
mods.server.ntfy.enable = true; mods.server.ntfy.enable = true;
mods.server.lemmy.enable = true; mods.server.lemmy.enable = true;
mods.server.audio.enable = true; mods.server.audio.enable = true;
mods.server.murmur.enable = true;
mods.server.atuin.enable = true; mods.server.atuin.enable = true;
mods.server.seedbox.enable = true; mods.server.seedbox.enable = true;

View file

@ -10,6 +10,8 @@ in {
mods.xdg.enable = true; mods.xdg.enable = true;
mods.social.enable = false; mods.social.enable = false;
mods.i3.enable = false; mods.i3.enable = false;
mods.terminal.wezterm.enable = true;
mods.terminal.nushell.enable = true;
mods.terminal.zsh.enable = true; mods.terminal.zsh.enable = true;
mods.terminal.emulator.enable = false; mods.terminal.emulator.enable = false;
mods.terminal.development.enable = true; mods.terminal.development.enable = true;

View file

@ -5,16 +5,91 @@
osConfig, osConfig,
... ...
}: let }: let
# Bootstrap script that runs as the first program inside a new wezterm window.
# It sets up the three tabs synchronously before the shell prompt appears,
# so by the time the window is visible the layout is already complete.
# Receives the project path as $1.
wezterm-zmenu-init = pkgs.writeShellApplication {
name = "wezterm-zmenu-init";
runtimeInputs = [pkgs.wezterm pkgs.zsh];
text = ''
ZPATH=$1
WIN=$(wezterm cli list --format json 2>/dev/null \
| tr ',' '\n' \
| awk -v pane="$WEZTERM_PANE" '
/window_id/ { gsub(/.*: */,""); w=int($0) }
/pane_id/ { gsub(/.*: */,""); if (int($0)==pane) { print w; exit } }')
P1=$(wezterm cli spawn --window-id "$WIN" --cwd "$ZPATH" -- direnv exec . nvim)
wezterm cli spawn --window-id "$WIN" --cwd "$ZPATH" > /dev/null
wezterm cli spawn --window-id "$WIN" --cwd "$ZPATH" -- lazygit > /dev/null
wezterm cli activate-tab --tab-index 0 --pane-id "$P1" 2>/dev/null
'';
};
zmenu = with pkgs; zmenu = with pkgs;
writeShellApplication { writeShellApplication {
name = "zmenu"; name = "zmenu";
runtimeInputs = [zellij zoxide wmctrl i3 rofi alacritty zsh]; runtimeInputs =
text = '' [zoxide wmctrl i3 rofi zsh]
++ lib.optionals config.mods.terminal.wezterm.enable [wezterm]
++ lib.optionals (!config.mods.terminal.wezterm.enable) [zellij alacritty];
text =
if config.mods.terminal.wezterm.enable
then ''
ZPATH=$(zoxide query -l | sed -e "s|$HOME/||g" | rofi -dmenu)
[[ -z "$ZPATH" ]] && exit
ZSESH=$(echo "$ZPATH" | tr / -)
# Per-workspace socket registry so we can focus existing workspaces.
SOCKDIR="$XDG_RUNTIME_DIR/zmenu-wez"
mkdir -p "$SOCKDIR"
SOCKFILE="$SOCKDIR/$ZSESH"
SOCK=""
if [[ -f "$SOCKFILE" ]]; then
SOCK=$(cat "$SOCKFILE")
# Extract the PID from the socket path (gui-sock-<pid>) and check
# if the process is still alive. Dead sockets linger on disk.
SOCK_PID=''${SOCK##*-}
if ! kill -0 "$SOCK_PID" 2>/dev/null; then
rm -f "$SOCKFILE"
SOCK=""
fi
fi
if [[ -n "$SOCK" ]]; then
# Workspace open: raise its window by matching the wezterm PID in
# wmctrl's process list, then activate a pane to ensure focus.
SOCK_PID=''${SOCK##*-}
WIN_HEX=$(wmctrl -lp | awk -v pid="$SOCK_PID" '$3==pid {print $1; exit}')
wmctrl -i -a "$WIN_HEX"
PANE=$(WEZTERM_UNIX_SOCKET="$SOCK" wezterm cli list --format json 2>/dev/null \
| tr ',' '\n' \
| awk '/pane_id/{gsub(/.*: */,""); print int($0); exit}')
WEZTERM_UNIX_SOCKET="$SOCK" wezterm cli activate-pane --pane-id "$PANE" 2>/dev/null
else
# Not open: the init script runs as the first program inside the new
# window. It sets up all tabs synchronously before the shell prompt
# appears, so the layout is complete before anything is visible.
wezterm start --always-new-process \
--workspace "$ZSESH" --cwd "$HOME/$ZPATH" \
-- ${lib.getExe wezterm-zmenu-init} "$HOME/$ZPATH" &
WEZ_PID=$!
# Record the socket for future focus calls.
for _ in $(seq 50); do
sleep 0.1
[[ -S /run/user/$UID/wezterm/gui-sock-$WEZ_PID ]] && break
done
echo "/run/user/$UID/wezterm/gui-sock-$WEZ_PID" > "$SOCKFILE"
fi
''
else ''
ZPATH=$(zoxide query -l | sed -e "s|$HOME/||g" | rofi -dmenu) ZPATH=$(zoxide query -l | sed -e "s|$HOME/||g" | rofi -dmenu)
[[ -z "$ZPATH" ]] && exit [[ -z "$ZPATH" ]] && exit
ZSESH=$(echo "$ZPATH" | tr / -) ZSESH=$(echo "$ZPATH" | tr / -)
ZWIND=$(wmctrl -l | grep "$ZSESH$" || echo "") ZWIND=$(wmctrl -l | grep "$ZSESH$" || echo "")
cd "$ZPATH" cd "$HOME/$ZPATH"
if [[ -z "$ZWIND" ]]; then if [[ -z "$ZWIND" ]]; then
alacritty -T "$ZSESH" -e zsh -c "zellij -s $ZSESH -n dev || zellij a $ZSESH" alacritty -T "$ZSESH" -e zsh -c "zellij -s $ZSESH -n dev || zellij a $ZSESH"
else else
@ -94,7 +169,10 @@ in
enable = true; enable = true;
config = { config = {
modifier = modifier; modifier = modifier;
terminal = "alacritty"; terminal =
if config.mods.terminal.wezterm.enable
then "wezterm"
else "alacritty";
menu = "rofi -show drun"; menu = "rofi -show drun";
window = { window = {

View file

@ -23,35 +23,43 @@ in {
shellAliases = aliases; shellAliases = aliases;
# vi mode + sensible defaults # vi mode + sensible defaults via flat assignments (avoids clobbering other modules)
extraConfig = '' settings = {
$env.config = { show_banner = false;
show_banner: false edit_mode = "vi";
edit_mode: vi cursor_shape = {
vi_insert = "line";
vi_normal = "block";
};
history = {
max_size = 2097152;
sync_on_enter = true;
file_format = "sqlite";
isolation = false;
};
completions = {
case_sensitive = false;
quick = true;
partial = true;
algorithm = "fuzzy";
};
table.mode = "rounded";
};
cursor_shape: { # Append the / keybinding after all integrations (including atuin) are sourced,
vi_insert: line # so _atuin_search_cmd is defined when this runs.
vi_normal: block extraConfig = lib.mkAfter ''
} $env.config = (
$env.config | upsert keybindings (
history: { $env.config.keybindings | append {
max_size: 2097152 name: atuin_search_vi_normal
sync_on_enter: true modifier: none
file_format: "sqlite" keycode: char_/
isolation: false mode: vi_normal
} event: { send: executehostcommand cmd: (_atuin_search_cmd) }
completions: {
case_sensitive: false
quick: true
partial: true
algorithm: "fuzzy"
}
table: {
mode: rounded
}
} }
)
)
''; '';
# Carry over zsh session variables # Carry over zsh session variables

View file

@ -42,6 +42,7 @@ in
atuin = { atuin = {
enable = true; enable = true;
enableZshIntegration = true; enableZshIntegration = true;
enableNushellIntegration = true;
flags = ["--disable-up-arrow"]; flags = ["--disable-up-arrow"];
settings = { settings = {
sync_frequency = "5m"; sync_frequency = "5m";

View file

@ -28,21 +28,52 @@
# vi directions: key, arrow alias, wezterm direction name # vi directions: key, arrow alias, wezterm direction name
viDirs = [ viDirs = [
{key = "h"; arrow = "LeftArrow"; dir = "Left";} {
{key = "j"; arrow = "DownArrow"; dir = "Down";} key = "h";
{key = "k"; arrow = "UpArrow"; dir = "Up";} arrow = "LeftArrow";
{key = "l"; arrow = "RightArrow"; dir = "Right";} dir = "Left";
}
{
key = "j";
arrow = "DownArrow";
dir = "Down";
}
{
key = "k";
arrow = "UpArrow";
dir = "Up";
}
{
key = "l";
arrow = "RightArrow";
dir = "Right";
}
]; ];
# hjkl + arrow equivalents; mkAction :: dir -> lua-action-string # hjkl + arrow equivalents; mkAction :: dir -> lua-action-string
viDirKeys = {mods ? null, indent ? " ", mkAction}: viDirKeys = {
mods ? null,
indent ? " ",
mkAction,
}:
lib.concatMapStrings (d: lib.concatMapStrings (d:
luaKey {inherit mods indent; key = d.key; action = mkAction d.dir;} + luaKey {
luaKey {inherit mods indent; key = d.arrow; action = mkAction d.dir;}) inherit mods indent;
key = d.key;
action = mkAction d.dir;
}
+ luaKey {
inherit mods indent;
key = d.arrow;
action = mkAction d.dir;
})
viDirs; viDirs;
# 1-9 tab-jump bindings # 1-9 tab-jump bindings
tabJumpKeys = {mods ? null, indent ? " "}: tabJumpKeys = {
mods ? null,
indent ? " ",
}:
lib.concatStrings (builtins.genList (i: lib.concatStrings (builtins.genList (i:
luaKey { luaKey {
inherit mods indent; inherit mods indent;
@ -52,46 +83,100 @@
9); 9);
# hjkl + arrows for AdjustPaneSize at a given step # hjkl + arrows for AdjustPaneSize at a given step
resizeDirKeys = {step, indent ? " "}: resizeDirKeys = {
step,
indent ? " ",
}:
lib.concatMapStrings (d: lib.concatMapStrings (d:
luaKey {inherit indent; key = d.key; action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";} + luaKey {
luaKey {inherit indent; key = d.arrow; action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";}) inherit indent;
key = d.key;
action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";
}
+ luaKey {
inherit indent;
key = d.arrow;
action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";
})
viDirs; viDirs;
# Uppercase HJKL fine-tune resize # Uppercase HJKL fine-tune resize
resizeShiftKeys = {step, indent ? " "}: resizeShiftKeys = {
step,
indent ? " ",
}:
lib.concatMapStrings (d: lib.concatMapStrings (d:
luaKey {inherit indent; key = lib.toUpper d.key; action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";}) luaKey {
inherit indent;
key = lib.toUpper d.key;
action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";
})
viDirs; viDirs;
# Wrap a lua action string so it also pops the key table (return to locked) # Wrap a lua action string so it also pops the key table (return to locked)
andPop = action: "act.Multiple({ ${action}, act.PopKeyTable })"; andPop = action: "act.Multiple({ ${action}, act.PopKeyTable })";
# Standard exit bindings, given the mode's own pop key # Standard exit bindings, given the mode's own pop key
exitKeys = {selfKey, indent ? " "}: exitKeys = {
selfKey,
indent ? " ",
}:
lib.concatStrings [ lib.concatStrings [
(luaKey {inherit indent; key = selfKey; action = "act.PopKeyTable";}) (luaKey {
(luaKey {inherit indent; key = "Escape"; action = "act.PopKeyTable";}) inherit indent;
(luaKey {inherit indent; key = "Enter"; action = "act.PopKeyTable";}) key = selfKey;
(luaKey {inherit indent; key = "Space"; mods = "ALT"; action = "act.PopKeyTable";}) action = "act.PopKeyTable";
})
(luaKey {
inherit indent;
key = "Escape";
action = "act.PopKeyTable";
})
(luaKey {
inherit indent;
key = "Enter";
action = "act.PopKeyTable";
})
(luaKey {
inherit indent;
key = "Space";
mods = "ALT";
action = "act.PopKeyTable";
})
]; ];
# ── Status bar ─────────────────────────────────────────────────────────────── # ── Status bar ───────────────────────────────────────────────────────────────
# Per-mode accent colour (base16) and hint string shown on the left status bar # Per-mode accent colour (base16) and hint string shown in the status bar
# Hints use nerd font glyphs: separators, icons, key labels
modes = { modes = {
locked = {fg = color.base03; hint = "Alt+Space:enter Alt+hl:focus/tab Alt+1-9:tab Alt+n:split C-ud:scroll";}; locked = {
normal = {fg = color.base0D; hint = "p:pane t:tab r:resize s:scroll n:split Esc:exit";}; fg = color.base03;
pane = {fg = color.base0B; hint = "hjkl:focus n/r:split d:split f:zoom x:close Esc:exit";}; hint = "󱁐:󰌌 hl:󰹳 ^ud:󰹹 1-9:󰓩 n:󰤻 s:󱇳 w:󰅙 t:󰓩 p:󱃔";
tab = {fg = color.base0C; hint = "hjkl:prev/next 1-9:jump n:new x:close r:rename Esc:exit";}; };
resize = {fg = color.base0A; hint = "hjkl:+5 HJKL:+1 Esc:exit";}; normal = {
scroll = {fg = color.base0E; hint = "jk:line ud:half hl:page f:search Esc:exit";}; fg = color.base0D;
hint = "t:󰐕 x:󰅙 r:󰩨 c:󰆏 s:󰍉 esc:󱊷";
};
resize = {
fg = color.base0A;
hint = "hjkl:󰁌 HJKL:󰁎 esc:󱊷";
};
copy_mode = {
fg = color.base0E;
hint = "hjkl:󰹳 v:󰒆 V:󰒇 ^v:󰒈 y:󰆏 /:󰍉 n/p:󰁹 esc:󱊷";
};
search_mode = {
fg = color.base08;
hint = "type:󰈙 ^n/^p:󰁹 ^r:󰑓 /esc:󰆐";
};
}; };
# Lua table literal mapping mode name -> { fg, hint } # Lua table literal mapping mode name -> { fg, hint }
modeTableEntries = lib.concatStringsSep "\n " (lib.mapAttrsToList (name: m: '' modeTableEntries = lib.concatStringsSep "\n " (lib.mapAttrsToList (name: m: ''
["${name}"] = { fg = "${m.fg}", hint = "${m.hint}" }, ["${name}"] = { fg = "${m.fg}", hint = "${m.hint}" },
'') modes); '')
modes);
in { in {
options.mods.terminal.wezterm.enable = lib.mkEnableOption "enables wezterm"; options.mods.terminal.wezterm.enable = lib.mkEnableOption "enables wezterm";
@ -122,6 +207,51 @@ in {
end) end)
end end
-- scroll_or_passthrough: Ctrl+key scrolls at the shell prompt but passes
-- the key through when a program has taken the alt screen (vim, less, fzf).
local function scroll_or_passthrough(key, pages)
return wezterm.action_callback(function(window, pane)
if pane:is_alt_screen_active() then
window:perform_action(act.SendKey({ key = key, mods = "CTRL" }), pane)
else
window:perform_action(act.ScrollByPage(pages), pane)
end
end)
end
-- tab_is_zoomed: true if any pane in the current tab is zoomed.
-- Used by move_vertical to decide whether to carry zoom forward.
local function tab_is_zoomed(pane)
for _, info in ipairs(pane:tab():panes_with_info()) do
if info.is_zoomed then return true end
end
return false
end
-- move_vertical: move focus Up/Down.
-- If any pane in the tab is currently zoomed (zoom-mode is "on" for this
-- tab), zoom the destination pane too stacked-panel behaviour.
-- Otherwise just move focus plainly.
local function move_vertical(direction)
return wezterm.action_callback(function(window, pane)
local neighbour = pane:tab():get_pane_direction(direction)
if neighbour == nil then return end
if tab_is_zoomed(pane) then
-- unzoom_on_switch_pane unzooms the current pane when we move;
-- then SetPaneZoomState(true) zooms the newly active pane.
window:perform_action(
act.Multiple({
act.ActivatePaneDirection(direction),
act.SetPaneZoomState(true),
}),
pane
)
else
window:perform_action(act.ActivatePaneDirection(direction), pane)
end
end)
end
-- Status bar -- Status bar
-- Per-mode accent colour and hint text, generated from Nix/stylix palette -- Per-mode accent colour and hint text, generated from Nix/stylix palette
@ -131,27 +261,107 @@ in {
local bg_bar = "${color.base01}" local bg_bar = "${color.base01}"
local fg_text = "${color.base05}" local fg_text = "${color.base05}"
local fg_dim = "${color.base03}"
local bg_main = "${color.base00}" local bg_main = "${color.base00}"
-- Render hints with the key in the mode accent colour and the icon dimmed.
-- Each hint token is "key:icon"; we colour them individually.
local function render_hints(hint, accent)
local cells = {}
for token in hint:gmatch("%S+") do
local key, icon = token:match("^(.-):(.)$")
if key and icon then
table.insert(cells, { Background = { Color = bg_bar } })
table.insert(cells, { Foreground = { Color = accent } })
table.insert(cells, { Text = key })
table.insert(cells, { Foreground = { Color = fg_text } })
table.insert(cells, { Text = ":" .. icon .. " " })
else
table.insert(cells, { Background = { Color = bg_bar } })
table.insert(cells, { Foreground = { Color = fg_dim } })
table.insert(cells, { Text = token .. " " })
end
end
return cells
end
wezterm.on("format-window-title", function(tab, _pane, _tabs, _panes, _config)
local ok, win = pcall(function() return wezterm.mux.get_window(tab.window_id) end)
if ok and win then
local ws = win:get_workspace()
if ws ~= "default" then
return ws .. " " .. tab.active_pane.title
end
end
return tab.active_pane.title
end)
-- format-tab-title: show "rank/total title" when the active tab is in
-- stacked (zoom) mode; otherwise show plain " title ".
-- Returning a cell table gives us explicit padding and colours;
-- returning a plain string loses the retro tab bar's built-in spacing.
local tab_bg_active = "${color.base0D}"
local tab_fg_active = "${color.base00}"
local tab_bg_inactive = "${color.base01}"
local tab_fg_inactive = "${color.base03}"
wezterm.on("format-tab-title", function(tab, _tabs, panes, _config, _hover, _max_width)
local title = (tab.tab_title and tab.tab_title ~= "") and tab.tab_title or tab.active_pane.title
local bg = tab.is_active and tab_bg_active or tab_bg_inactive
local fg = tab.is_active and tab_fg_active or tab_fg_inactive
-- Only compute stack position for the active tab.
-- The `panes` event parameter only contains the active pane snapshot
-- and lacks `top`, so we use wezterm.mux.get_tab():panes_with_info()
-- which returns full PaneInformation including top/is_zoomed/is_active.
if tab.is_active then
local mux_tab = wezterm.mux.get_tab(tab.tab_id)
if mux_tab then
local infos = mux_tab:panes_with_info()
table.sort(infos, function(a, b) return a.top < b.top end)
local any_zoomed = false
local active_rank, total = 0, 0
for _, p in ipairs(infos) do
total = total + 1
if p.is_active then active_rank = total end
if p.is_zoomed then any_zoomed = true end
end
if any_zoomed and total > 1 and active_rank > 0 then
title = active_rank .. "/" .. total .. " " .. title
end
end
end
return {
{ Background = { Color = bg } },
{ Foreground = { Color = fg } },
{ Text = " " .. title .. " " },
}
end)
wezterm.on("update-status", function(window, _pane) wezterm.on("update-status", function(window, _pane)
local kt = window:active_key_table() local kt = window:active_key_table()
local mode = kt or "locked" local mode = kt or "locked"
local info = mode_info[mode] or mode_info["locked"] local info = mode_info[mode] or mode_info["locked"]
-- Left: coloured mode badge + hint line window:set_left_status("")
window:set_left_status(wezterm.format({
{ Background = { Color = info.fg } },
{ Foreground = { Color = bg_main } },
{ Attribute = { Intensity = "Bold" } },
{ Text = " " .. mode:upper() .. " " },
"ResetAttributes",
{ Background = { Color = bg_bar } },
{ Foreground = { Color = fg_text } },
{ Text = " " .. info.hint .. " " },
"ResetAttributes",
}))
window:set_right_status("") -- Right: coloured hints then mode badge
local cells = { { Text = " " } }
for _, c in ipairs(render_hints(info.hint, info.fg)) do
table.insert(cells, c)
end
table.insert(cells, "ResetAttributes")
table.insert(cells, { Background = { Color = info.fg } })
table.insert(cells, { Foreground = { Color = bg_bar } })
table.insert(cells, { Text = "" })
table.insert(cells, { Foreground = { Color = bg_main } })
table.insert(cells, { Attribute = { Intensity = "Bold" } })
table.insert(cells, { Text = " " .. mode:upper() .. " " })
table.insert(cells, "ResetAttributes")
window:set_right_status(wezterm.format(cells))
end) end)
-- Appearance -- Appearance
@ -170,7 +380,29 @@ in {
config.default_cursor_style = "BlinkingBar" config.default_cursor_style = "BlinkingBar"
config.hide_tab_bar_if_only_one_tab = false config.hide_tab_bar_if_only_one_tab = false
config.use_fancy_tab_bar = false config.use_fancy_tab_bar = false
config.tab_bar_at_bottom = true config.tab_max_width = 24
config.colors = {
tab_bar = {
background = "${color.base01}",
active_tab = { bg_color = "${color.base0D}", fg_color = "${color.base00}", intensity = "Bold" },
inactive_tab = { bg_color = "${color.base01}", fg_color = "${color.base03}" },
inactive_tab_hover = { bg_color = "${color.base02}", fg_color = "${color.base04}" },
new_tab = { bg_color = "${color.base01}", fg_color = "${color.base03}" },
new_tab_hover = { bg_color = "${color.base02}", fg_color = "${color.base04}" },
},
}
config.tab_bar_at_bottom = false
-- SSH Domains
config.ssh_domains = {
{
name = "muho",
remote_address = "muho",
username = "muon",
},
}
-- Shell -- Shell
@ -182,18 +414,23 @@ in {
config.scrollback_lines = 10000 config.scrollback_lines = 10000
-- Stacked panes
-- When switching away from a zoomed pane, unzoom it automatically.
-- Paired with stack_focus (Alt+j/k) this gives Zellij-style stacked panels.
config.unzoom_on_switch_pane = true
-- Key bindings -- Key bindings
-- --
-- Default mode = "locked" (all keys pass through to the shell). -- Default mode = "locked" (all keys pass through to the shell).
-- Alt+Space enters "normal" mode (like Zellij). -- Alt+Space enters "normal" mode.
-- From normal: ppane ttab rresize sscroll -- From normal: tnew tab xclose tab p/nsplit rresize mode
-- Esc / Enter / Alt+Space back to locked from any mode. -- Esc / Enter / Alt+Space back to locked from any mode.
config.disable_default_key_bindings = true config.disable_default_key_bindings = true
config.keys = { config.keys = {
-- Alt+h/l: move focus or switch tab at edge (Zellij MoveFocusOrTab) -- Alt+h/l: move focus or switch tab at edge (Zellij MoveFocusOrTab)
-- Alt+j/k: move focus between panes -- Alt+j/k: initial bindings (overridden below by stacked-focus variants)
${viDirKeys { ${viDirKeys {
mods = "ALT"; mods = "ALT";
indent = " "; indent = " ";
@ -203,9 +440,27 @@ in {
else "act.ActivatePaneDirection(\"${d}\")"; else "act.ActivatePaneDirection(\"${d}\")";
}} }}
-- Alt+1-9: jump to tab by number -- Alt+1-9: jump to tab by number
${tabJumpKeys {mods = "ALT"; indent = " ";}} ${tabJumpKeys {
mods = "ALT";
indent = " ";
}}
-- Alt+n: new pane (split right) -- Alt+n: new pane (split right)
{ key = "n", mods = "ALT", action = act.SplitHorizontal({ domain = "CurrentPaneDomain" }) }, { key = "n", mods = "ALT", action = act.SplitHorizontal({ domain = "CurrentPaneDomain" }) },
-- Alt+s: split below, then zoom the new pane so stacked mode starts.
-- Two separate perform_action calls: the split runs first and focuses
-- the new pane, then the second call zooms window:active_pane() which
-- is now the new pane (not the original `pane` argument).
{ key = "s", mods = "ALT", action = wezterm.action_callback(function(window, pane)
window:perform_action(act.SplitVertical({ domain = "CurrentPaneDomain" }), pane)
window:perform_action(act.SetPaneZoomState(true), window:active_pane())
end)
},
-- Alt+j/k: move_vertical plain focus move normally, stacked zoom
-- when any pane in the tab is already zoomed (mirrors Alt+f state).
{ key = "j", mods = "ALT", action = move_vertical("Down") },
{ key = "k", mods = "ALT", action = move_vertical("Up") },
{ key = "DownArrow", mods = "ALT", action = move_vertical("Down") },
{ key = "UpArrow", mods = "ALT", action = move_vertical("Up") },
-- Alt+f: toggle zoom -- Alt+f: toggle zoom
{ key = "f", mods = "ALT", action = act.TogglePaneZoomState }, { key = "f", mods = "ALT", action = act.TogglePaneZoomState },
-- Alt+[/]: rotate panes -- Alt+[/]: rotate panes
@ -218,11 +473,31 @@ in {
-- Alt+i/o: reorder tabs -- Alt+i/o: reorder tabs
{ key = "i", mods = "ALT", action = act.MoveTabRelative(-1) }, { key = "i", mods = "ALT", action = act.MoveTabRelative(-1) },
{ key = "o", mods = "ALT", action = act.MoveTabRelative(1) }, { key = "o", mods = "ALT", action = act.MoveTabRelative(1) },
-- Alt+w: close current pane; if tab was in stacked (zoom) mode, re-zoom
-- the new active pane so zoom mode persists across close.
-- confirm=false because the callback must run synchronously after close;
-- a confirm dialog would make the zoom fire before the user answers.
-- Guard: only re-zoom when there will still be panes left after close.
{ key = "w", mods = "ALT", action = wezterm.action_callback(function(window, pane)
local panes = pane:tab():panes()
local was_zoomed = tab_is_zoomed(pane)
window:perform_action(act.CloseCurrentPane({ confirm = false }), pane)
if was_zoomed and #panes > 1 then
window:perform_action(act.SetPaneZoomState(true), window:active_pane())
end
end)
},
-- Alt+p: command palette
{ key = "p", mods = "ALT", action = act.ActivateCommandPalette },
-- Alt+t: tab navigator (searchable tab list)
{ key = "t", mods = "ALT", action = act.ShowTabNavigator },
-- Alt+q: quit -- Alt+q: quit
{ key = "q", mods = "ALT", action = act.QuitApplication }, { key = "q", mods = "ALT", action = act.QuitApplication },
-- Ctrl+U/D: scroll half page (matches Alacritty) -- Ctrl+U/D: scroll half page when at the shell prompt; pass through
{ key = "u", mods = "CTRL", action = act.ScrollByPage(-0.5) }, -- to the running program when it is using the alt screen (vim, less,
{ key = "d", mods = "CTRL", action = act.ScrollByPage(0.5) }, -- fzf, etc. switch to the alt screen and need the raw key).
{ key = "u", mods = "CTRL", action = scroll_or_passthrough("u", -0.5) },
{ key = "d", mods = "CTRL", action = scroll_or_passthrough("d", 0.5) },
-- Copy / paste -- Copy / paste
{ key = "c", mods = "CTRL|SHIFT", action = act.CopyTo("Clipboard") }, { key = "c", mods = "CTRL|SHIFT", action = act.CopyTo("Clipboard") },
{ key = "v", mods = "CTRL|SHIFT", action = act.PasteFrom("Clipboard") }, { key = "v", mods = "CTRL|SHIFT", action = act.PasteFrom("Clipboard") },
@ -234,53 +509,12 @@ in {
-- NORMAL -- NORMAL
normal = { normal = {
{ key = "p", action = act.ActivateKeyTable({ name = "pane", one_shot = false }) }, { key = "t", action = ${andPop "act.SpawnTab(\"CurrentPaneDomain\")"} },
{ key = "t", action = act.ActivateKeyTable({ name = "tab", one_shot = false }) },
{ key = "r", action = act.ActivateKeyTable({ name = "resize", one_shot = false }) },
{ key = "s", action = act.ActivateKeyTable({ name = "scroll", one_shot = false }) },
{ key = "n", action = ${andPop "act.SplitHorizontal({ domain = \"CurrentPaneDomain\" })"} },
${exitKeys {selfKey = "Escape";}} },
-- PANE
pane = {
${viDirKeys {
mkAction = d:
if d == "Left" || d == "Right"
then "move_focus_or_tab(\"${d}\")"
else "act.ActivatePaneDirection(\"${d}\")";
}}
{ key = "n", action = ${andPop "act.SplitHorizontal({ domain = \"CurrentPaneDomain\" })"} },
{ key = "r", action = ${andPop "act.SplitHorizontal({ domain = \"CurrentPaneDomain\" })"} },
{ key = "d", action = ${andPop "act.SplitVertical({ domain = \"CurrentPaneDomain\" })"} },
{ key = "f", action = ${andPop "act.TogglePaneZoomState"} },
{ key = "z", action = ${andPop "act.TogglePaneZoomState"} },
{ key = "x", action = ${andPop "act.CloseCurrentPane({ confirm = true })"} },
{ key = "Tab", action = act.ActivatePaneDirection("Next") },
${exitKeys {selfKey = "p";}} },
-- TAB
tab = {
{ key = "h", action = act.ActivateTabRelative(-1) },
{ key = "k", action = act.ActivateTabRelative(-1) },
{ key = "j", action = act.ActivateTabRelative(1) },
{ key = "l", action = act.ActivateTabRelative(1) },
{ key = "LeftArrow", action = act.ActivateTabRelative(-1) },
{ key = "UpArrow", action = act.ActivateTabRelative(-1) },
{ key = "RightArrow", action = act.ActivateTabRelative(1) },
{ key = "DownArrow", action = act.ActivateTabRelative(1) },
{ key = "Tab", action = act.ActivateTabRelative(1) },
${tabJumpKeys {}}
{ key = "n", action = ${andPop "act.SpawnTab(\"CurrentPaneDomain\")"} },
{ key = "x", action = ${andPop "act.CloseCurrentTab({ confirm = true })"} }, { key = "x", action = ${andPop "act.CloseCurrentTab({ confirm = true })"} },
{ key = "r", action = act.PromptInputLine({ { key = "r", action = act.ActivateKeyTable({ name = "resize", one_shot = false }) },
description = "Rename tab", { key = "c", action = ${andPop "act.ActivateCopyMode"} },
action = wezterm.action_callback(function(window, _, line) { key = "s", action = ${andPop "act.Search({ CaseSensitiveString = \"\" })"} },
if line then window:active_tab():set_title(line) end ${exitKeys {selfKey = "Escape";}} },
window:perform_action(act.PopKeyTable, window:active_pane())
end),
})
},
${exitKeys {selfKey = "t";}} },
-- RESIZE -- RESIZE
resize = { resize = {
@ -290,21 +524,71 @@ in {
{ key = "=", action = act.AdjustPaneSize({ "Right", 5 }) }, { key = "=", action = act.AdjustPaneSize({ "Right", 5 }) },
${exitKeys {selfKey = "r";}} }, ${exitKeys {selfKey = "r";}} },
-- SCROLL -- COPY MODE
scroll = { copy_mode = {
{ key = "j", action = act.ScrollByLine(1) }, -- movement
{ key = "k", action = act.ScrollByLine(-1) }, { key = "h", action = act.CopyMode("MoveLeft") },
{ key = "d", action = act.ScrollByPage(0.5) }, { key = "j", action = act.CopyMode("MoveDown") },
{ key = "u", action = act.ScrollByPage(-0.5) }, { key = "k", action = act.CopyMode("MoveUp") },
{ key = "h", action = act.ScrollByPage(-1) }, { key = "l", action = act.CopyMode("MoveRight") },
{ key = "l", action = act.ScrollByPage(1) }, { key = "LeftArrow", action = act.CopyMode("MoveLeft") },
{ key = "DownArrow", action = act.ScrollByLine(1) }, { key = "DownArrow", action = act.CopyMode("MoveDown") },
{ key = "UpArrow", action = act.ScrollByLine(-1) }, { key = "UpArrow", action = act.CopyMode("MoveUp") },
{ key = "PageDown", action = act.ScrollByPage(1) }, { key = "RightArrow", action = act.CopyMode("MoveRight") },
{ key = "PageUp", action = act.ScrollByPage(-1) }, { key = "w", action = act.CopyMode("MoveForwardWord") },
{ key = "f", action = act.Search({ CaseSensitiveString = "" }) }, { key = "b", action = act.CopyMode("MoveBackwardWord") },
{ key = "c", mods = "CTRL", action = act.ScrollToBottom }, { key = "e", action = act.CopyMode("MoveForwardWordEnd") },
${exitKeys {selfKey = "s";}} }, { key = "0", action = act.CopyMode("MoveToStartOfLine") },
{ key = "^", action = act.CopyMode("MoveToStartOfLineContent") },
{ key = "$", action = act.CopyMode("MoveToEndOfLineContent") },
{ key = "g", action = act.CopyMode("MoveToScrollbackTop") },
{ key = "G", action = act.CopyMode("MoveToScrollbackBottom") },
{ key = "Enter", action = act.CopyMode("MoveToStartOfNextLine") },
{ key = "u", mods = "CTRL", action = act.CopyMode({ MoveByPage = -0.5 }) },
{ key = "d", mods = "CTRL", action = act.CopyMode({ MoveByPage = 0.5 }) },
{ key = "b", mods = "CTRL", action = act.CopyMode("PageUp") },
{ key = "f", mods = "CTRL", action = act.CopyMode("PageDown") },
{ key = "PageUp", action = act.CopyMode("PageUp") },
{ key = "PageDown", action = act.CopyMode("PageDown") },
-- selection
{ key = "v", action = act.CopyMode({ SetSelectionMode = "Cell" }) },
{ key = "V", action = act.CopyMode({ SetSelectionMode = "Line" }) },
{ key = "v", mods = "CTRL", action = act.CopyMode({ SetSelectionMode = "Block" }) },
{ key = "o", action = act.CopyMode("MoveToSelectionOtherEnd") },
{ key = "O", action = act.CopyMode("MoveToSelectionOtherEndHoriz") },
-- yank and exit
{ key = "y", action = act.Multiple({
act.CopyTo("ClipboardAndPrimarySelection"),
act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }),
})
},
-- search within copy mode
{ key = "/", action = act.Search({ CaseSensitiveString = "" }) },
{ key = "n", action = act.CopyMode("NextMatch") },
{ key = "p", action = act.CopyMode("PriorMatch") },
-- exit
{ key = "q", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) },
{ key = "Escape", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) },
{ key = "c", mods = "CTRL", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) },
},
-- SEARCH MODE
search_mode = {
-- navigate matches
{ key = "Enter", action = act.CopyMode("AcceptPattern") },
{ key = "n", mods = "CTRL", action = act.CopyMode("NextMatch") },
{ key = "p", mods = "CTRL", action = act.CopyMode("PriorMatch") },
{ key = "PageUp", action = act.CopyMode("PriorMatchPage") },
{ key = "PageDown", action = act.CopyMode("NextMatchPage") },
{ key = "UpArrow", action = act.CopyMode("PriorMatch") },
{ key = "DownArrow", action = act.CopyMode("NextMatch") },
-- cycle match type (case-sensitive / insensitive / regex)
{ key = "r", mods = "CTRL", action = act.CopyMode("CycleMatchType") },
-- clear search input
{ key = "u", mods = "CTRL", action = act.CopyMode("ClearPattern") },
-- Escape: dismiss search bar, return to copy mode at current match
{ key = "Escape", action = act.CopyMode("AcceptPattern") },
},
} }
return config return config

View file

@ -1,4 +1,8 @@
{ pkgs, lib, ... }: { {
pkgs,
lib,
...
}: {
imports = [ imports = [
./containers ./containers
./gaming ./gaming
@ -25,5 +29,6 @@
./lemmy.nix ./lemmy.nix
./audio.nix ./audio.nix
./atuin.nix ./atuin.nix
./murmur.nix
]; ];
} }

View file

@ -0,0 +1,25 @@
{
pkgs,
lib,
config,
...
}: let
cfg = config.mods.server.murmur;
in
with lib; {
options.mods.server = {
murmur = {
enable = mkEnableOption {
default = false;
description = "enables murmur server";
};
};
};
config = mkIf cfg.enable {
services.murmur = {
enable = true;
openFirewall = true;
};
};
}

View file

@ -1,4 +1,7 @@
{ inputs, system, sources, ... }: { { inputs, system, sources, ... }:
let
pkgs = import inputs.nixpkgs { inherit system; };
in {
mkHost = host: mkHost = host:
inputs.nixpkgs.lib.nixosSystem { inputs.nixpkgs.lib.nixosSystem {
specialArgs = { inherit inputs system sources; }; specialArgs = { inherit inputs system sources; };
@ -11,4 +14,41 @@
inputs.impermanence.nixosModules.impermanence inputs.impermanence.nixosModules.impermanence
]; ];
}; };
# Build a standalone HM configuration for a given host NixOS config and
# home.nix file. osConfig is injected as a specialArg so all modules that
# reference it continue to work.
mkHome = { hostConfig, homeFile, username ? "muon" }:
inputs.home-manager.lib.homeManagerConfiguration {
inherit pkgs;
extraSpecialArgs = {
inherit inputs system sources;
osConfig = hostConfig.config;
};
modules = [
homeFile
inputs.self.outputs.homeManagerModules.default
inputs.stylix.homeManagerModules.stylix
({ osConfig, ... }: {
home.username = username;
home.homeDirectory = "/home/${username}";
# Mirror the stylix settings from the NixOS config so the standalone
# HM build has the same theme without re-declaring it.
stylix = {
enable = osConfig.stylix.enable;
autoEnable = osConfig.stylix.autoEnable;
base16Scheme = osConfig.stylix.base16Scheme;
image = osConfig.stylix.image;
cursor = osConfig.stylix.cursor;
# Forward individual font options; stylix derives fonts.packages
# internally as read-only so we must not forward that attr.
fonts.monospace = osConfig.stylix.fonts.monospace;
fonts.sansSerif = osConfig.stylix.fonts.sansSerif;
fonts.serif = osConfig.stylix.fonts.serif;
fonts.emoji = osConfig.stylix.fonts.emoji;
fonts.sizes = osConfig.stylix.fonts.sizes;
};
})
];
};
} }