Skip to content

feat(terminal): Xshell-style Ctrl+A+D session detach with background keep-alive - #76

Open
GOODBOY008 wants to merge 1 commit into
mainfrom
feat/xshell-ctrl-a-d-detach
Open

feat(terminal): Xshell-style Ctrl+A+D session detach with background keep-alive#76
GOODBOY008 wants to merge 1 commit into
mainfrom
feat/xshell-ctrl-a-d-detach

Conversation

@GOODBOY008

Copy link
Copy Markdown
Owner

Summary

Implements Xshell-style Ctrl+A+D to detach a terminal session into the background while the SSH connection and PTY channel stay alive. The remote shell keeps running and can be re-attached later, or terminated from the sidebar.

Closes #75

What changed

Backend (Rust)

  • connection_manager.rs: Added a detached_sessions registry (DetachSession { session, generation, drain_cancel }).
    • detach_pty_connection() moves a live PTY into the registry (SSH + PTY stay alive).
    • start_pty_connection() re-attaches an existing detached session instead of spawning a fresh shell.
    • close_detached_session(), has_detached_session(), list_detached_sessions().
    • A background drain task discards output while detached so the remote process never stalls on a full channel.
  • websocket_server.rs: New generation-guarded Detach WS message. Per-reader CancellationToken lets detach stop streaming without killing the session; StartPty re-attaches.
  • commands.rs / lib.rs: Registered list_detached_sessions, has_detached_session, close_detached_session.

Frontend (React)

  • pty-terminal.tsx: Ctrl+A is now a prefix key — Ctrl+A → D detaches; Ctrl+A → any other key forwards the buffered \x01 to the remote (tmux/screen friendly); Cmd+A still selects-all on macOS. On detach it sends the WS Detach, skips Close on unmount, and suppresses auto-reconnect.
  • App.tsx: handleDetachTab / handleReattachSession / handleCloseDetachedSession manage the detached session list.
  • connection-manager.tsx: "Detached Sessions" section in the sidebar with reattach (click) and terminate (trash) actions.
  • Context menus: "Detach Session" entries in both the terminal and tab-bar context menus.
  • terminal-detach-registry.ts (new): bridges tab-bar detach to the mounted terminal's WebSocket.
  • i18n: keys added to en.json and zh-CN.json.

Testing

  • cargo test: 111 passed (5 new tests for the detached registry).
  • pnpm test: 510 passed.
  • tsc --noEmit: clean.
  • pnpm i18n:check: passes.
  • pnpm lint: no new issues vs. base (3 pre-existing errors untouched).

Notes

  • Detached sessions are in-memory only (die with the app process), matching Xshell behavior.

…und keep-alive

Implements Ctrl+A+D to detach a terminal session into the background while
the SSH connection and PTY channel stay alive (Xshell detach behavior).

Backend (Rust):
- Add a detached-session registry to ConnectionManager with detach/reattach
  methods and a background output-drain task so the remote process never
  stalls while detached.
- Add a generation-guarded Detach WebSocket message; StartPty now re-attaches
  an existing detached session instead of spawning a fresh shell.
- Separate per-reader cancellation tokens so detaching stops streaming
  without killing the underlying SSH/PTY session.
- Add list_detached_sessions / has_detached_session / close_detached_session
  Tauri commands.

Frontend (React):
- Treat Ctrl+A as a prefix key: Ctrl+A then D detaches; any other key
  forwards the buffered Ctrl+A (\x01) to the remote so tmux/screen users are
  unaffected (Cmd+A still selects-all on macOS).
- Show detached sessions in the sidebar with reattach/terminate actions.
- Add Detach entries to the terminal and tab-bar context menus.
- Add i18n keys (en/zh-CN).
Copilot AI review requested due to automatic review settings July 31, 2026 15:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Xshell-style Ctrl+A → D detach for SSH PTY terminals, keeping the backend PTY + SSH session alive while the UI tab is removed, and enabling later re-attach/termination via the sidebar.

Changes:

  • Backend: introduce a detached-session registry, a WS Detach message, and new Tauri commands to query/close detached sessions.
  • Frontend: implement Ctrl+A prefix handling in PtyTerminal, add tab/sidebar actions for detach/reattach/close, and bridge tab-bar detach to the mounted terminal via a registry.
  • i18n: add strings for detach/reattach/close flows in en and zh-CN.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/locales/zh-CN.json Adds Chinese strings for detached session toasts/sidebar/context menu.
src/locales/en.json Adds English strings for detached session toasts/sidebar/context menu.
src/lib/terminal-detach-registry.ts New registry to let non-terminal UI (tab bar) trigger the terminal’s WS detach handshake.
src/lib/terminal-callbacks-context.tsx Adds an onDetachTab callback to the terminal callbacks interface.
src/components/terminal/terminal-tab-portals.tsx Wires onDetach into PtyTerminal instances rendered via portals.
src/components/terminal/terminal-group-view.tsx Passes onDetachTab down to the tab bar.
src/components/terminal/terminal-context-menu.tsx Adds “Detach Session” item to terminal context menu.
src/components/terminal/group-tab-bar.tsx Adds “Detach Session” item to tab-bar context menu.
src/components/pty-terminal.tsx Implements Ctrl+A prefix handling, WS Detach send, and cleanup rules for detached sessions.
src/components/connection-manager.tsx Adds “Detached Sessions” sidebar section with reattach/terminate actions.
src/App.tsx Tracks detached sessions in state and implements detach/reattach/close handlers.
src-tauri/src/websocket_server.rs Adds Detach WS message and per-reader cancellation tokens to stop streaming without killing the PTY session.
src-tauri/src/lib.rs Registers new detached-session Tauri commands.
src-tauri/src/connection_manager.rs Implements detached-session registry + drain task and adds unit tests.
src-tauri/src/commands.rs Adds Tauri commands: list/has/close detached sessions.

Comment on lines +674 to +684
tracing::info!(
"Detaching PTY connection: {} (gen: {:?})",
connection_id,
generation
);
// Stop the reader task so it no longer streams to this WS.
// The session itself is NOT cancelled — it stays alive in the
// backend so a later StartPty can re-attach to it.
if let Some(reader_cancel) = reader_tokens.lock().await.get(&connection_id) {
reader_cancel.cancel();
}
Comment thread src/App.tsx
Comment on lines +1079 to +1089
const handleCloseDetachedSession = useCallback(async (connectionId: string) => {
setDetachedSessions(prev => prev.filter(s => s.connectionId !== connectionId));
try {
await invoke('close_detached_session', { connection_id: connectionId });
toast.success(t('app.detachedSessionClosed'));
} catch (error) {
toast.error(t('app.detachedSessionCloseFailed'), {
description: error instanceof Error ? error.message : String(error),
});
}
}, [t]);
Comment thread src/App.tsx
Comment on lines +1011 to +1014
// If the tab-bar context menu initiated this, the PtyTerminal itself hasn't
// sent the Detach WS message yet — ask the mounted terminal to do so.
requestDetach(tabId);

Comment on lines +346 to +350
{/* Detach into background (SSH terminal tabs only) */}
{onDetachTab && tab.tabType !== 'file-browser' && tab.tabType !== 'desktop' && tab.tabType !== 'editor' && (
<>
<ContextMenuItem onClick={() => onDetachTab(tab.id)}>
<MonitorOff className="mr-2 h-4 w-4" />
Comment on lines +707 to +712
let session = crate::ssh::PtySession {
input_tx: tx,
output_rx: Arc::new(tokio::sync::Mutex::new(mpsc::channel::<Vec<u8>>(1).1)),
channel_id: unsafe { std::mem::transmute(0u32) },
resize_tx: rtx,
cancel: CancellationToken::new(),
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Xshell-style Ctrl+A+D session detach (keep session alive in background)

2 participants