diff --git a/src/client/tab.rs b/src/client/tab.rs index b87926b7..8e07dfc3 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -10,7 +10,11 @@ use crate::pty_bus::{PtyInstruction, VteBytes}; use crate::utils::shared::adjust_to_size; use crate::wasm_vm::PluginInstruction; use crate::{boundaries::Boundaries, panes::PluginPane}; -use serde::{Deserialize, Serialize}; +use crate::{layout::Layout, wasm_vm::PluginInstruction}; +use crate::{ + os_input_output::{ClientOsApi, ServerOsApiInstruction}, + utils::shared::pad_to_size, +}; use std::os::unix::io::RawFd; use std::time::Instant; use std::{ @@ -67,8 +71,7 @@ pub struct Tab { max_panes: Option, full_screen_ws: PositionAndSize, fullscreen_is_active: bool, - synchronize_is_active: bool, - os_api: Box, + os_api: Box, pub send_plugin_instructions: SenderWithContext, pub send_app_instructions: SenderWithContext, should_clear_display_before_rendering: bool, @@ -224,7 +227,7 @@ impl Tab { position: usize, name: String, full_screen_ws: &PositionAndSize, - mut os_api: Box, + os_api: Box, send_plugin_instructions: SenderWithContext, send_app_instructions: SenderWithContext, max_panes: Option, @@ -235,11 +238,15 @@ impl Tab { ) -> Self { let panes = if let Some(PaneId::Terminal(pid)) = pane_id { let new_terminal = TerminalPane::new(pid, *full_screen_ws); - os_api.set_terminal_size_using_fd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ); + send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + new_terminal.pid, + new_terminal.columns() as u16, + new_terminal.rows() as u16, + ), + )) + .unwrap(); let mut panes: BTreeMap> = BTreeMap::new(); panes.insert(PaneId::Terminal(pid), Box::new(new_terminal)); panes @@ -292,11 +299,15 @@ impl Tab { terminal_pane.set_max_width(max_columns); } terminal_pane.change_pos_and_size(&position_and_size); - self.os_api.set_terminal_size_using_fd( - *pid, - position_and_size.columns as u16, - position_and_size.rows as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + *pid, + position_and_size.columns as u16, + position_and_size.rows as u16, + ), + )) + .unwrap(); } None => { // we filled the entire layout, no room for this pane @@ -338,11 +349,15 @@ impl Tab { // there are still panes left to fill, use the pids we received in this method let pid = new_pids.next().unwrap(); // if this crashes it means we got less pids than there are panes in this layout let new_terminal = TerminalPane::new(*pid, *position_and_size); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + new_terminal.pid, + new_terminal.columns() as u16, + new_terminal.rows() as u16, + ), + )) + .unwrap(); self.panes .insert(PaneId::Terminal(*pid), Box::new(new_terminal)); } @@ -368,11 +383,15 @@ impl Tab { if !self.has_panes() { if let PaneId::Terminal(term_pid) = pid { let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + new_terminal.pid, + new_terminal.columns() as u16, + new_terminal.rows() as u16, + ), + )) + .unwrap(); self.panes.insert(pid, Box::new(new_terminal)); self.active_terminal = Some(pid); } @@ -418,19 +437,27 @@ impl Tab { if let PaneId::Terminal(term_pid) = pid { let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws); let new_terminal = TerminalPane::new(term_pid, bottom_winsize); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - bottom_winsize.columns as u16, - bottom_winsize.rows as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + new_terminal.pid, + bottom_winsize.columns as u16, + bottom_winsize.rows as u16, + ), + )) + .unwrap(); terminal_to_split.change_pos_and_size(&top_winsize); self.panes.insert(pid, Box::new(new_terminal)); if let PaneId::Terminal(terminal_id_to_split) = terminal_id_to_split { - self.os_api.set_terminal_size_using_fd( - terminal_id_to_split, - top_winsize.columns as u16, - top_winsize.rows as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + terminal_id_to_split, + top_winsize.columns as u16, + top_winsize.rows as u16, + ), + )) + .unwrap(); } self.active_terminal = Some(pid); } @@ -438,19 +465,27 @@ impl Tab { if let PaneId::Terminal(term_pid) = pid { let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws); let new_terminal = TerminalPane::new(term_pid, right_winsize); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - right_winsize.columns as u16, - right_winsize.rows as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + new_terminal.pid, + right_winsize.columns as u16, + right_winsize.rows as u16, + ), + )) + .unwrap(); terminal_to_split.change_pos_and_size(&left_winsize); self.panes.insert(pid, Box::new(new_terminal)); if let PaneId::Terminal(terminal_id_to_split) = terminal_id_to_split { - self.os_api.set_terminal_size_using_fd( - terminal_id_to_split, - left_winsize.columns as u16, - left_winsize.rows as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + terminal_id_to_split, + left_winsize.columns as u16, + left_winsize.rows as u16, + ), + )) + .unwrap(); } } } @@ -466,11 +501,15 @@ impl Tab { if !self.has_panes() { if let PaneId::Terminal(term_pid) = pid { let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + new_terminal.pid, + new_terminal.columns() as u16, + new_terminal.rows() as u16, + ), + )) + .unwrap(); self.panes.insert(pid, Box::new(new_terminal)); self.active_terminal = Some(pid); } @@ -496,20 +535,32 @@ impl Tab { active_pane.change_pos_and_size(&top_winsize); - let new_terminal = TerminalPane::new(term_pid, bottom_winsize); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - bottom_winsize.columns as u16, - bottom_winsize.rows as u16, - ); - self.panes.insert(pid, Box::new(new_terminal)); + let new_terminal = TerminalPane::new(term_pid, bottom_winsize); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + new_terminal.pid, + bottom_winsize.columns as u16, + bottom_winsize.rows as u16, + ), + )) + .unwrap(); + self.panes.insert(pid, Box::new(new_terminal)); - if let PaneId::Terminal(active_terminal_pid) = active_pane_id { - self.os_api.set_terminal_size_using_fd( - *active_terminal_pid, - top_winsize.columns as u16, - top_winsize.rows as u16, - ); + if let PaneId::Terminal(active_terminal_pid) = active_pane_id { + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + *active_terminal_pid, + top_winsize.columns as u16, + top_winsize.rows as u16, + ), + )) + .unwrap(); + } + + self.active_terminal = Some(pid); + self.render(); } self.active_terminal = Some(pid); @@ -524,11 +575,15 @@ impl Tab { if !self.has_panes() { if let PaneId::Terminal(term_pid) = pid { let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + new_terminal.pid, + new_terminal.columns() as u16, + new_terminal.rows() as u16, + ), + )) + .unwrap(); self.panes.insert(pid, Box::new(new_terminal)); self.active_terminal = Some(pid); } @@ -560,12 +615,32 @@ impl Tab { ); self.panes.insert(pid, Box::new(new_terminal)); - if let PaneId::Terminal(active_terminal_pid) = active_pane_id { - self.os_api.set_terminal_size_using_fd( - *active_terminal_pid, - left_winsize.columns as u16, - left_winsize.rows as u16, - ); + let new_terminal = TerminalPane::new(term_pid, right_winsize); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + new_terminal.pid, + right_winsize.columns as u16, + right_winsize.rows as u16, + ), + )) + .unwrap(); + self.panes.insert(pid, Box::new(new_terminal)); + + if let PaneId::Terminal(active_terminal_pid) = active_pane_id { + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + *active_terminal_pid, + left_winsize.columns as u16, + left_winsize.rows as u16, + ), + )) + .unwrap(); + } + + self.active_terminal = Some(pid); + self.render(); } self.active_terminal = Some(pid); @@ -622,13 +697,17 @@ impl Tab { match self.get_active_pane_id() { Some(PaneId::Terminal(active_terminal_id)) => { let active_terminal = self.get_active_pane().unwrap(); - let mut adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes); - self.os_api - .write_to_tty_stdin(active_terminal_id, &mut adjusted_input) - .expect("failed to write to terminal"); - self.os_api - .tcdrain(active_terminal_id) - .expect("failed to drain terminal"); + let adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::WriteToTtyStdin(active_terminal_id, adjusted_input), + )) + .unwrap(); + self.send_app_instructions + .send(AppInstruction::OsApi(ServerOsApiInstruction::TcDrain( + active_terminal_id, + ))) + .unwrap(); } Some(PaneId::Plugin(pid)) => { for key in parse_keys(&input_bytes) { @@ -690,11 +769,15 @@ impl Tab { } let active_terminal = self.panes.get(&active_pane_id).unwrap(); if let PaneId::Terminal(active_pid) = active_pane_id { - self.os_api.set_terminal_size_using_fd( - active_pid, - active_terminal.columns() as u16, - active_terminal.rows() as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + active_pid, + active_terminal.columns() as u16, + active_terminal.rows() as u16, + ), + )) + .unwrap(); } self.render(); self.toggle_fullscreen_is_active(); @@ -1254,88 +1337,120 @@ impl Tab { let terminal = self.panes.get_mut(id).unwrap(); terminal.reduce_height_down(count); if let PaneId::Terminal(pid) = id { - self.os_api.set_terminal_size_using_fd( - *pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + *pid, + terminal.columns() as u16, + terminal.rows() as u16, + ), + )) + .unwrap(); } } fn reduce_pane_height_up(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.reduce_height_up(count); if let PaneId::Terminal(pid) = id { - self.os_api.set_terminal_size_using_fd( - *pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + *pid, + terminal.columns() as u16, + terminal.rows() as u16, + ), + )) + .unwrap(); } } fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.increase_height_down(count); if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.set_terminal_size_using_fd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + pid, + terminal.columns() as u16, + terminal.rows() as u16, + ), + )) + .unwrap(); } } fn increase_pane_height_up(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.increase_height_up(count); if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.set_terminal_size_using_fd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + pid, + terminal.columns() as u16, + terminal.rows() as u16, + ), + )) + .unwrap(); } } fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.increase_width_right(count); if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.set_terminal_size_using_fd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + pid, + terminal.columns() as u16, + terminal.rows() as u16, + ), + )) + .unwrap(); } } fn increase_pane_width_left(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.increase_width_left(count); if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.set_terminal_size_using_fd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + pid, + terminal.columns() as u16, + terminal.rows() as u16, + ), + )) + .unwrap(); } } fn reduce_pane_width_right(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.reduce_width_right(count); if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.set_terminal_size_using_fd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + pid, + terminal.columns() as u16, + terminal.rows() as u16, + ), + )) + .unwrap(); } } fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.reduce_width_left(count); if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.set_terminal_size_using_fd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); + self.send_app_instructions + .send(AppInstruction::OsApi( + ServerOsApiInstruction::SetTerminalSizeUsingFd( + pid, + terminal.columns() as u16, + terminal.rows() as u16, + ), + )) + .unwrap(); } } fn pane_is_between_vertical_borders( diff --git a/src/common/errors.rs b/src/common/errors.rs index 3b5c8354..4259600f 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -1,7 +1,7 @@ //! Error context system based on a thread-local representation of the call stack, itself based on //! the instructions that are sent between threads. -use super::{os_input_output::OsApiInstruction, AppInstruction, OPENCALLS}; +use super::{os_input_output::ServerOsApiInstruction, AppInstruction, OPENCALLS}; use crate::pty_bus::PtyInstruction; use crate::screen::ScreenInstruction; use serde::{Deserialize, Serialize}; @@ -297,26 +297,21 @@ impl From<&PtyInstruction> for PtyContext { #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum OsContext { - SpawnTerminal, - GetTerminalSizeUsingFd, SetTerminalSizeUsingFd, - SetRawMode, - UnsetRawMode, - ReadFromTtyStdout, WriteToTtyStdin, TcDrain, - Kill, - ReadFromStdin, - GetStdoutWriter, - BoxClone, + Exit, } -impl From<&OsApiInstruction> for OsContext { - fn from(os_instruction: &OsApiInstruction) -> Self { +impl From<&ServerOsApiInstruction> for OsContext { + fn from(os_instruction: &ServerOsApiInstruction) -> Self { match *os_instruction { - OsApiInstruction::SetTerminalSizeUsingFd(_, _, _) => OsContext::SetTerminalSizeUsingFd, - OsApiInstruction::WriteToTtyStdin(_, _) => OsContext::WriteToTtyStdin, - OsApiInstruction::TcDrain(_) => OsContext::TcDrain, + ServerOsApiInstruction::SetTerminalSizeUsingFd(_, _, _) => { + OsContext::SetTerminalSizeUsingFd + } + ServerOsApiInstruction::WriteToTtyStdin(_, _) => OsContext::WriteToTtyStdin, + ServerOsApiInstruction::TcDrain(_) => OsContext::TcDrain, + ServerOsApiInstruction::Exit => OsContext::Exit, } } } @@ -356,6 +351,7 @@ pub enum AppContext { ToPlugin, ToScreen, DoneClosingPane, + OsApi, } impl From<&AppInstruction> for AppContext { @@ -364,6 +360,7 @@ impl From<&AppInstruction> for AppContext { AppInstruction::Exit => AppContext::Exit, AppInstruction::Error(_) => AppContext::Error, AppInstruction::ToPty(_) => AppContext::ToPty, + AppInstruction::OsApi(_) => AppContext::OsApi, AppInstruction::ToPlugin(_) => AppContext::ToPlugin, AppInstruction::ToScreen(_) => AppContext::ToScreen, AppInstruction::DoneClosingPane => AppContext::DoneClosingPane, diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index f8f07f53..b74e2929 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -5,7 +5,7 @@ use super::keybinds::Keybinds; use crate::common::input::config::Config; use crate::common::{AppInstruction, SenderWithContext, OPENCALLS}; use crate::errors::ContextType; -use crate::os_input_output::OsApi; +use crate::os_input_output::ClientOsApi; use crate::pty_bus::PtyInstruction; use crate::screen::ScreenInstruction; use crate::wasm_vm::PluginInstruction; @@ -19,8 +19,7 @@ use zellij_tile::data::{Event, InputMode, Key, ModeInfo, Palette}; struct InputHandler { /// The current input mode mode: InputMode, - os_input: Box, - config: Config, + os_input: Box, command_is_executing: CommandIsExecuting, send_screen_instructions: SenderWithContext, send_plugin_instructions: SenderWithContext, @@ -31,7 +30,7 @@ struct InputHandler { impl InputHandler { /// Returns a new [`InputHandler`] with the attributes specified as arguments. fn new( - os_input: Box, + os_input: Box, command_is_executing: CommandIsExecuting, config: Config, send_screen_instructions: SenderWithContext, @@ -336,8 +335,7 @@ pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo { /// Entry point to the module. Instantiates an [`InputHandler`] and starts /// its [`InputHandler::handle_input()`] loop. pub fn input_loop( - os_input: Box, - config: Config, + os_input: Box, command_is_executing: CommandIsExecuting, send_screen_instructions: SenderWithContext, send_plugin_instructions: SenderWithContext, diff --git a/src/common/mod.rs b/src/common/mod.rs index d003aab0..0b55d3b9 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -31,7 +31,7 @@ use crate::server::start_server; use command_is_executing::CommandIsExecuting; use errors::{AppContext, ContextType, ErrorContext, PluginContext, ScreenContext}; use input::handler::input_loop; -use os_input_output::{OsApi, OsApiInstruction}; +use os_input_output::{ClientOsApi, ServerOsApiInstruction}; use pty_bus::PtyInstruction; use screen::{Screen, ScreenInstruction}; use serde::{Deserialize, Serialize}; @@ -51,7 +51,7 @@ pub enum ServerInstruction { NewClient(String), ToPty(PtyInstruction), ToScreen(ScreenInstruction), - OsApi(OsApiInstruction), + OsApi(ServerOsApiInstruction), DoneClosingPane, ClosePluginPane(u32), Exit, @@ -180,6 +180,7 @@ pub enum AppInstruction { ToPty(PtyInstruction), ToScreen(ScreenInstruction), ToPlugin(PluginInstruction), + OsApi(ServerOsApiInstruction), DoneClosingPane, } @@ -199,7 +200,9 @@ impl From for AppInstruction { /// Start Zellij with the specified [`OsApi`] and command-line arguments. // FIXME this should definitely be modularized and split into different functions. -pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { +pub fn start(mut os_input: Box, opts: CliArgs) { + let (ipc_thread, server_name) = start_server(opts.clone()); + let take_snapshot = "\u{1b}[?1049h"; os_input.unset_raw_mode(0); let _ = os_input @@ -228,8 +231,6 @@ pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { let mut send_app_instructions = SenderWithContext::new(err_ctx, SenderType::SyncSender(send_app_instructions)); - let (ipc_thread, server_name) = start_server(os_input.clone(), opts.clone()); - let (client_buffer_path, client_buffer) = SharedRingBuffer::create_temp(8192).unwrap(); let mut send_server_instructions = IpcSenderWithContext::new(SharedRingBuffer::open(&server_name).unwrap()); @@ -612,7 +613,14 @@ pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { send_plugin_instructions.send(instruction).unwrap(); } AppInstruction::ToPty(instruction) => { - let _ = send_server_instructions.send(ServerInstruction::ToPty(instruction)); + let _ = send_server_instructions + .send(ServerInstruction::ToPty(instruction)) + .unwrap(); + } + AppInstruction::OsApi(instruction) => { + let _ = send_server_instructions + .send(ServerInstruction::OsApi(instruction)) + .unwrap(); } AppInstruction::DoneClosingPane => command_is_executing.done_closing_pane(), } diff --git a/src/common/os_input_output.rs b/src/common/os_input_output.rs index 0e7363ea..5c0115bc 100644 --- a/src/common/os_input_output.rs +++ b/src/common/os_input_output.rs @@ -7,13 +7,13 @@ use nix::sys::termios; use nix::sys::wait::waitpid; use nix::unistd; use nix::unistd::{ForkResult, Pid}; +use serde::{Deserialize, Serialize}; use std::io; use std::io::prelude::*; use std::os::unix::io::RawFd; use std::path::PathBuf; use std::process::{Child, Command}; use std::sync::{Arc, Mutex}; -use serde::{Deserialize, Serialize}; use signal_hook::{consts::signal::*, iterator::Signals}; @@ -156,23 +156,15 @@ fn spawn_terminal(file_to_open: Option, orig_termios: termios::Termios) } #[derive(Clone)] -pub struct OsInputOutput { +pub struct ServerOsInputOutput { orig_termios: Arc>, } /// The `OsApi` trait represents an abstract interface to the features of an operating system that /// Zellij requires. -pub trait OsApi: Send + Sync { - /// Returns the size of the terminal associated to file descriptor `fd`. - fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize; +pub trait ServerOsApi: Send + Sync { /// Sets the size of the terminal associated to file descriptor `fd`. fn set_terminal_size_using_fd(&mut self, fd: RawFd, cols: u16, rows: u16); - /// Set the terminal associated to file descriptor `fd` to - /// [raw mode](https://en.wikipedia.org/wiki/Terminal_mode). - fn set_raw_mode(&mut self, fd: RawFd); - /// Set the terminal associated to file descriptor `fd` to - /// [cooked mode](https://en.wikipedia.org/wiki/Terminal_mode). - fn unset_raw_mode(&mut self, fd: RawFd); /// Spawn a new terminal, with an optional file to open in a terminal program. fn spawn_terminal(&mut self, file_to_open: Option) -> (RawFd, RawFd); /// Read bytes from the standard output of the virtual terminal referred to by `fd`. @@ -186,30 +178,14 @@ pub trait OsApi: Send + Sync { // or a nix::unistd::Pid. See `man kill.3`, nix::sys::signal::kill (both take an argument // called `pid` and of type `pid_t`, and not `fd`) fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error>; - /// Returns the raw contents of standard input. - fn read_from_stdin(&self) -> Vec; - /// Returns the writer that allows writing to standard output. - fn get_stdout_writer(&self) -> Box; /// Returns a [`Box`] pointer to this [`OsApi`] struct. - fn box_clone(&self) -> Box; - fn receive_sigwinch(&self, cb: Box); - fn load_palette(&self) -> Palette; + fn box_clone(&self) -> Box; } -impl OsApi for OsInputOutput { - fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize { - get_terminal_size_using_fd(fd) - } +impl ServerOsApi for ServerOsInputOutput { fn set_terminal_size_using_fd(&mut self, fd: RawFd, cols: u16, rows: u16) { set_terminal_size_using_fd(fd, cols, rows); } - fn set_raw_mode(&mut self, fd: RawFd) { - into_raw_mode(fd); - } - fn unset_raw_mode(&mut self, fd: RawFd) { - let orig_termios = self.orig_termios.lock().unwrap(); - unset_raw_mode(fd, orig_termios.clone()); - } fn spawn_terminal(&mut self, file_to_open: Option) -> (RawFd, RawFd) { let orig_termios = self.orig_termios.lock().unwrap(); spawn_terminal(file_to_open, orig_termios.clone()) @@ -223,7 +199,72 @@ impl OsApi for OsInputOutput { fn tcdrain(&mut self, fd: RawFd) -> Result<(), nix::Error> { termios::tcdrain(fd) } - fn box_clone(&self) -> Box { + fn box_clone(&self) -> Box { + Box::new((*self).clone()) + } + fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error> { + kill(Pid::from_raw(pid), Some(Signal::SIGINT)).unwrap(); + waitpid(Pid::from_raw(pid), None).unwrap(); + Ok(()) + } +} + +impl Clone for Box { + fn clone(&self) -> Box { + self.box_clone() + } +} + +pub fn get_server_os_input() -> ServerOsInputOutput { + let current_termios = termios::tcgetattr(0).unwrap(); + let orig_termios = Arc::new(Mutex::new(current_termios)); + ServerOsInputOutput { orig_termios } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum ServerOsApiInstruction { + SetTerminalSizeUsingFd(RawFd, u16, u16), + WriteToTtyStdin(RawFd, Vec), + TcDrain(RawFd), + Exit, +} + +#[derive(Clone)] +pub struct ClientOsInputOutput { + orig_termios: Arc>, +} + +/// The `OsApi` trait represents an abstract interface to the features of an operating system that +/// Zellij requires. +pub trait ClientOsApi: Send + Sync { + /// Returns the size of the terminal associated to file descriptor `fd`. + fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize; + /// Set the terminal associated to file descriptor `fd` to + /// [raw mode](https://en.wikipedia.org/wiki/Terminal_mode). + fn set_raw_mode(&mut self, fd: RawFd); + /// Set the terminal associated to file descriptor `fd` to + /// [cooked mode](https://en.wikipedia.org/wiki/Terminal_mode). + fn unset_raw_mode(&mut self, fd: RawFd); + /// Returns the writer that allows writing to standard output. + fn get_stdout_writer(&self) -> Box; + /// Returns the raw contents of standard input. + fn read_from_stdin(&self) -> Vec; + /// Returns a [`Box`] pointer to this [`OsApi`] struct. + fn box_clone(&self) -> Box; +} + +impl ClientOsApi for ClientOsInputOutput { + fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize { + get_terminal_size_using_fd(fd) + } + fn set_raw_mode(&mut self, fd: RawFd) { + into_raw_mode(fd); + } + fn unset_raw_mode(&mut self, fd: RawFd) { + let orig_termios = self.orig_termios.lock().unwrap(); + unset_raw_mode(fd, orig_termios.clone()); + } + fn box_clone(&self) -> Box { Box::new((*self).clone()) } fn read_from_stdin(&self) -> Vec { @@ -239,51 +280,16 @@ impl OsApi for OsInputOutput { let stdout = ::std::io::stdout(); Box::new(stdout) } - fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error> { - // TODO: - // Ideally, we should be using SIGINT rather than SIGKILL here, but there are cases in which - // the terminal we're trying to kill hangs on SIGINT and so all the app gets stuck - // that's why we're sending SIGKILL here - // A better solution would be to send SIGINT here and not wait for it, and then have - // a background thread do the waitpid stuff and send SIGKILL if the process is stuck - kill(Pid::from_raw(pid), Some(Signal::SIGKILL)).unwrap(); - waitpid(Pid::from_raw(pid), None).unwrap(); - Ok(()) - } - fn receive_sigwinch(&self, cb: Box) { - let mut signals = Signals::new(&[SIGWINCH, SIGTERM, SIGINT, SIGQUIT]).unwrap(); - for signal in signals.forever() { - match signal { - SIGWINCH => { - cb(); - } - SIGTERM | SIGINT | SIGQUIT => { - break; - } - _ => unreachable!(), - } - } - } - fn load_palette(&self) -> Palette { - default_palette() - } } -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum OsApiInstruction { - SetTerminalSizeUsingFd(RawFd, u16, u16), - WriteToTtyStdin(RawFd, Vec), - TcDrain(RawFd), -} - -impl Clone for Box { - fn clone(&self) -> Box { +impl Clone for Box { + fn clone(&self) -> Box { self.box_clone() } } -pub fn get_os_input() -> OsInputOutput { +pub fn get_client_os_input() -> ClientOsInputOutput { let current_termios = termios::tcgetattr(0).unwrap(); let orig_termios = Arc::new(Mutex::new(current_termios)); - OsInputOutput { orig_termios } + ClientOsInputOutput { orig_termios } } diff --git a/src/common/pty_bus.rs b/src/common/pty_bus.rs index e08e8576..a0eda375 100644 --- a/src/common/pty_bus.rs +++ b/src/common/pty_bus.rs @@ -12,7 +12,7 @@ use std::path::PathBuf; use super::{IpcSenderWithContext, ScreenInstruction, OPENCALLS}; use crate::layout::Layout; -use crate::os_input_output::OsApi; +use crate::os_input_output::ServerOsApi; use crate::utils::logging::debug_to_file; use crate::{ common::ServerInstruction, @@ -22,11 +22,11 @@ use crate::{ pub struct ReadFromPid { pid: RawFd, - os_input: Box, + os_input: Box, } impl ReadFromPid { - pub fn new(pid: &RawFd, os_input: Box) -> ReadFromPid { + pub fn new(pid: &RawFd, os_input: Box) -> ReadFromPid { ReadFromPid { pid: *pid, os_input, @@ -187,7 +187,7 @@ pub enum PtyInstruction { pub struct PtyBus { pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, pub id_to_child_pid: HashMap, - os_input: Box, + os_input: Box, debug_to_file: bool, task_handles: HashMap>, pub send_server_instructions: IpcSenderWithContext, @@ -195,7 +195,7 @@ pub struct PtyBus { fn stream_terminal_bytes( pid: RawFd, - os_input: Box, + os_input: Box, mut send_server_instructions: IpcSenderWithContext, debug: bool, ) -> JoinHandle<()> { @@ -274,7 +274,7 @@ fn stream_terminal_bytes( impl PtyBus { pub fn new( receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, - os_input: Box, + os_input: Box, send_server_instructions: IpcSenderWithContext, debug_to_file: bool, ) -> Self { diff --git a/src/common/screen.rs b/src/common/screen.rs index 7848c243..ae1f4dd7 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; use std::sync::mpsc::Receiver; use super::{AppInstruction, SenderWithContext}; -use crate::os_input_output::OsApi; +use crate::os_input_output::ClientOsApi; use crate::panes::PositionAndSize; use crate::pty_bus::{PtyInstruction, VteBytes}; use crate::tab::Tab; @@ -78,10 +78,7 @@ pub struct Screen { /// The index of this [`Screen`]'s active [`Tab`]. active_tab_index: Option, /// The [`OsApi`] this [`Screen`] uses. - os_api: Box, - mode_info: ModeInfo, - input_mode: InputMode, - colors: Palette, + os_api: Box, } impl Screen { @@ -93,7 +90,7 @@ impl Screen { send_plugin_instructions: SenderWithContext, send_app_instructions: SenderWithContext, full_screen_ws: &PositionAndSize, - os_api: Box, + os_api: Box, max_panes: Option, mode_info: ModeInfo, input_mode: InputMode, diff --git a/src/main.rs b/src/main.rs index e5cdbe53..c103085b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,8 @@ use structopt::StructOpt; use crate::cli::CliArgs; use crate::command_is_executing::CommandIsExecuting; -use crate::os_input_output::get_os_input; +use crate::os_input_output::get_client_os_input; +use crate::pty_bus::VteEvent; use crate::utils::{ consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, logging::*, @@ -86,7 +87,7 @@ pub fn main() { .send(ServerInstruction::OpenFile(file_to_open)) .unwrap(); } else { - let os_input = get_os_input(); + let os_input = get_client_os_input(); atomic_create_dir(ZELLIJ_TMP_DIR).unwrap(); atomic_create_dir(ZELLIJ_TMP_LOG_DIR).unwrap(); start(Box::new(os_input), opts, config); diff --git a/src/server/mod.rs b/src/server/mod.rs index c1713918..00368904 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -4,7 +4,7 @@ use crate::common::{ ServerInstruction, }; use crate::errors::{ContextType, ErrorContext, OsContext, PtyContext}; -use crate::os_input_output::{OsApi, OsApiInstruction}; +use crate::os_input_output::{get_server_os_input, ServerOsApi, ServerOsApiInstruction}; use crate::panes::PaneId; use crate::pty_bus::{PtyBus, PtyInstruction}; use crate::screen::ScreenInstruction; @@ -14,9 +14,10 @@ use std::path::PathBuf; use std::sync::mpsc::channel; use std::thread; -pub fn start_server(os_input: Box, opts: CliArgs) -> (thread::JoinHandle<()>, String) { +pub fn start_server(opts: CliArgs) -> (thread::JoinHandle<()>, String) { let (send_pty_instructions, receive_pty_instructions): ChannelWithContext = channel(); + let os_input = Box::new(get_server_os_input()); let mut send_pty_instructions = SenderWithContext::new( ErrorContext::new(), SenderType::Sender(send_pty_instructions), @@ -30,8 +31,9 @@ pub fn start_server(os_input: Box, opts: CliArgs) -> (thread::JoinHan #[cfg(test)] let (server_name, server_buffer) = SharedRingBuffer::create_temp(8192).unwrap(); - let (send_os_instructions, receive_os_instructions): ChannelWithContext = - channel(); + let (send_os_instructions, receive_os_instructions): ChannelWithContext< + ServerOsApiInstruction, + > = channel(); let mut send_os_instructions = SenderWithContext::new( ErrorContext::new(), SenderType::Sender(send_os_instructions), @@ -131,22 +133,23 @@ pub fn start_server(os_input: Box, opts: CliArgs) -> (thread::JoinHan .expect("failed to receive an event on the channel"); err_ctx.add_call(ContextType::Os(OsContext::from(&event))); match event { - OsApiInstruction::SetTerminalSizeUsingFd(fd, cols, rows) => { + ServerOsApiInstruction::SetTerminalSizeUsingFd(fd, cols, rows) => { os_input.set_terminal_size_using_fd(fd, cols, rows); } - OsApiInstruction::WriteToTtyStdin(fd, mut buf) => { + ServerOsApiInstruction::WriteToTtyStdin(fd, mut buf) => { let slice = buf.as_mut_slice(); os_input.write_to_tty_stdin(fd, slice).unwrap(); } - OsApiInstruction::TcDrain(fd) => { + ServerOsApiInstruction::TcDrain(fd) => { os_input.tcdrain(fd).unwrap(); } + ServerOsApiInstruction::Exit => break, } } }) .unwrap(); - let join_handle = thread::Builder::new() + let server_handle = thread::Builder::new() .name("ipc_server".to_string()) .spawn({ let recv_server_instructions = IpcReceiver::new(server_buffer); @@ -158,6 +161,7 @@ pub fn start_server(os_input: Box, opts: CliArgs) -> (thread::JoinHan recv_server_instructions.recv().unwrap(); err_ctx.add_call(ContextType::IPCServer); send_pty_instructions.update(err_ctx); + send_os_instructions.update(err_ctx); if send_client_instructions.len() == 1 { send_client_instructions[0].update(err_ctx); } @@ -213,6 +217,7 @@ pub fn start_server(os_input: Box, opts: CliArgs) -> (thread::JoinHan } ServerInstruction::Exit => { let _ = send_pty_instructions.send(PtyInstruction::Exit); + let _ = send_os_instructions.send(ServerOsApiInstruction::Exit); let _ = pty_thread.join(); let _ = os_thread.join(); let _ = send_client_instructions[0].send(ClientInstruction::Exit); @@ -222,5 +227,5 @@ pub fn start_server(os_input: Box, opts: CliArgs) -> (thread::JoinHan } }) .unwrap(); - (join_handle, server_name) + (server_handle, server_name) } diff --git a/src/tests/fakes.rs b/src/tests/fakes.rs index 8e4aff25..57f1ac95 100644 --- a/src/tests/fakes.rs +++ b/src/tests/fakes.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use std::sync::{Arc, Condvar, Mutex}; use std::time::{Duration, Instant}; -use crate::os_input_output::OsApi; +use crate::os_input_output::{ClientOsApi, ServerOsApi}; use crate::tests::possible_tty_inputs::{get_possible_tty_inputs, Bytes}; use crate::utils::shared::default_palette; use zellij_tile::data::Palette; @@ -116,7 +116,7 @@ impl FakeInputOutput { } } -impl OsApi for FakeInputOutput { +impl ClientOsApi for FakeInputOutput { fn get_terminal_size_using_fd(&self, pid: RawFd) -> PositionAndSize { if let Some(new_position_and_size) = self.sigwinch_event { let (lock, _cvar) = &*self.should_trigger_sigwinch; @@ -129,6 +129,42 @@ impl OsApi for FakeInputOutput { let winsize = win_sizes.get(&pid).unwrap(); *winsize } + fn set_raw_mode(&mut self, pid: RawFd) { + self.io_events + .lock() + .unwrap() + .push(IoEvent::IntoRawMode(pid)); + } + fn unset_raw_mode(&mut self, pid: RawFd) { + self.io_events + .lock() + .unwrap() + .push(IoEvent::UnsetRawMode(pid)); + } + fn box_clone(&self) -> Box { + Box::new((*self).clone()) + } + fn read_from_stdin(&self) -> Vec { + loop { + let last_snapshot_time = { *self.last_snapshot_time.lock().unwrap() }; + if last_snapshot_time.elapsed() > MIN_TIME_BETWEEN_SNAPSHOTS { + break; + } else { + ::std::thread::sleep(MIN_TIME_BETWEEN_SNAPSHOTS - last_snapshot_time.elapsed()); + } + } + self.stdin_commands + .lock() + .unwrap() + .pop_front() + .unwrap_or(vec![]) + } + fn get_stdout_writer(&self) -> Box { + Box::new(self.stdout_writer.clone()) + } +} + +impl ServerOsApi for FakeInputOutput { fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16) { let terminal_input = self .possible_tty_inputs @@ -143,23 +179,21 @@ impl OsApi for FakeInputOutput { .unwrap() .push(IoEvent::SetTerminalSizeUsingFd(pid, cols, rows)); } - fn set_raw_mode(&mut self, pid: RawFd) { - self.io_events - .lock() - .unwrap() - .push(IoEvent::IntoRawMode(pid)); - } - fn unset_raw_mode(&mut self, pid: RawFd) { - self.io_events - .lock() - .unwrap() - .push(IoEvent::UnsetRawMode(pid)); - } fn spawn_terminal(&mut self, _file_to_open: Option) -> (RawFd, RawFd) { let next_terminal_id = self.stdin_writes.lock().unwrap().keys().len() as RawFd + 1; self.add_terminal(next_terminal_id); (next_terminal_id as i32, next_terminal_id + 1000) // secondary number is arbitrary here } + fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result { + let mut stdin_writes = self.stdin_writes.lock().unwrap(); + let write_buffer = stdin_writes.get_mut(&pid).unwrap(); + let mut bytes_written = 0; + for byte in buf { + bytes_written += 1; + write_buffer.push(*byte); + } + Ok(bytes_written) + } fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result { let mut read_buffers = self.read_buffers.lock().unwrap(); let mut bytes_read = 0; @@ -177,21 +211,11 @@ impl OsApi for FakeInputOutput { None => Err(nix::Error::Sys(nix::errno::Errno::EAGAIN)), } } - fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result { - let mut stdin_writes = self.stdin_writes.lock().unwrap(); - let write_buffer = stdin_writes.get_mut(&pid).unwrap(); - let mut bytes_written = 0; - for byte in buf { - bytes_written += 1; - write_buffer.push(*byte); - } - Ok(bytes_written) - } fn tcdrain(&mut self, pid: RawFd) -> Result<(), nix::Error> { self.io_events.lock().unwrap().push(IoEvent::TcDrain(pid)); Ok(()) } - fn box_clone(&self) -> Box { + fn box_clone(&self) -> Box { Box::new((*self).clone()) } fn read_from_stdin(&self) -> Vec {