use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::thread;
use super::{Session, exit_reaper};
use crate::observer::IoObserver;
use crate::wait::exit::ExitNotifier;
use crate::wait::output::OutputNotifier;
use crate::{Error, ExitStatus, Result};
use tastty::{Builder, Terminal};
#[derive(Default, Clone)]
pub struct DriverOptions {
pub observer: Option<Arc<dyn IoObserver>>,
pub on_redraw: Option<Arc<dyn Fn() + Send + Sync>>,
}
impl std::fmt::Debug for DriverOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DriverOptions")
.field("observer", &self.observer.as_ref().map(|_| "<observer>"))
.field("on_redraw", &self.on_redraw.as_ref().map(|_| "<callback>"))
.finish()
}
}
impl Session {
pub fn spawn(builder: Builder) -> Result<Self> {
Self::spawn_with(builder, DriverOptions::default())
}
pub fn spawn_with(builder: Builder, options: DriverOptions) -> Result<Self> {
let DriverOptions {
observer,
on_redraw: user_on_redraw,
} = options;
let (output_notifier, output_timer) = OutputNotifier::new()?;
let exit_notifier = Arc::new(ExitNotifier::new());
let mut inner = builder;
if let Some(obs) = observer.clone() {
let on_output = Arc::clone(&obs);
inner = inner.on_output(move |bytes| {
on_output.on_output(bytes);
});
let on_input = obs;
inner = inner.on_input(move |bytes| {
on_input.on_input(bytes);
});
}
{
let notifier = Arc::clone(&output_notifier);
inner = inner.on_redraw(move || {
notifier.notify_tick();
if let Some(cb) = &user_on_redraw {
cb();
}
});
}
let terminal = Arc::new(Terminal::spawn(inner).map_err(|err| match err {
tastty::Error::MissingCommand => Error::MissingCommand,
other => Error::Spawn(other),
})?);
let reaper_shutdown = Arc::new(AtomicBool::new(false));
let reaper = {
let terminal = Arc::clone(&terminal);
let exit = Arc::clone(&exit_notifier);
let output = Arc::clone(&output_notifier);
let observer = observer.clone();
let shutdown = Arc::clone(&reaper_shutdown);
Some(
thread::Builder::new()
.name("tastty-driver-exit-reaper".into())
.spawn(move || exit_reaper(terminal, exit, output, observer, shutdown))
.map_err(|source| Error::ThreadSpawn {
source,
name: "tastty-driver-exit-reaper",
})?,
)
};
Ok(Self {
terminal,
observer,
exit_notifier,
output_notifier,
output_timer: Some(output_timer),
reaper_shutdown,
reaper,
})
}
pub fn run_to_exit(builder: Builder) -> Result<ExitStatus> {
Self::spawn(builder)?.wait_exit()
}
}