summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKunal Mohan <kunalmohan99@gmail.com>2021-05-22 15:45:47 +0530
committerKunal Mohan <kunalmohan99@gmail.com>2021-05-22 22:19:50 +0530
commitfa0a7e05c384f0da1a6fe3dd240181a6ff528b58 (patch)
treeb188dbaefc080c0155475634c7427d8322fa5ef9
parentac082a1c930a356253f5cb3b685aabe28f87cba6 (diff)
Add ability to attach to sessions
-rw-r--r--Cargo.lock3
-rw-r--r--src/main.rs15
-rw-r--r--src/tests/fakes.rs1
-rw-r--r--src/tests/mod.rs2
-rw-r--r--zellij-client/Cargo.toml1
-rw-r--r--zellij-client/src/input_handler.rs4
-rw-r--r--zellij-client/src/lib.rs93
-rw-r--r--zellij-server/src/lib.rs63
-rw-r--r--zellij-server/src/os_input_output.rs25
-rw-r--r--zellij-server/src/route.rs21
-rw-r--r--zellij-server/src/screen.rs1
-rw-r--r--zellij-utils/Cargo.toml2
-rw-r--r--zellij-utils/src/consts.rs5
-rw-r--r--zellij-utils/src/errors.rs1
-rw-r--r--zellij-utils/src/ipc.rs35
15 files changed, 208 insertions, 64 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 48aa2bf2c..a637785b5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2299,6 +2299,7 @@ dependencies = [
name = "zellij-client"
version = "0.12.0"
dependencies = [
+ "names",
"termbg",
"zellij-utils",
]
@@ -2346,8 +2347,8 @@ dependencies = [
"interprocess",
"lazy_static",
"libc",
- "names",
"nix",
+ "once_cell",
"serde",
"serde_yaml",
"signal-hook 0.3.8",
diff --git a/src/main.rs b/src/main.rs
index 82fc14d5e..319ad307d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -50,7 +50,20 @@ pub fn main() {
process::exit(1);
}
};
- start_client(Box::new(os_input), opts, config);
+ if let Some(Command::Sessions(Sessions::Attach {
+ session_name,
+ force,
+ })) = opts.command.clone()
+ {
+ start_client(
+ Box::new(os_input),
+ opts,
+ config,
+ Some((session_name, force)),
+ );
+ } else {
+ start_client(Box::new(os_input), opts, config, None);
+ }
}
}
diff --git a/src/tests/fakes.rs b/src/tests/fakes.rs
index c81e226ff..cb41821a3 100644
--- a/src/tests/fakes.rs
+++ b/src/tests/fakes.rs
@@ -300,6 +300,7 @@ impl ServerOsApi for FakeInputOutput {
}
fn add_client_sender(&self) {}
fn remove_client_sender(&self) {}
+ fn send_to_temp_client(&self, _msg: ServerToClientMsg) {}
fn update_receiver(&mut self, _stream: LocalSocketStream) {}
fn load_palette(&self) -> Palette {
default_palette()
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
index 3e548db7c..f8a48a39f 100644
--- a/src/tests/mod.rs
+++ b/src/tests/mod.rs
@@ -21,6 +21,6 @@ pub fn start(
start_server(server_os_input, PathBuf::from(""));
})
.unwrap();
- start_client(client_os_input, opts, config);
+ start_client(client_os_input, opts, config, None);
let _ = server_thread.join();
}
diff --git a/zellij-client/Cargo.toml b/zellij-client/Cargo.toml
index f9d0db086..76a25c1c4 100644
--- a/zellij-client/Cargo.toml
+++ b/zellij-client/Cargo.toml
@@ -9,6 +9,7 @@ license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+names = "0.11.0"
termbg = "0.2.3"
zellij-utils = { path = "../zellij-utils/", version = "0.12.0" }
diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs
index 9fa001cda..70f6e9824 100644
--- a/zellij-client/src/input_handler.rs
+++ b/zellij-client/src/input_handler.rs
@@ -7,7 +7,7 @@ use zellij_utils::{
channels::{SenderWithContext, OPENCALLS},
errors::ContextType,
input::{actions::Action, cast_termion_key, config::Config, keybinds::Keybinds},
- ipc::ClientToServerMsg,
+ ipc::{ClientToServerMsg, ExitReason},
};
use termion::input::TermReadEventsAndRaw;
@@ -169,7 +169,7 @@ impl InputHandler {
/// same as quitting Zellij).
fn exit(&mut self) {
self.send_client_instructions
- .send(ClientInstruction::Exit)
+ .send(ClientInstruction::Exit(ExitReason::Normal))
.unwrap();
}
}
diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs
index 0eff09af0..7042bed72 100644
--- a/zellij-client/src/lib.rs
+++ b/zellij-client/src/lib.rs
@@ -20,26 +20,24 @@ use zellij_utils::{
consts::{SESSION_NAME, ZELLIJ_IPC_PIPE},
errors::{ClientContext, ContextType, ErrorInstruction},
input::{actions::Action, config::Config, options::Options},
- ipc::{ClientAttributes, ClientToServerMsg, ServerToClientMsg},
+ ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
};
/// Instructions related to the client-side application
#[derive(Debug, Clone)]
pub(crate) enum ClientInstruction {
Error(String),
- Render(Option<String>),
+ Render(String),
UnblockInputThread,
- Exit,
- ServerError(String),
+ Exit(ExitReason),
}
impl From<ServerToClientMsg> for ClientInstruction {
fn from(instruction: ServerToClientMsg) -> Self {
match instruction {
- ServerToClientMsg::Exit => ClientInstruction::Exit,
+ ServerToClientMsg::Exit(e) => ClientInstruction::Exit(e),
ServerToClientMsg::Render(buffer) => ClientInstruction::Render(buffer),
ServerToClientMsg::UnblockInputThread => ClientInstruction::UnblockInputThread,
- ServerToClientMsg::ServerError(backtrace) => ClientInstruction::ServerError(backtrace),
}
}
}
@@ -47,9 +45,8 @@ impl From<ServerToClientMsg> for ClientInstruction {
impl From<&ClientInstruction> for ClientContext {
fn from(client_instruction: &ClientInstruction) -> Self {
match *client_instruction {
- ClientInstruction::Exit => ClientContext::Exit,
+ ClientInstruction::Exit(_) => ClientContext::Exit,
ClientInstruction::Error(_) => ClientContext::Error,
- ClientInstruction::ServerError(_) => ClientContext::ServerError,
ClientInstruction::Render(_) => ClientContext::Render,
ClientInstruction::UnblockInputThread => ClientContext::UnblockInputThread,
}
@@ -79,7 +76,12 @@ fn spawn_server(socket_path: &Path) -> io::Result<()> {
}
}
-pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: Config) {
+pub fn start_client(
+ mut os_input: Box<dyn ClientOsApi>,
+ opts: CliArgs,
+ config: Config,
+ attach_to: Option<(String, bool)>,
+) {
let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l";
let take_snapshot = "\u{1b}[?1049h";
let bracketed_paste = "\u{1b}[?2004h";
@@ -94,12 +96,6 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
.write(clear_client_terminal_attributes.as_bytes())
.unwrap();
std::env::set_var(&"ZELLIJ", "0");
- std::env::set_var(&"ZELLIJ_SESSION_NAME", &*SESSION_NAME);
-
- #[cfg(not(any(feature = "test", test)))]
- spawn_server(&*ZELLIJ_IPC_PIPE).unwrap();
-
- let mut command_is_executing = CommandIsExecuting::new();
let config_options = Options::from_cli(&config.options, opts.command.clone());
@@ -108,12 +104,34 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
position_and_size: full_screen_ws,
palette,
};
+
+ #[cfg(not(any(feature = "test", test)))]
+ let first_msg = if let Some((name, force)) = attach_to {
+ SESSION_NAME.set(name).unwrap();
+ std::env::set_var(&"ZELLIJ_SESSION_NAME", SESSION_NAME.get().unwrap());
+
+ ClientToServerMsg::AttachClient(client_attributes, force)
+ } else {
+ SESSION_NAME
+ .set(names::Generator::default().next().unwrap())
+ .unwrap();
+ std::env::set_var(&"ZELLIJ_SESSION_NAME", SESSION_NAME.get().unwrap());
+
+ spawn_server(&*ZELLIJ_IPC_PIPE).unwrap();
+
+ ClientToServerMsg::NewClient(client_attributes, Box::new(opts), Box::new(config_options))
+ };
+ #[cfg(any(feature = "test", test))]
+ let first_msg = {
+ let _ = SESSION_NAME.set("".into());
+ ClientToServerMsg::NewClient(client_attributes, Box::new(opts), Box::new(config_options))
+ };
+
os_input.connect_to_server(&*ZELLIJ_IPC_PIPE);
- os_input.send_to_server(ClientToServerMsg::NewClient(
- client_attributes,
- Box::new(opts),
- Box::new(config_options),
- ));
+ os_input.send_to_server(first_msg);
+
+ let mut command_is_executing = CommandIsExecuting::new();
+
os_input.set_raw_mode(0);
let _ = os_input
.get_stdout_writer()
@@ -170,7 +188,7 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
let send_client_instructions = send_client_instructions.clone();
move || {
send_client_instructions
- .send(ClientInstruction::Exit)
+ .send(ClientInstruction::Exit(ExitReason::Normal))
.unwrap()
}
}),
@@ -187,11 +205,8 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
move || loop {
let (instruction, err_ctx) = os_input.recv_from_server();
err_ctx.update_thread_ctx();
- match instruction {
- ServerToClientMsg::Exit | ServerToClientMsg::ServerError(_) => {
- should_break = true;
- }
- _ => {}
+ if let ServerToClientMsg::Exit(_) = instruction {
+ should_break = true;
}
send_client_instructions.send(instruction.into()).unwrap();
if should_break {
@@ -216,6 +231,8 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
std::process::exit(1);
};
+ let exit_msg: String;
+
loop {
let (client_instruction, mut err_ctx) = receive_client_instructions
.recv()
@@ -223,21 +240,25 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
err_ctx.add_call(ContextType::Client((&client_instruction).into()));
match client_instruction {
- ClientInstruction::Exit => break,
+ ClientInstruction::Exit(reason) => {
+ match reason {
+ ExitReason::Error(_) => handle_error(format!("{}", reason)),
+ ExitReason::ForceDetached => {
+ os_input.send_to_server(ClientToServerMsg::ClientDetached);
+ }
+ _ => {}
+ }
+ exit_msg = format!("{}", reason);
+ break;
+ }
ClientInstruction::Error(backtrace) => {
let _ = os_input.send_to_server(ClientToServerMsg::Action(Action::Quit));
handle_error(backtrace);
}
- ClientInstruction::ServerError(backtrace) => {
- handle_error(backtrace);
- }
ClientInstruction::Render(output) => {
- if output.is_none() {
- break;
- }
let mut stdout = os_input.get_stdout_writer();
stdout
- .write_all(&output.unwrap().as_bytes())
+ .write_all(&output.as_bytes())
.expect("cannot write to stdout");
stdout.flush().expect("could not flush");
}
@@ -255,8 +276,8 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
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
+ "{}\n{}{}{}{}\n",
+ goto_start_of_last_line, restore_snapshot, reset_style, show_cursor, exit_msg
);
os_input.unset_raw_mode(0);
diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs
index 586bd3e6e..c1f471c76 100644
--- a/zellij-server/src/lib.rs
+++ b/zellij-server/src/lib.rs
@@ -15,7 +15,7 @@ use std::sync::{Arc, RwLock};
use std::thread;
use std::{path::PathBuf, sync::mpsc};
use wasmer::Store;
-use zellij_tile::data::PluginCapabilities;
+use zellij_tile::data::{Event, InputMode, PluginCapabilities};
use crate::{
os_input_output::ServerOsApi,
@@ -30,8 +30,8 @@ use zellij_utils::{
channels::{ChannelWithContext, SenderType, SenderWithContext, SyncChannelWithContext},
cli::CliArgs,
errors::{ContextType, ErrorInstruction, ServerContext},
- input::options::Options,
- ipc::{ClientAttributes, ClientToServerMsg, ServerToClientMsg},
+ input::{get_mode_info, options::Options},
+ ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
setup::{get_default_data_dir, install::populate_data_dir},
};
@@ -44,13 +44,17 @@ pub(crate) enum ServerInstruction {
ClientExit,
Error(String),
DetachSession,
+ AttachClient(ClientAttributes, bool),
}
impl From<ClientToServerMsg> for ServerInstruction {
fn from(instruction: ClientToServerMsg) -> Self {
match instruction {
- ClientToServerMsg::NewClient(pos, opts, options) => {
- ServerInstruction::NewClient(pos, opts, options)
+ ClientToServerMsg::NewClient(attrs, opts, options) => {
+ ServerInstruction::NewClient(attrs, opts, options)
+ }
+ ClientToServerMsg::AttachClient(attrs, force) => {
+ ServerInstruction::AttachClient(attrs, force)
}
_ => unreachable!(),
}
@@ -66,6 +70,7 @@ impl From<&ServerInstruction> for ServerContext {
ServerInstruction::ClientExit => ServerContext::ClientExit,
ServerInstruction::Error(_) => ServerContext::Error,
ServerInstruction::DetachSession => ServerContext::DetachSession,
+ ServerInstruction::AttachClient(..) => ServerContext::AttachClient,
}
}
}
@@ -134,8 +139,9 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
let session_data = session_data.clone();
let os_input = os_input.clone();
let to_server = to_server.clone();
+ let session_state = session_state.clone();
- move || route_thread_main(session_data, os_input, to_server)
+ move || route_thread_main(session_data, session_state, os_input, to_server)
})
.unwrap();
#[cfg(not(any(feature = "test", test)))]
@@ -148,6 +154,7 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
let os_input = os_input.clone();
let session_data = session_data.clone();
+ let session_state = session_state.clone();
let to_server = to_server.clone();
let socket_path = socket_path.clone();
move || {
@@ -160,6 +167,7 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
let mut os_input = os_input.clone();
os_input.update_receiver(stream);
let session_data = session_data.clone();
+ let session_state = session_state.clone();
let to_server = to_server.clone();
thread::Builder::new()
.name("server_router".to_string())
@@ -168,7 +176,14 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
let os_input = os_input.clone();
let to_server = to_server.clone();
- move || route_thread_main(session_data, os_input, to_server)
+ move || {
+ route_thread_main(
+ session_data,
+ session_state,
+ os_input,
+ to_server,
+ )
+ }
})
.unwrap();
}
@@ -204,6 +219,28 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.send_to_pty(PtyInstruction::NewTab)
.unwrap();
}
+ ServerInstruction::AttachClient(attrs, _) => {
+ *session_state.write().unwrap() = SessionState::Attached;
+ let rlock = session_data.read().unwrap();
+ let session_data = rlock.as_ref().unwrap();
+ session_data
+ .senders
+ .send_to_screen(ScreenInstruction::TerminalResize(attrs.position_and_size))
+ .unwrap();
+ let mode_info =
+ get_mode_info(InputMode::Normal, attrs.palette, session_data.capabilities);
+ session_data
+ .senders
+ .send_to_screen(ScreenInstruction::ChangeMode(mode_info.clone()))
+ .unwrap();
+ session_data
+ .senders
+ .send_to_plugin(PluginInstruction::Update(
+ None,
+ Event::ModeUpdate(mode_info),
+ ))
+ .unwrap();
+ }
ServerInstruction::UnblockInputThread => {
if *session_state.read().unwrap() == SessionState::Attached {
os_input.send_to_client(ServerToClientMsg::UnblockInputThread);
@@ -211,22 +248,26 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
}
ServerInstruction::ClientExit => {
*session_data.write().unwrap() = None;
- os_input.send_to_client(ServerToClientMsg::Exit);
+ os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Normal));
break;
}
ServerInstruction::DetachSession => {
*session_state.write().unwrap() = SessionState::Detached;
- os_input.send_to_client(ServerToClientMsg::Exit);
+ os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Normal));
os_input.remove_client_sender();
}
ServerInstruction::Render(output) => {
if *session_state.read().unwrap() == SessionState::Attached {
- os_input.send_to_client(ServerToClientMsg::Render(output));
+ os_input.send_to_client(
+ output.map_or(ServerToClientMsg::Exit(ExitReason::Normal), |op| {
+ ServerToClientMsg::Render(op)
+ }),
+ );
}
}
ServerInstruction::Error(backtrace) => {
if *session_state.read().unwrap() == SessionState::Attached {
- os_input.send_to_client(ServerToClientMsg::ServerError(backtrace));
+ os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Error(backtrace)));
}
break;
}
diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs
index 333b41288..51903c9a5 100644
--- a/zellij-server/src/os_input_output.rs
+++ b/zellij-server/src/os_input_output.rs
@@ -17,7 +17,10 @@ use signal_hook::consts::*;
use zellij_tile::data::Palette;
use zellij_utils::{
errors::ErrorContext,
- ipc::{ClientToServerMsg, IpcReceiverWithContext, IpcSenderWithContext, ServerToClientMsg},
+ ipc::{
+ ClientToServerMsg, ExitReason, IpcReceiverWithContext, IpcSenderWithContext,
+ ServerToClientMsg,
+ },
shared::default_palette,
};
@@ -156,6 +159,7 @@ pub trait ServerOsApi: Send + Sync {
fn send_to_client(&self, msg: ServerToClientMsg);
/// Adds a sender to client
fn add_client_sender(&self);
+ fn send_to_temp_client(&self, msg: ServerToClientMsg);
/// Removes the sender to client
fn remove_client_sender(&self);
/// Update the receiver socket for the client
@@ -211,7 +215,6 @@ impl ServerOsApi for ServerOsInputOutput {
.send(msg);
}
fn add_client_sender(&self) {
- assert!(self.send_instructions_to_client.lock().unwrap().is_none());
let sender = self
.receive_instructions_from_client
.as_ref()
@@ -219,7 +222,23 @@ impl ServerOsApi for ServerOsInputOutput {
.lock()
.unwrap()
.get_sender();
- *self.send_instructions_to_client.lock().unwrap() = Some(sender);
+ let old_sender = self
+ .send_instructions_to_client
+ .lock()
+ .unwrap()
+ .replace(sender);
+ if let Some(mut sender) = old_sender {
+ sender.send(ServerToClientMsg::Exit(ExitReason::ForceDetached));
+ }
+ }
+ fn send_to_temp_client(&self, msg: ServerToClientMsg) {
+ self.receive_instructions_from_client
+ .as_ref()
+ .unwrap()
+ .lock()
+ .unwrap()
+ .get_sender()
+ .send(msg);
}
fn remove_client_sender(&self) {
assert!(self.send_instructions_to_client.lock().unwrap().is_some());
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs
index 6804fdf78..c8f78caae 100644
--- a/zellij-server/src/route.rs
+++ b/zellij-server/src/route.rs
@@ -4,7 +4,7 @@ use zellij_utils::zellij_tile::data::Event;
use crate::{
os_input_output::ServerOsApi, pty::PtyInstruction, screen::ScreenInstruction,
- wasm_vm::PluginInstruction, ServerInstruction, SessionMetaData,
+ wasm_vm::PluginInstruction, ServerInstruction, SessionMetaData, SessionState,
};
use zellij_utils::{
channels::SenderWithContext,
@@ -12,7 +12,7 @@ use zellij_utils::{
actions::{Action, Direction},
get_mode_info,
},
- ipc::ClientToServerMsg,
+ ipc::{ClientToServerMsg, ExitReason, ServerToClientMsg},
};
fn route_action(
@@ -203,6 +203,7 @@ fn route_action(
pub(crate) fn route_thread_main(
session_data: Arc<RwLock<Option<SessionMetaData>>>,
+ session_state: Arc<RwLock<SessionState>>,
os_input: Box<dyn ServerOsApi>,
to_server: SenderWithContext<ServerInstruction>,
) {
@@ -210,6 +211,7 @@ pub(crate) fn route_thread_main(
let (instruction, err_ctx) = os_input.recv_from_client();
err_ctx.update_thread_ctx();
let rlocked_sessions = session_data.read().unwrap();
+
match instruction {
ClientToServerMsg::Action(action) => {
if let Some(rlocked_sessions) = rlocked_sessions.as_ref() {
@@ -227,9 +229,24 @@ pub(crate) fn route_thread_main(
.unwrap();
}
ClientToServerMsg::NewClient(..) => {
+ if *session_state.read().unwrap() != SessionState::Uninitialized {
+ os_input.send_to_temp_client(ServerToClientMsg::Exit(ExitReason::Error(
+ "Cannot add new client".into(),
+ )));
+ break;
+ }
+ os_input.add_client_sender();
+ to_server.send(instruction.into()).unwrap();
+ }
+ ClientToServerMsg::AttachClient(_, force) => {
+ if *session_state.read().unwrap() == SessionState::Attached && !force {
+ os_input.send_to_temp_client(ServerToClientMsg::Exit(ExitReason::CannotAttach));
+ break;
+ }
os_input.add_client_sender();
to_server.send(instruction.into()).unwrap();
}
+ ClientToServerMsg::ClientDetached => break,
}
}
}
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index 24b66199e..068037eea 100644
--- a/zellij-server/src/s