use std::sync::{atomic::AtomicBool, Arc}; use bytes::Bytes; use ratatui::{layout::Rect, Frame}; use tokio::sync::{mpsc, Notify}; use crate::events::AppEvent; use crate::layout::PaneId; /// Live runtime for a server-owned terminal. /// /// The PTY implementation still delegates to the legacy pane runtime while the /// migration proceeds, but production code now depends on this terminal-layer /// type instead of the pane module's implementation detail. pub struct TerminalRuntime(crate::pane::PaneRuntime); impl TerminalRuntime { pub fn shutdown(self) { self.0.shutdown(); } #[cfg(unix)] pub fn duplicate_handoff_fd(&self) -> std::io::Result { self.0.duplicate_handoff_fd() } #[cfg(unix)] pub fn preserve_for_handoff(self) { self.0.preserve_for_handoff() } #[cfg(unix)] pub fn assume_handoff_ownership(&mut self) { self.0.assume_handoff_ownership(); } #[cfg(unix)] pub fn set_handoff_reader_paused(&self, paused: bool) { self.0.set_handoff_reader_paused(paused); } #[cfg(unix)] pub fn pause_handoff_reader(&self, timeout: std::time::Duration) -> std::io::Result<()> { self.0.pause_handoff_reader(timeout) } #[cfg(unix)] pub fn handoff_pane(&self, pane_id: u32) -> crate::server::handoff::HandoffPane { self.0.handoff_pane(pane_id) } #[cfg(unix)] pub fn handoff_history_ansi(&self) -> Option { self.0.handoff_history_ansi() } #[cfg(unix)] pub fn from_handoff_fd( import: crate::pane::PaneRuntimeImport, scrollback_limit_bytes: usize, host_terminal_theme: crate::terminal_theme::TerminalTheme, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, ) -> std::io::Result { crate::pane::PaneRuntime::from_handoff_fd( import, scrollback_limit_bytes, host_terminal_theme, events, render_notify, render_dirty, ) .map(Self) } pub fn spawn( pane_id: PaneId, rows: u16, cols: u16, cwd: std::path::PathBuf, scrollback_limit_bytes: usize, host_terminal_theme: crate::terminal_theme::TerminalTheme, default_shell: &str, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, ) -> std::io::Result { crate::pane::PaneRuntime::spawn( pane_id, rows, cols, cwd, scrollback_limit_bytes, host_terminal_theme, default_shell, events, render_notify, render_dirty, ) .map(Self) } pub fn spawn_with_initial_history( pane_id: PaneId, rows: u16, cols: u16, cwd: std::path::PathBuf, scrollback_limit_bytes: usize, host_terminal_theme: crate::terminal_theme::TerminalTheme, default_shell: &str, initial_history_ansi: Option<&str>, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, ) -> std::io::Result { crate::pane::PaneRuntime::spawn_with_initial_history( pane_id, rows, cols, cwd, scrollback_limit_bytes, host_terminal_theme, default_shell, initial_history_ansi, events, render_notify, render_dirty, ) .map(Self) } pub fn spawn_shell_command( pane_id: PaneId, rows: u16, cols: u16, cwd: std::path::PathBuf, command: &str, extra_env: &[(String, String)], scrollback_limit_bytes: usize, host_terminal_theme: crate::terminal_theme::TerminalTheme, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, ) -> std::io::Result { crate::pane::PaneRuntime::spawn_shell_command( pane_id, rows, cols, cwd, command, extra_env, scrollback_limit_bytes, host_terminal_theme, events, render_notify, render_dirty, ) .map(Self) } pub fn spawn_argv_command( pane_id: PaneId, rows: u16, cols: u16, cwd: std::path::PathBuf, argv: &[String], scrollback_limit_bytes: usize, host_terminal_theme: crate::terminal_theme::TerminalTheme, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, ) -> std::io::Result { crate::pane::PaneRuntime::spawn_argv_command( pane_id, rows, cols, cwd, argv, scrollback_limit_bytes, host_terminal_theme, events, render_notify, render_dirty, ) .map(Self) } pub fn spawn_agent_restore( pane_id: PaneId, rows: u16, cols: u16, cwd: std::path::PathBuf, launch: crate::agent_resume::AgentResumeLaunch<'_>, scrollback_limit_bytes: usize, host_terminal_theme: crate::terminal_theme::TerminalTheme, default_shell: &str, events: mpsc::Sender, render_notify: Arc, render_dirty: Arc, ) -> std::io::Result { crate::pane::PaneRuntime::spawn_agent_restore( pane_id, rows, cols, cwd, launch, scrollback_limit_bytes, host_terminal_theme, default_shell, events, render_notify, render_dirty, ) .map(Self) } pub fn apply_host_terminal_theme(&self, theme: crate::terminal_theme::TerminalTheme) { self.0.apply_host_terminal_theme(theme); } pub fn begin_graceful_release(&self, agent: crate::detect::Agent) { self.0.begin_graceful_release(agent); } pub fn resize(&self, rows: u16, cols: u16, cell_width_px: u32, cell_height_px: u32) { self.0.resize(rows, cols, cell_width_px, cell_height_px); } pub fn nudge_child_redraw_after_handoff(&self) { self.0.nudge_child_redraw_after_handoff(); } pub fn scroll_up(&self, lines: usize) { self.0.scroll_up(lines); } pub fn scroll_down(&self, lines: usize) { self.0.scroll_down(lines); } pub fn scroll_reset(&self) { self.0.scroll_reset(); } pub fn set_scroll_offset_from_bottom(&self, lines: usize) { self.0.set_scroll_offset_from_bottom(lines); } pub fn scroll_metrics(&self) -> Option { self.0.scroll_metrics() } pub fn input_state(&self) -> Option { self.0.input_state() } pub fn cursor_state( &self, area: Rect, show_cursor: bool, ) -> Option { self.0.cursor_state(area, show_cursor) } pub fn visible_text(&self) -> String { self.0.visible_text() } pub fn visible_ansi(&self) -> String { self.0.visible_ansi() } pub fn recent_text(&self, lines: usize) -> String { self.0.recent_text(lines) } pub fn recent_ansi(&self, lines: usize) -> String { self.0.recent_ansi(lines) } pub fn recent_unwrapped_text(&self, lines: usize) -> String { self.0.recent_unwrapped_text(lines) } pub fn recent_unwrapped_ansi(&self, lines: usize) -> String { self.0.recent_unwrapped_ansi(lines) } pub fn snapshot_history(&self) -> Option { self.0.snapshot_history() } pub fn extract_selection(&self, selection: &crate::selection::Selection) -> Option { self.0.extract_selection(selection) } pub fn render(&self, frame: &mut Frame, area: Rect, show_cursor: bool) { self.0.render(frame, area, show_cursor); } pub fn visible_hyperlinks(&self, area: Rect) -> Vec<((u16, u16), String, String)> { self.0.visible_hyperlinks(area) } pub fn kitty_image_placements_with_data_filter( &self, needs_data: F, ) -> Vec where F: FnMut(crate::ghostty::KittyImageDescriptor) -> bool, { self.0.kitty_image_placements_with_data_filter(needs_data) } pub fn keyboard_protocol(&self) -> crate::input::KeyboardProtocol { self.0.keyboard_protocol() } pub fn encode_terminal_key(&self, key: crate::input::TerminalKey) -> Vec { self.0.encode_terminal_key(key) } pub async fn send_bytes(&self, bytes: Bytes) -> Result<(), mpsc::error::SendError> { self.0.send_bytes(bytes).await } pub fn try_send_bytes(&self, bytes: Bytes) -> Result<(), mpsc::error::TrySendError> { self.0.try_send_bytes(bytes) } pub async fn send_paste(&self, text: String) -> Result<(), mpsc::error::SendError> { self.0.send_paste(text).await } pub fn try_send_focus_event(&self, event: crate::ghostty::FocusEvent) -> bool { self.0.try_send_focus_event(event) } pub fn wheel_routing(&self) -> Option { self.0.wheel_routing() } pub fn encode_mouse_button( &self, kind: crossterm::event::MouseEventKind, column: u16, row: u16, modifiers: crossterm::event::KeyModifiers, ) -> Option> { self.0.encode_mouse_button(kind, column, row, modifiers) } pub fn encode_mouse_wheel( &self, kind: crossterm::event::MouseEventKind, column: u16, row: u16, modifiers: crossterm::event::KeyModifiers, ) -> Option> { self.0.encode_mouse_wheel(kind, column, row, modifiers) } pub fn encode_alternate_scroll( &self, kind: crossterm::event::MouseEventKind, ) -> Option> { self.0.encode_alternate_scroll(kind) } pub fn cwd(&self) -> Option { self.0.cwd() } } #[cfg(test)] impl TerminalRuntime { pub(crate) fn current_size(&self) -> (u16, u16) { self.0.current_size() } pub(crate) fn test_with_channel(cols: u16, rows: u16) -> (Self, mpsc::Receiver) { let (runtime, rx) = crate::pane::PaneRuntime::test_with_channel(cols, rows); (Self(runtime), rx) } pub(crate) fn test_with_channel_capacity( cols: u16, rows: u16, capacity: usize, ) -> (Self, mpsc::Receiver) { let (runtime, rx) = crate::pane::PaneRuntime::test_with_channel_capacity(cols, rows, capacity); (Self(runtime), rx) } pub(crate) fn test_with_screen_bytes(cols: u16, rows: u16, bytes: &[u8]) -> Self { Self(crate::pane::PaneRuntime::test_with_screen_bytes( cols, rows, bytes, )) } pub(crate) fn test_with_scrollback_bytes( cols: u16, rows: u16, scrollback_limit_bytes: usize, bytes: &[u8], ) -> Self { Self(crate::pane::PaneRuntime::test_with_scrollback_bytes( cols, rows, scrollback_limit_bytes, bytes, )) } pub(crate) fn test_with_channel_and_scrollback_bytes( cols: u16, rows: u16, scrollback_limit_bytes: usize, bytes: &[u8], channel_capacity: usize, ) -> (Self, mpsc::Receiver) { let (runtime, rx) = crate::pane::PaneRuntime::test_with_channel_and_scrollback_bytes( cols, rows, scrollback_limit_bytes, bytes, channel_capacity, ); (Self(runtime), rx) } }