#[cfg(unix)]
use std::{
collections::HashMap,
io::Write,
sync::{Arc, Mutex},
time::Duration,
};
#[cfg(unix)]
use anyhow::{Context, Result};
#[cfg(unix)]
use ghosttea::{
FrameHub, HostShutdownAnnouncer, Session, SessionProgramKind, SessionRegistry, TextEngine,
session::{Persistence, SpawnOptions},
};
#[cfg(unix)]
use ghosttea_truffle::{TruffleTerminalConfig, serve_compact_loopback};
#[cfg(unix)]
const DEVICE_ID: &str = "compact-interop-device";
#[cfg(unix)]
const CLIENT_ID: &str = "truffle:compact-interop";
#[cfg(unix)]
const SESSION_SCRIPT: &str = "printf 'interop-line-1\\ninterop-line-2\\n'; exec cat";
#[cfg(not(unix))]
fn main() {
eprintln!("compact-interop-host is unix-only: it stages SIGSTOP/SIGCONT/SIGTERM scenarios");
std::process::exit(1);
}
#[cfg(unix)]
#[tokio::main]
async fn main() -> Result<()> {
let registry = SessionRegistry::default();
let session = Session::spawn(
SpawnOptions {
executable: "/bin/sh".to_owned(),
args: vec!["-c".to_owned(), SESSION_SCRIPT.to_owned()],
cwd: None,
env: HashMap::new(),
environment: None,
cols: 80,
rows: 24,
persistence: Persistence::TerminateWithApp,
program_kind: SessionProgramKind::default(),
owner_id: None,
},
FrameHub::new(8),
Arc::new(Mutex::new(
TextEngine::discover().context("discover text engine")?,
)),
Arc::new(|_, _| {}),
)
.context("spawn the interop session")?;
let session_id = session.id();
registry
.write()
.unwrap()
.insert(session_id.clone(), session);
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.context("bind the compact interop listener")?;
let port = listener.local_addr()?.port();
let ready = serde_json::json!({
"port": port,
"sessionId": session_id,
"deviceId": DEVICE_ID,
"clientId": CLIENT_ID,
"pid": std::process::id(),
});
println!("GHOSTTEA_COMPACT_INTEROP_READY {ready}");
std::io::stdout().flush().ok();
let shutdown = HostShutdownAnnouncer::new();
let config = TruffleTerminalConfig {
allow_tailnet_write: true,
..TruffleTerminalConfig::default()
};
let (_host_config, host_config) = tokio::sync::watch::channel(Arc::new(
ghosttea::ConfigSnapshot::default().terminal_presentation(),
));
tokio::select! {
served = serve_compact_loopback(
listener,
registry,
config,
DEVICE_ID.to_owned(),
CLIENT_ID.to_owned(),
shutdown.clone(),
host_config,
) => served,
signal = terminated() => {
signal?;
shutdown.announce();
tokio::time::sleep(Duration::from_millis(250)).await;
Ok(())
}
}
}
#[cfg(unix)]
async fn terminated() -> Result<()> {
use tokio::signal::unix::{SignalKind, signal};
let mut term = signal(SignalKind::terminate())?;
let mut interrupt = signal(SignalKind::interrupt())?;
tokio::select! {
_ = term.recv() => {}
_ = interrupt.recv() => {}
}
Ok(())
}