Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions src/cli/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -840,15 +840,14 @@ pub async fn cmd_register(config: &Config, pid: Option<u32>, name: Option<String
})
.await?;
match resp {
Response::Ok(ResponseData::SessionRegistered { id, capture_path }) => {
// Print shell commands for the hook to eval.
// Use tee-based capture (same as adopted sessions) to preserve
// the terminal's direct relationship with the shell — tab titles,
// CWD tracking, colors, and all shell integrations work normally.
let escaped_path = capture_path.replace('\'', "'\\''");
Response::Ok(ResponseData::SessionRegistered { id, .. }) => {
// Only export the session ID — no exec, no redirect, no child
// processes. The shell continues completely unmodified so titles,
// CWD, colors, isatty(), and TUI apps all work normally.
// Output capture is not available for hooked sessions (snag output
// won't have scrollback), but ls/cwd/ps/attach/send all work via
// /proc and the stolen master fd.
println!("export SNAG_SESSION={id}");
println!("export SNAG_CAPTURE='{escaped_path}'");
println!("exec > >(tee -a '{escaped_path}') 2>&1");
Ok(())
}
Response::Error { message, .. } => {
Expand Down
13 changes: 12 additions & 1 deletion src/daemon/pty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use nix::pty::openpty;
use nix::sys::wait::WaitStatus;
use nix::unistd::{close, dup2, fork, setsid, ForkResult, Pid};
use std::ffi::CString;
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
use std::os::fd::{AsFd, AsRawFd, OwnedFd, RawFd};
use std::path::{Path, PathBuf};

pub struct SpawnResult {
Expand Down Expand Up @@ -184,3 +184,14 @@ pub fn fg_process(pts_path: &Path) -> Vec<(u32, String)> {

results
}

/// Get the foreground process name for a PTY via tcgetpgrp.
/// Returns "idle" if the shell is in the foreground, the command name
/// if another process is running, or None on error.
pub fn fg_process_name(master_fd: &impl AsFd, shell_pid: Option<Pid>) -> Option<String> {
let pgid = nix::unistd::tcgetpgrp(master_fd).ok()?;
if shell_pid.is_some_and(|sp| sp == pgid) {
return Some("idle".to_string());
}
read_comm(pgid.as_raw() as u32).or(Some("idle".to_string()))
}
17 changes: 1 addition & 16 deletions src/daemon/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,22 +100,7 @@ impl Session {
.and_then(|pid| pty::read_cwd(pid.as_raw() as u32))
.unwrap_or_else(|| "?".to_string());

let fg = pty::fg_process(&self.pts_path);
let fg_process = fg
.iter()
.find(|(pid, _)| {
self.child_pid
.map(|cp| *pid != cp.as_raw() as u32)
.unwrap_or(true)
})
.map(|(_, cmd)| cmd.clone())
.or_else(|| {
if fg.is_empty() {
None
} else {
Some("idle".to_string())
}
});
let fg_process = pty::fg_process_name(&self.master_fd, self.child_pid);

SessionInfo {
id: self.id.clone(),
Expand Down
Loading