diff options
| author | Kunal Mohan <44079328+kunalmohan@users.noreply.github.com> | 2021-05-06 22:11:07 +0530 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-05-06 22:11:07 +0530 |
| commit | d5433f8f8995c155f231c823d49813882b6212bf (patch) | |
| tree | 529d5ee706ded18cd24f26a3b0372bf17ecb81ed /src/client | |
| parent | docs(changelog): Add Manpage #455 (diff) | |
| parent | explicitly set file and directory permissions (diff) | |
| download | sl0ppij-d5433f8f8995c155f231c823d49813882b6212bf.tar.gz sl0ppij-d5433f8f8995c155f231c823d49813882b6212bf.tar.xz sl0ppij-d5433f8f8995c155f231c823d49813882b6212bf.zip | |
Merge pull request #223 from zellij-org/isolate-pty
Psuedo Client-Server model
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/mod.rs | 170 | ||||
| -rw-r--r-- | src/client/pane_resizer.rs | 6 | ||||
| -rw-r--r-- | src/client/panes/terminal_pane.rs | 5 | ||||
| -rw-r--r-- | src/client/tab.rs | 78 |
4 files changed, 208 insertions, 51 deletions
diff --git a/src/client/mod.rs b/src/client/mod.rs index cba12a46..8a5834f0 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -4,4 +4,172 @@ pub mod pane_resizer; pub mod panes; pub mod tab; -pub fn _start_client() {} +use serde::{Deserialize, Serialize}; +use std::io::Write; +use std::sync::mpsc; +use std::thread; + +use crate::cli::CliArgs; +use crate::common::{ + command_is_executing::CommandIsExecuting, + errors::{ClientContext, ContextType}, + input::config::Config, + input::handler::input_loop, + os_input_output::ClientOsApi, + SenderType, SenderWithContext, SyncChannelWithContext, +}; +use crate::server::ServerInstruction; + +/// Instructions related to the client-side application and sent from server to client +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum ClientInstruction { + Error(String), + Render(Option<String>), + UnblockInputThread, + Exit, +} + +pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: Config) { + let take_snapshot = "\u{1b}[?1049h"; + os_input.unset_raw_mode(0); + let _ = os_input + .get_stdout_writer() + .write(take_snapshot.as_bytes()) + .unwrap(); + std::env::set_var(&"ZELLIJ", "0"); + + let mut command_is_executing = CommandIsExecuting::new(); + + let full_screen_ws = os_input.get_terminal_size_using_fd(0); + os_input.connect_to_server(); + os_input.send_to_server(ServerInstruction::NewClient(full_screen_ws, opts)); + os_input.set_raw_mode(0); + + let (send_client_instructions, receive_client_instructions): SyncChannelWithContext< + ClientInstruction, + > = mpsc::sync_channel(50); + let send_client_instructions = + SenderWithContext::new(SenderType::SyncSender(send_client_instructions)); + + #[cfg(not(test))] + std::panic::set_hook({ + use crate::errors::handle_panic; + let send_client_instructions = send_client_instructions.clone(); + Box::new(move |info| { + handle_panic(info, &send_client_instructions); + }) + }); + + let _stdin_thread = thread::Builder::new() + .name("stdin_handler".to_string()) + .spawn({ + let send_client_instructions = send_client_instructions.clone(); + let command_is_executing = command_is_executing.clone(); + let os_input = os_input.clone(); + move || { + input_loop( + os_input, + config, + command_is_executing, + send_client_instructions, + ) + } + }); + + let _signal_thread = thread::Builder::new() + .name("signal_listener".to_string()) + .spawn({ + let os_input = os_input.clone(); + move || { + os_input.receive_sigwinch(Box::new({ + let os_api = os_input.clone(); + move || { + os_api.send_to_server(ServerInstruction::TerminalResize( + os_api.get_terminal_size_using_fd(0), + )); + } + })); + } + }) + .unwrap(); + + let router_thread = thread::Builder::new() + .name("router".to_string()) + .spawn({ + let os_input = os_input.clone(); + move || { + loop { + let (instruction, mut err_ctx) = os_input.recv_from_server(); + err_ctx.add_call(ContextType::Client(ClientContext::from(&instruction))); + if let ClientInstruction::Exit = instruction { + break; + } + send_client_instructions.send(instruction).unwrap(); + } + send_client_instructions + .send(ClientInstruction::Exit) + .unwrap(); + } + }) + .unwrap(); + + #[warn(clippy::never_loop)] + loop { + let (client_instruction, mut err_ctx) = receive_client_instructions + .recv() + .expect("failed to receive app instruction on channel"); + + err_ctx.add_call(ContextType::Client(ClientContext::from( + &client_instruction, + ))); + match client_instruction { + ClientInstruction::Exit => break, + ClientInstruction::Error(backtrace) => { + let _ = os_input.send_to_server(ServerInstruction::ClientExit); + os_input.unset_raw_mode(0); + let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); + let restore_snapshot = "\u{1b}[?1049l"; + let error = format!( + "{}\n{}{}", + goto_start_of_last_line, restore_snapshot, backtrace + ); + let _ = os_input + .get_stdout_writer() + .write(error.as_bytes()) + .unwrap(); + std::process::exit(1); + } + ClientInstruction::Render(output) => { + if output.is_none() { + break; + } + let mut stdout = os_input.get_stdout_writer(); + stdout + .write_all(&output.unwrap().as_bytes()) + .expect("cannot write to stdout"); + stdout.flush().expect("could not flush"); + } + ClientInstruction::UnblockInputThread => { + command_is_executing.unblock_input_thread(); + } + } + } + + let _ = os_input.send_to_server(ServerInstruction::ClientExit); + router_thread.join().unwrap(); + + // cleanup(); + let reset_style = "\u{1b}[m"; + let show_cursor = "\u{1b}[?25h"; + let restore_snapshot = "\u{1b}[?1049l"; + let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); + let goodbye_message = format!( + "{}\n{}{}{}Bye from Zellij!\n", + goto_start_of_last_line, restore_snapshot, reset_style, show_cursor + ); + + os_input.unset_raw_mode(0); + let mut stdout = os_input.get_stdout_writer(); + let _ = stdout.write(goodbye_message.as_bytes()).unwrap(); + stdout.flush().unwrap(); +} diff --git a/src/client/pane_resizer.rs b/src/client/pane_resizer.rs index f9ec71b8..73678499 100644 --- a/src/client/pane_resizer.rs +++ b/src/client/pane_resizer.rs @@ -1,4 +1,4 @@ -use crate::os_input_output::OsApi; +use crate::os_input_output::ServerOsApi; use crate::panes::{PaneId, PositionAndSize}; use crate::tab::Pane; use std::{ @@ -8,7 +8,7 @@ use std::{ pub struct PaneResizer<'a> { panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>, - os_api: &'a mut Box<dyn OsApi>, + os_api: &'a mut Box<dyn ServerOsApi>, } // TODO: currently there are some functions here duplicated with Tab @@ -17,7 +17,7 @@ pub struct PaneResizer<'a> { impl<'a> PaneResizer<'a> { pub fn new( panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>, - os_api: &'a mut Box<dyn OsApi>, + os_api: &'a mut Box<dyn ServerOsApi>, ) -> Self { PaneResizer { panes, os_api } } diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs index a5a957e6..f58c3a27 100644 --- a/src/client/panes/terminal_pane.rs +++ b/src/client/panes/terminal_pane.rs @@ -1,6 +1,7 @@ use crate::tab::Pane; use ::nix::pty::Winsize; use ::std::os::unix::io::RawFd; +use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::time::Instant; @@ -10,7 +11,7 @@ use crate::panes::terminal_character::{ }; use crate::pty_bus::VteBytes; -#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)] +#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug, Serialize, Deserialize)] pub enum PaneId { Terminal(RawFd), Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct? @@ -18,7 +19,7 @@ pub enum PaneId { /// Contains the position and size of a [`Pane`], or more generally of any terminal, measured /// in character rows and columns. -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] pub struct PositionAndSize { pub x: usize, pub y: usize, diff --git a/src/client/tab.rs b/src/client/tab.rs index b01e04e5..0098d78b 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -2,22 +2,23 @@ //! as well as how they should be resized use crate::client::pane_resizer::PaneResizer; -use crate::common::{input::handler::parse_keys, AppInstruction, SenderWithContext}; +use crate::common::{input::handler::parse_keys, SenderWithContext}; use crate::layout::Layout; -use crate::os_input_output::OsApi; +use crate::os_input_output::ServerOsApi; use crate::panes::{PaneId, PositionAndSize, TerminalPane}; use crate::pty_bus::{PtyInstruction, VteBytes}; +use crate::server::ServerInstruction; use crate::utils::shared::adjust_to_size; use crate::wasm_vm::PluginInstruction; use crate::{boundaries::Boundaries, panes::PluginPane}; use serde::{Deserialize, Serialize}; use std::os::unix::io::RawFd; +use std::sync::mpsc::channel; use std::time::Instant; use std::{ cmp::Reverse, collections::{BTreeMap, HashSet}, }; -use std::{io::Write, sync::mpsc::channel}; use zellij_tile::data::{Event, InputMode, ModeInfo, Palette}; const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this @@ -67,11 +68,11 @@ pub struct Tab { max_panes: Option<usize>, full_screen_ws: PositionAndSize, fullscreen_is_active: bool, + os_api: Box<dyn ServerOsApi>, + send_plugin_instructions: SenderWithContext<PluginInstruction>, + send_pty_instructions: SenderWithContext<PtyInstruction>, + send_server_instructions: SenderWithContext<ServerInstruction>, synchronize_is_active: bool, - os_api: Box<dyn OsApi>, - pub send_pty_instructions: SenderWithContext<PtyInstruction>, - pub send_plugin_instructions: SenderWithContext<PluginInstruction>, - pub send_app_instructions: SenderWithContext<AppInstruction>, should_clear_display_before_rendering: bool, pub mode_info: ModeInfo, pub input_mode: InputMode, @@ -225,10 +226,10 @@ impl Tab { position: usize, name: String, full_screen_ws: &PositionAndSize, - mut os_api: Box<dyn OsApi>, - send_pty_instructions: SenderWithContext<PtyInstruction>, + mut os_api: Box<dyn ServerOsApi>, send_plugin_instructions: SenderWithContext<PluginInstruction>, - send_app_instructions: SenderWithContext<AppInstruction>, + send_pty_instructions: SenderWithContext<PtyInstruction>, + send_server_instructions: SenderWithContext<ServerInstruction>, max_panes: Option<usize>, pane_id: Option<PaneId>, mode_info: ModeInfo, @@ -260,9 +261,9 @@ impl Tab { fullscreen_is_active: false, synchronize_is_active: false, os_api, - send_app_instructions, - send_pty_instructions, send_plugin_instructions, + send_pty_instructions, + send_server_instructions, should_clear_display_before_rendering: false, mode_info, input_mode, @@ -400,8 +401,8 @@ impl Tab { ); if terminal_id_to_split.is_none() { self.send_pty_instructions - .send(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty - .unwrap(); + .send(PtyInstruction::ClosePane(pid)) + .unwrap(); // we can't open this pane, close the pty return; // likely no terminal large enough to split } let terminal_id_to_split = terminal_id_to_split.unwrap(); @@ -481,8 +482,8 @@ impl Tab { let active_pane = self.panes.get_mut(active_pane_id).unwrap(); if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 + 1 { self.send_pty_instructions - .send(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty - .unwrap(); + .send(PtyInstruction::ClosePane(pid)) + .unwrap(); // we can't open this pane, close the pty return; } let terminal_ws = PositionAndSize { @@ -538,8 +539,8 @@ impl Tab { let active_pane = self.panes.get_mut(active_pane_id).unwrap(); if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 + 1 { self.send_pty_instructions - .send(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty - .unwrap(); + .send(PtyInstruction::ClosePane(pid)) + .unwrap(); // we can't open this pane, close the pty return; } let terminal_ws = PositionAndSize { @@ -729,20 +730,16 @@ impl Tab { if self.panes_contain_widechar() { self.set_force_render() } - let mut stdout = self.os_api.get_stdout_writer(); + let mut output = String::new(); let mut boundaries = Boundaries::new( self.full_screen_ws.columns as u16, self.full_screen_ws.rows as u16, ); let hide_cursor = "\u{1b}[?25l"; - stdout - .write_all(&hide_cursor.as_bytes()) - .expect("cannot write to stdout"); + output.push_str(hide_cursor); if self.should_clear_display_before_rendering { let clear_display = "\u{1b}[2J"; - stdout - .write_all(&clear_display.as_bytes()) - .expect("cannot write to stdout"); + output.push_str(clear_display); self.should_clear_display_before_rendering = false; } for (kind, pane) in self.panes.iter_mut() { @@ -761,48 +758,39 @@ impl Tab { adjust_to_size(&vte_output, pane.rows(), pane.columns()) }; // FIXME: Use Termion for cursor and style clearing? - write!( - stdout, + output.push_str(&format!( "\u{1b}[{};{}H\u{1b}[m{}", pane.y() + 1, pane.x() + 1, vte_output - ) - .expect("cannot write to stdout"); + )); } } } // TODO: only render (and calculate) boundaries if there was a resize - let vte_output = boundaries.vte_output(); - stdout - .write_all(&vte_output.as_bytes()) - .expect("cannot write to stdout"); + output.push_str(&boundaries.vte_output()); match self.get_active_terminal_cursor_position() { Some((cursor_position_x, cursor_position_y)) => { let show_cursor = "\u{1b}[?25h"; - let goto_cursor_position = format!( + let goto_cursor_position = &format!( "\u{1b}[{};{}H\u{1b}[m", cursor_position_y + 1, cursor_position_x + 1 ); // goto row/col - stdout - .write_all(&show_cursor.as_bytes()) - .expect("cannot write to stdout"); - stdout - .write_all(&goto_cursor_position.as_bytes()) - .expect("cannot write to stdout"); - stdout.flush().expect("could not flush"); + output.push_str(show_cursor); + output.push_str(goto_cursor_position); } None => { let hide_cursor = "\u{1b}[?25l"; - stdout - .write_all(&hide_cursor.as_bytes()) - .expect("cannot write to stdout"); - stdout.flush().expect("could not flush"); + output.push_str(hide_cursor); } } + + self.send_server_instructions + .send(ServerInstruction::Render(Some(output))) + .unwrap(); } fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> { self.panes.iter() |
