aboutsummaryrefslogtreecommitdiffstats
path: root/src/client
diff options
context:
space:
mode:
authorKunal Mohan <44079328+kunalmohan@users.noreply.github.com>2021-05-06 22:11:07 +0530
committerGitHub <noreply@github.com>2021-05-06 22:11:07 +0530
commitd5433f8f8995c155f231c823d49813882b6212bf (patch)
tree529d5ee706ded18cd24f26a3b0372bf17ecb81ed /src/client
parentdocs(changelog): Add Manpage #455 (diff)
parentexplicitly set file and directory permissions (diff)
downloadsl0ppij-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.rs170
-rw-r--r--src/client/pane_resizer.rs6
-rw-r--r--src/client/panes/terminal_pane.rs5
-rw-r--r--src/client/tab.rs78
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()