diff --git a/modules/home/desktop/i3.nix b/modules/home/desktop/i3.nix index 80defe6..d472021 100644 --- a/modules/home/desktop/i3.nix +++ b/modules/home/desktop/i3.nix @@ -5,22 +5,100 @@ osConfig, ... }: 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 } }') + wezterm cli set-tab-title project 2>/dev/null + P2=$(wezterm cli spawn --window-id "$WIN" --cwd "$ZPATH") + wezterm cli set-tab-title --pane-id "$P2" test 2>/dev/null + P3=$(wezterm cli spawn --window-id "$WIN" --cwd "$ZPATH" -- lazygit) + wezterm cli set-tab-title --pane-id "$P3" git 2>/dev/null + wezterm cli activate-tab --tab-index 0 2>/dev/null + exec zsh -c "direnv exec . nvim" + ''; + }; + zmenu = with pkgs; writeShellApplication { name = "zmenu"; - runtimeInputs = [zellij zoxide wmctrl i3 rofi alacritty zsh]; - text = '' - ZPATH=$(zoxide query -l | sed -e "s|$HOME/||g" | rofi -dmenu) - [[ -z "$ZPATH" ]] && exit - ZSESH=$(echo "$ZPATH" | tr / -) - ZWIND=$(wmctrl -l | grep "$ZSESH$" || echo "") - cd "$ZPATH" - if [[ -z "$ZWIND" ]]; then - alacritty -T "$ZSESH" -e zsh -c "zellij -s $ZSESH -n dev || zellij a $ZSESH" - else - wmctrl -a "$ZSESH" - fi - ''; + runtimeInputs = + [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-) 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) + [[ -z "$ZPATH" ]] && exit + ZSESH=$(echo "$ZPATH" | tr / -) + ZWIND=$(wmctrl -l | grep "$ZSESH$" || echo "") + cd "$HOME/$ZPATH" + if [[ -z "$ZWIND" ]]; then + alacritty -T "$ZSESH" -e zsh -c "zellij -s $ZSESH -n dev || zellij a $ZSESH" + else + wmctrl -a "$ZSESH" + fi + ''; }; in with lib; { @@ -94,7 +172,10 @@ in enable = true; config = { modifier = modifier; - terminal = if config.mods.terminal.wezterm.enable then "wezterm" else "alacritty"; + terminal = + if config.mods.terminal.wezterm.enable + then "wezterm" + else "alacritty"; menu = "rofi -show drun"; window = {