1mod app;
7mod event;
8mod ui;
9
10use std::io::{self, IsTerminal, Stdout};
11
12use anyhow::{bail, Context, Result};
13use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
14use crossterm::execute;
15use crossterm::terminal::{
16 disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
17};
18use deck_orchestrator::Handle;
19use ratatui::backend::CrosstermBackend;
20use ratatui::Terminal;
21
22pub use app::App;
23
24pub async fn run() -> Result<()> {
27 let mut app = App::new(None);
28 run_with(&mut app).await
29}
30
31pub async fn run_with_handle(handle: Handle, session: deck_core::SessionId) -> Result<()> {
33 let mut app = App::new(Some(AppHandle { handle, session }));
34 run_with(&mut app).await
35}
36
37struct TerminalGuard {
42 terminal: Terminal<CrosstermBackend<Stdout>>,
43}
44
45impl TerminalGuard {
46 fn enter() -> Result<Self> {
47 if !io::stdin().is_terminal() {
48 bail!(
49 "ono-sendai needs an interactive TTY on stdin. \
50 Run it from a real terminal — not a pipe, CI runner, or \
51 non-PTY ssh session."
52 );
53 }
54 enable_raw_mode().context("enable raw mode")?;
55 let mut stdout = io::stdout();
56 if let Err(e) = execute!(stdout, EnterAlternateScreen, EnableMouseCapture) {
57 let _ = disable_raw_mode();
60 return Err(e).context("enter alternate screen");
61 }
62 let backend = CrosstermBackend::new(stdout);
63 let terminal = Terminal::new(backend).context("create terminal")?;
64 Ok(Self { terminal })
65 }
66}
67
68impl Drop for TerminalGuard {
69 fn drop(&mut self) {
70 let _ = execute!(
73 self.terminal.backend_mut(),
74 LeaveAlternateScreen,
75 DisableMouseCapture
76 );
77 let _ = self.terminal.show_cursor();
78 let _ = disable_raw_mode();
79 }
80}
81
82async fn run_with(app: &mut App) -> Result<()> {
83 let mut guard = TerminalGuard::enter()?;
84 app.run(&mut guard.terminal).await
85}
86
87#[derive(Clone)]
90pub struct AppHandle {
91 pub handle: Handle,
92 pub session: deck_core::SessionId,
93}
94
95impl std::fmt::Debug for AppHandle {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 f.debug_struct("AppHandle")
98 .field("session", &self.session)
99 .finish_non_exhaustive()
100 }
101}