This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

tools.workspace-manager: add on_create with tmux sessions and terminal windows

+111 -3
+13 -1
3modules/workspace-manager.nix
··· 24 24 default = [ ]; 25 25 description = "Commands to run when leaving this workspace"; 26 26 }; 27 + on_create = lib.mkOption { 28 + type = lib.types.listOf lib.types.str; 29 + default = [ ]; 30 + description = "Commands to run in a tmux session when the workspace is first entered. Each command gets its own tmux window with visible logs. Reattach with: tmux attach -t ws-<name>"; 31 + }; 27 32 }; 28 33 }; 29 34 ··· 33 38 name = "${name}.json"; 34 39 path = pkgs.writeText "${name}.json" ( 35 40 builtins.toJSON { 36 - inherit (ws) directory on_enter on_leave; 41 + inherit (ws) directory on_enter on_leave on_create; 37 42 } 38 43 ); 39 44 }) cfg.workspaces ··· 77 82 default = "~"; 78 83 description = "Default directory for workspaces without configuration"; 79 84 }; 85 + 86 + terminalCommand = lib.mkOption { 87 + type = lib.types.str; 88 + default = "kitty"; 89 + description = "Terminal emulator to use for on_create commands. Must support '-T title' for setting the window title."; 90 + }; 80 91 }; 81 92 82 93 config = lib.mkIf cfg.enable { ··· 93 104 "WORKSPACE_MANAGER_CONFIG_DIR=${cfg.configDir}" 94 105 "WORKSPACE_MANAGER_SYSTEM_CONFIG_DIR=${systemConfigDir}" 95 106 "WORKSPACE_MANAGER_DEFAULT_DIR=${cfg.defaultDirectory}" 107 + "WORKSPACE_MANAGER_TERMINAL=${cfg.terminalCommand}" 96 108 ]; 97 109 }; 98 110 };
+97 -1
tools/workspace-manager/daemon.py
··· 46 46 Path(_default_dir_env).expanduser() if _default_dir_env else Path.home() 47 47 ) 48 48 49 + TERMINAL_COMMAND = os.environ.get("WORKSPACE_MANAGER_TERMINAL", "kitty") 50 + 49 51 50 52 def load_config_file(config_file: Path) -> dict[str, Any] | None: 51 53 """Load a workspace config file, returning None if it doesn't exist or fails.""" ··· 71 73 self.directory: Path = DEFAULT_DIRECTORY 72 74 self.on_enter: list[str] = [] 73 75 self.on_leave: list[str] = [] 76 + self.on_create: list[str] = [] 74 77 self._load() 75 78 76 79 def _load(self) -> None: ··· 98 101 self.on_enter = list(merged["on_enter"]) 99 102 if "on_leave" in merged: 100 103 self.on_leave = list(merged["on_leave"]) 104 + if "on_create" in merged: 105 + self.on_create = list(merged["on_create"]) 101 106 102 107 103 108 class WorkspaceManager: ··· 106 111 def __init__(self) -> None: 107 112 self.current_workspace: str | None = None 108 113 self.configs: dict[str, WorkspaceConfig] = {} 114 + self.created_workspaces: set[str] = set() 109 115 110 116 def get_config(self, workspace: str) -> WorkspaceConfig: 111 117 """Get or create config for a workspace (configs are loaded fresh each time)""" ··· 120 126 return config.directory 121 127 return DEFAULT_DIRECTORY 122 128 129 + def _tmux_session_exists(self, session_name: str) -> bool: 130 + """Check if a tmux session already exists""" 131 + try: 132 + result = subprocess.run( 133 + ["tmux", "has-session", "-t", session_name], 134 + capture_output=True, 135 + ) 136 + return result.returncode == 0 137 + except FileNotFoundError: 138 + print("Warning: tmux not found", file=sys.stderr) 139 + return False 140 + 141 + async def _run_on_create(self, workspace: str, config: WorkspaceConfig) -> None: 142 + """Start on_create commands each in their own tmux session + terminal. 143 + 144 + For each command, creates a tmux session named <workspace>-start<N> 145 + running the command, then opens a terminal window attached to it. 146 + If the terminal is closed, reattach with: tmux attach -t <workspace>-start<N> 147 + """ 148 + if not config.on_create: 149 + return 150 + 151 + work_dir = str(config.directory) 152 + 153 + for i, cmd in enumerate(config.on_create, start=1): 154 + session_name = f"{workspace}-start{i}" 155 + 156 + # Skip if tmux session already exists (e.g. from a previous run) 157 + if self._tmux_session_exists(session_name): 158 + print( 159 + f"Tmux session '{session_name}' already exists, skipping", 160 + file=sys.stderr, 161 + ) 162 + continue 163 + 164 + # Create a detached tmux session running the command 165 + try: 166 + subprocess.run( 167 + [ 168 + "tmux", 169 + "new-session", 170 + "-d", 171 + "-s", 172 + session_name, 173 + "-c", 174 + work_dir, 175 + cmd, 176 + ], 177 + check=True, 178 + ) 179 + print( 180 + f"Created tmux session '{session_name}': {cmd}", 181 + file=sys.stderr, 182 + ) 183 + except (OSError, subprocess.CalledProcessError) as e: 184 + print( 185 + f"Failed to create tmux session '{session_name}': {e}", 186 + file=sys.stderr, 187 + ) 188 + continue 189 + 190 + # Open a terminal window attached to the tmux session 191 + try: 192 + subprocess.Popen( 193 + [ 194 + TERMINAL_COMMAND, 195 + "-T", 196 + session_name, 197 + "tmux", 198 + "attach", 199 + "-t", 200 + session_name, 201 + ], 202 + start_new_session=True, 203 + ) 204 + print( 205 + f"Opened terminal for tmux session '{session_name}'", 206 + file=sys.stderr, 207 + ) 208 + except OSError as e: 209 + print( 210 + f"Failed to open terminal for '{session_name}': {e}", 211 + file=sys.stderr, 212 + ) 213 + 123 214 async def handle_workspace_change( 124 215 self, old_workspace: str | None, new_workspace: str 125 216 ) -> None: ··· 133 224 # Update current workspace 134 225 self.current_workspace = new_workspace 135 226 136 - # Run on_enter hooks for new workspace 227 + # Run on_create hooks if this is the first time entering the workspace 137 228 new_config = self.get_config(new_workspace) 229 + if new_workspace not in self.created_workspaces: 230 + self.created_workspaces.add(new_workspace) 231 + await self._run_on_create(new_workspace, new_config) 232 + 233 + # Run on_enter hooks for new workspace 138 234 for hook in new_config.on_enter: 139 235 await self._run_hook(hook, "on_enter", new_workspace) 140 236
+1 -1
tools/workspace-manager/flake-module.nix
··· 38 38 cp daemon.py $out/bin/workspace-manager-daemon 39 39 chmod +x $out/bin/workspace-manager-daemon 40 40 wrapProgram $out/bin/workspace-manager-daemon \ 41 - --prefix PATH : ${python}/bin:${pkgs.sway}/bin 41 + --prefix PATH : ${python}/bin:${pkgs.sway}/bin:${pkgs.tmux}/bin 42 42 runHook postInstall 43 43 ''; 44 44 meta.mainProgram = "workspace-manager-daemon";