This commit is contained in:
2026-05-12 23:19:12 +09:00
parent b19d9d25f7
commit cde37d516b
21 changed files with 634 additions and 52 deletions

View File

@@ -48,6 +48,7 @@ pub async fn run(
.req_stream(OpReq::Attach {
session: session.clone(),
connection_id: Some(target.connection_id),
shell_id: None,
pty,
cols,
rows,
@@ -68,7 +69,7 @@ pub async fn run(
}
ui::print_info(&format!(
"attached to #{} ({}@{}) — Ctrl-] to detach",
target.connection_id, target.info.user, target.info.hostname
target.connection_id, target.info.user, target.info.hostname,
));
if pty {
@@ -83,7 +84,7 @@ pub async fn run(
result
}
async fn pump(
pub async fn pump(
client: &AuthedClient,
resps: &mut tokio::sync::mpsc::Receiver<OpResp>,
pty: bool,
@@ -145,7 +146,7 @@ async fn pump(
}
}
fn spawn_resize_watch(tx: tokio::sync::mpsc::Sender<(u16, u16)>) {
pub fn spawn_resize_watch(tx: tokio::sync::mpsc::Sender<(u16, u16)>) {
tokio::spawn(async move {
let mut last = terminal::size().unwrap_or((80, 24));
loop {

View File

@@ -3,3 +3,4 @@ pub mod connection;
pub mod connect;
pub mod keys;
pub mod watch;
pub mod shell;

View File

@@ -0,0 +1,99 @@
use crate::auth::AuthedClient;
use crate::cmd::connect;
use crate::cmd::connection;
use crate::ui;
use anyhow::{anyhow, Result};
use crossterm::terminal;
use rsh_types::{OpReq, OpResp};
pub async fn run(
client: &AuthedClient,
session: String,
connection_id: Option<u64>,
shell: Option<String>,
no_pty: bool,
) -> Result<()> {
let conns = connection::fetch(client, Some(session.clone())).await?;
if conns.is_empty() {
return Err(anyhow!("no connections for session '{session}'"));
}
let conn_id = match connection_id {
Some(id) => {
conns
.iter()
.find(|c| c.connection_id == id)
.ok_or_else(|| anyhow!("no connection {id} in session '{session}'"))?
.connection_id
}
None => {
if conns.len() == 1 {
conns[0].connection_id
} else {
let labels: Vec<String> = conns
.iter()
.map(|c| format!("#{} {}@{} ({})", c.connection_id, c.info.user, c.info.hostname, ui::fmt_time(c.connected_at)))
.collect();
let pick = inquire::Select::new("select connection:", labels.clone())
.prompt()
.map_err(|e| anyhow!("prompt: {e}"))?;
let idx = labels.iter().position(|l| l == &pick).unwrap();
conns[idx].connection_id
}
}
};
let (cols, rows) = terminal::size().unwrap_or((80, 24));
let pty = !no_pty;
let spawned = client
.req(OpReq::SpawnShell {
session: session.clone(),
connection_id: Some(conn_id),
shell,
pty,
cols,
rows,
})
.await?;
let shell_id = match spawned {
OpResp::ShellSpawned { shell_id, .. } => shell_id,
OpResp::Err(e) => return Err(anyhow!(e)),
other => return Err(anyhow!("unexpected: {other:?}")),
};
let (attach_id, mut resps) = client
.req_stream(OpReq::Attach {
session: session.clone(),
connection_id: Some(conn_id),
shell_id: Some(shell_id),
pty,
cols,
rows,
})
.await?;
let ready = resps.recv().await.ok_or_else(|| anyhow!("attach: no response"))?;
match ready {
OpResp::AttachReady { .. } => {}
OpResp::Err(e) => {
client.drop_stream(attach_id).await;
return Err(anyhow!(e));
}
other => {
client.drop_stream(attach_id).await;
return Err(anyhow!("unexpected: {other:?}"));
}
}
ui::print_info(&format!("spawned shell in #{conn_id} — Ctrl-] to detach"));
if pty {
terminal::enable_raw_mode().ok();
}
let result = connect::pump(client, &mut resps, pty).await;
if pty {
terminal::disable_raw_mode().ok();
}
client.drop_stream(attach_id).await;
println!();
result
}