aboutsummaryrefslogtreecommitdiffstats
path: root/src/server
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/server
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/server')
-rw-r--r--src/server/mod.rs814
1 files changed, 810 insertions, 4 deletions
diff --git a/src/server/mod.rs b/src/server/mod.rs
index 626b4e6b..6eb2455b 100644
--- a/src/server/mod.rs
+++ b/src/server/mod.rs
@@ -1,5 +1,811 @@
-use super::super::common::{screen};
+use interprocess::local_socket::LocalSocketListener;
+use serde::{Deserialize, Serialize};
+use std::path::PathBuf;
+use std::sync::mpsc::channel;
+use std::thread;
+use std::{collections::HashMap, fs};
+use std::{
+ collections::HashSet,
+ str::FromStr,
+ sync::{Arc, Mutex, RwLock},
+};
+use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
+use wasmer_wasi::{Pipe, WasiState};
+use zellij_tile::data::{Event, EventType, InputMode, ModeInfo};
-pub fn start_server() {
- // TODO
-} \ No newline at end of file
+use crate::cli::CliArgs;
+use crate::client::ClientInstruction;
+use crate::common::{
+ errors::{ContextType, PluginContext, PtyContext, ScreenContext, ServerContext},
+ input::actions::{Action, Direction},
+ input::handler::get_mode_info,
+ os_input_output::{set_permissions, ServerOsApi},
+ pty_bus::{PtyBus, PtyInstruction},
+ screen::{Screen, ScreenInstruction},
+ setup::install::populate_data_dir,
+ utils::consts::{ZELLIJ_IPC_PIPE, ZELLIJ_PROJ_DIR},
+ wasm_vm::{wasi_read_string, wasi_write_object, zellij_exports, PluginEnv, PluginInstruction},
+ ChannelWithContext, SenderType, SenderWithContext,
+};
+use crate::layout::Layout;
+use crate::panes::PaneId;
+use crate::panes::PositionAndSize;
+
+/// Instructions related to server-side application including the
+/// ones sent by client to server
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub enum ServerInstruction {
+ TerminalResize(PositionAndSize),
+ NewClient(PositionAndSize, CliArgs),
+ Action(Action),
+ Render(Option<String>),
+ UnblockInputThread,
+ ClientExit,
+}
+
+struct SessionMetaData {
+ pub send_pty_instructions: SenderWithContext<PtyInstruction>,
+ pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
+ pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ screen_thread: Option<thread::JoinHandle<()>>,
+ pty_thread: Option<thread::JoinHandle<()>>,
+ wasm_thread: Option<thread::JoinHandle<()>>,
+}
+
+impl Drop for SessionMetaData {
+ fn drop(&mut self) {
+ let _ = self.send_pty_instructions.send(PtyInstruction::Exit);
+ let _ = self.send_screen_instructions.send(ScreenInstruction::Exit);
+ let _ = self.send_plugin_instructions.send(PluginInstruction::Exit);
+ let _ = self.screen_thread.take().unwrap().join();
+ let _ = self.pty_thread.take().unwrap().join();
+ let _ = self.wasm_thread.take().unwrap().join();
+ }
+}
+
+pub fn start_server(os_input: Box<dyn ServerOsApi>) -> thread::JoinHandle<()> {
+ let (send_server_instructions, receive_server_instructions): ChannelWithContext<
+ ServerInstruction,
+ > = channel();
+ let send_server_instructions =
+ SenderWithContext::new(SenderType::Sender(send_server_instructions));
+ let sessions: Arc<RwLock<Option<SessionMetaData>>> = Arc::new(RwLock::new(None));
+
+ #[cfg(test)]
+ handle_client(
+ sessions.clone(),
+ os_input.clone(),
+ send_server_instructions.clone(),
+ );
+ #[cfg(not(test))]
+ let _ = thread::Builder::new()
+ .name("server_listener".to_string())
+ .spawn({
+ let os_input = os_input.clone();
+ let sessions = sessions.clone();
+ let send_server_instructions = send_server_instructions.clone();
+ move || {
+ drop(std::fs::remove_file(&*ZELLIJ_IPC_PIPE));
+ let listener = LocalSocketListener::bind(&**ZELLIJ_IPC_PIPE).unwrap();
+ set_permissions(&*ZELLIJ_IPC_PIPE);
+ for stream in listener.incoming() {
+ match stream {
+ Ok(stream) => {
+ let mut os_input = os_input.clone();
+ os_input.update_receiver(stream);
+ let sessions = sessions.clone();
+ let send_server_instructions = send_server_instructions.clone();
+ handle_client(sessions, os_input, send_server_instructions);
+ }
+ Err(err) => {
+ panic!("err {:?}", err);
+ }
+ }
+ }
+ }
+ });
+
+ thread::Builder::new()
+ .name("server_thread".to_string())
+ .spawn({
+ move || loop {
+ let (instruction, mut err_ctx) = receive_server_instructions.recv().unwrap();
+ err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction)));
+ match instruction {
+ ServerInstruction::NewClient(full_screen_ws, opts) => {
+ let session_data = init_session(
+ os_input.clone(),
+ opts,
+ send_server_instructions.clone(),
+ full_screen_ws,
+ );
+ *sessions.write().unwrap() = Some(session_data);
+ sessions
+ .read()
+ .unwrap()
+ .as_ref()
+ .unwrap()
+ .send_pty_instructions
+ .send(PtyInstruction::NewTab)
+ .unwrap();
+ }
+ ServerInstruction::UnblockInputThread => {
+ os_input.send_to_client(ClientInstruction::UnblockInputThread);
+ }
+ ServerInstruction::ClientExit => {
+ *sessions.write().unwrap() = None;
+ os_input.send_to_client(ClientInstruction::Exit);
+ drop(std::fs::remove_file(&*ZELLIJ_IPC_PIPE));
+ break;
+ }
+ ServerInstruction::Render(output) => {
+ os_input.send_to_client(ClientInstruction::Render(output))
+ }
+ _ => panic!("Received unexpected instruction."),
+ }
+ }
+ })
+ .unwrap()
+}
+
+fn handle_client(
+ sessions: Arc<RwLock<Option<SessionMetaData>>>,
+ mut os_input: Box<dyn ServerOsApi>,
+ send_server_instructions: SenderWithContext<ServerInstruction>,
+) {
+ thread::Builder::new()
+ .name("server_router".to_string())
+ .spawn(move || loop {
+ let (instruction, mut err_ctx) = os_input.recv_from_client();
+ err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction)));
+ let rlocked_sessions = sessions.read().unwrap();
+ match instruction {
+ ServerInstruction::ClientExit => {
+ send_server_instructions.send(instruction).unwrap();
+ break;
+ }
+ ServerInstruction::Action(action) => {
+ route_action(action, rlocked_sessions.as_ref().unwrap(), &*os_input);
+ }
+ ServerInstruction::TerminalResize(new_size) => {
+ rlocked_sessions
+ .as_ref()
+ .unwrap()
+ .send_screen_instructions
+ .send(ScreenInstruction::TerminalResize(new_size))
+ .unwrap();
+ }
+ ServerInstruction::NewClient(..) => {
+ os_input.add_client_sender();
+ send_server_instructions.send(instruction).unwrap();
+ }
+ _ => {
+ send_server_instructions.send(instruction).unwrap();
+ }
+ }
+ })
+ .unwrap();
+}
+
+fn init_session(
+ os_input: Box<dyn ServerOsApi>,
+ opts: CliArgs,
+ send_server_instructions: SenderWithContext<ServerInstruction>,
+ full_screen_ws: PositionAndSize,
+) -> SessionMetaData {
+ let (send_screen_instructions, receive_screen_instructions): ChannelWithContext<
+ ScreenInstruction,
+ > = channel();
+ let send_screen_instructions =
+ SenderWithContext::new(SenderType::Sender(send_screen_instructions));
+
+ let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext<
+ PluginInstruction,
+ > = channel();
+ let send_plugin_instructions =
+ SenderWithContext::new(SenderType::Sender(send_plugin_instructions));
+ let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> =
+ channel();
+ let send_pty_instructions = SenderWithContext::new(SenderType::Sender(send_pty_instructions));
+
+ // Determine and initialize the data directory
+ let data_dir = opts
+ .data_dir
+ .unwrap_or_else(|| ZELLIJ_PROJ_DIR.data_dir().to_path_buf());
+ populate_data_dir(&data_dir);
+
+ // Don't use default layouts in tests, but do everywhere else
+ #[cfg(not(test))]
+ let default_layout = Some(PathBuf::from("default"));
+ #[cfg(test)]
+ let default_layout = None;
+ let maybe_layout = opts
+ .layout
+ .map(|p| Layout::new(&p, &data_dir))
+ .or_else(|| default_layout.map(|p| Layout::from_defaults(&p, &data_dir)));
+
+ let mut pty_bus = PtyBus::new(
+ receive_pty_instructions,
+ send_screen_instructions.clone(),
+ send_plugin_instructions.clone(),
+ os_input.clone(),
+ opts.debug,
+ );
+
+ let pty_thread = thread::Builder::new()
+ .name("pty".to_string())
+ .spawn({
+ let send_server_instructions = send_server_instructions.clone();
+ move || loop {
+ let (event, mut err_ctx) = pty_bus
+ .receive_pty_instructions
+ .recv()
+ .expect("failed to receive event on channel");
+ err_ctx.add_call(ContextType::Pty(PtyContext::from(&event)));
+ match event {
+ PtyInstruction::SpawnTerminal(file_to_open) => {
+ let pid = pty_bus.spawn_terminal(file_to_open);
+ pty_bus
+ .send_screen_instructions
+ .send(ScreenInstruction::NewPane(PaneId::Terminal(pid)))
+ .unwrap();
+ }
+ PtyInstruction::SpawnTerminalVertically(file_to_open) => {
+ let pid = pty_bus.spawn_terminal(file_to_open);
+ pty_bus
+ .send_screen_instructions
+ .send(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid)))
+ .unwrap();
+ }
+ PtyInstruction::SpawnTerminalHorizontally(file_to_open) => {
+ let pid = pty_bus.spawn_terminal(file_to_open);
+ pty_bus
+ .send_screen_instructions
+ .send(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid)))
+ .unwrap();
+ }
+ PtyInstruction::NewTab => {
+ if let Some(layout) = maybe_layout.clone() {
+ pty_bus.spawn_terminals_for_layout(layout);
+ } else {
+ let pid = pty_bus.spawn_terminal(None);
+ pty_bus
+ .send_screen_instructions
+ .send(ScreenInstruction::NewTab(pid))
+ .unwrap();
+ }
+ }
+ PtyInstruction::ClosePane(id) => {
+ pty_bus.close_pane(id);
+ send_server_instructions
+ .send(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ PtyInstruction::CloseTab(ids) => {
+ pty_bus.close_tab(ids);
+ send_server_instructions
+ .send(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ PtyInstruction::Exit => {
+ break;
+ }
+ }
+ }
+ })
+ .unwrap();
+
+ let screen_thread = thread::Builder::new()
+ .name("screen".to_string())
+ .spawn({
+ let os_input = os_input.clone();
+ let send_plugin_instructions = send_plugin_instructions.clone();
+ let send_pty_instructions = send_pty_instructions.clone();
+ let send_server_instructions = send_server_instructions;
+ let max_panes = opts.max_panes;
+ let colors = os_input.load_palette();
+
+ move || {
+ let mut screen = Screen::new(
+ receive_screen_instructions,
+ send_plugin_instructions,
+ send_pty_instructions,
+ send_server_instructions,
+ &full_screen_ws,
+ os_input,
+ max_panes,
+ ModeInfo {
+ palette: colors,
+ ..ModeInfo::default()
+ },
+ InputMode::Normal,
+ colors,
+ );
+ loop {
+ let (event, mut err_ctx) = screen
+ .receiver
+ .recv()
+ .expect("failed to receive event on channel");
+ err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event)));
+ match event {
+ ScreenInstruction::PtyBytes(pid, vte_bytes) => {
+ let active_tab = screen.get_active_tab_mut().unwrap();
+ if active_tab.has_terminal_pid(pid) {
+ // it's most likely that this event is directed at the active tab
+ // look there first
+ active_tab.handle_pty_bytes(pid, vte_bytes);
+ } else {
+ // if this event wasn't directed at the active tab, start looking
+ // in other tabs
+ let all_tabs = screen.get_tabs_mut();
+ for tab in all_tabs.values_mut() {
+ if tab.has_terminal_pid(pid) {
+ tab.handle_pty_bytes(pid, vte_bytes);
+ break;
+ }
+ }
+ }
+ }
+ ScreenInstruction::Render => {
+ screen.render();
+ }
+ ScreenInstruction::NewPane(pid) => {
+ screen.get_active_tab_mut().unwrap().new_pane(pid);
+ screen
+ .send_server_instructions
+ .send(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::HorizontalSplit(pid) => {
+ screen.get_active_tab_mut().unwrap().horizontal_split(pid);
+ screen
+ .send_server_instructions
+ .send(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::VerticalSplit(pid) => {
+ screen.get_active_tab_mut().unwrap().vertical_split(pid);
+ screen
+ .send_server_instructions
+ .send(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::WriteCharacter(bytes) => {
+ let active_tab = screen.get_active_tab_mut().unwrap();
+ match active_tab.is_sync_panes_active() {
+ true => active_tab.write_to_terminals_on_current_tab(bytes),
+ false => active_tab.write_to_active_terminal(bytes),
+ }
+ }
+ ScreenInstruction::ResizeLeft => {
+ screen.get_active_tab_mut().unwrap().resize_left();
+ }
+ ScreenInstruction::ResizeRight => {
+ screen.get_active_tab_mut().unwrap().resize_right();
+ }
+ ScreenInstruction::ResizeDown => {
+ screen.get_active_tab_mut().unwrap().resize_down();
+ }
+ ScreenInstruction::ResizeUp => {
+ screen.get_active_tab_mut().unwrap().resize_up();
+ }
+ ScreenInstruction::SwitchFocus => {
+ screen.get_active_tab_mut().unwrap().move_focus();
+ }
+ ScreenInstruction::FocusNextPane => {
+ screen.get_active_tab_mut().unwrap().focus_next_pane();
+ }
+ ScreenInstruction::FocusPreviousPane => {
+ screen.get_active_tab_mut().unwrap().focus_previous_pane();
+ }
+ ScreenInstruction::MoveFocusLeft => {
+ screen.get_active_tab_mut().unwrap().move_focus_left();
+ }
+ ScreenInstruction::MoveFocusDown => {
+ screen.get_active_tab_mut().unwrap().move_focus_down();
+ }
+ ScreenInstruction::MoveFocusRight => {
+ screen.get_active_tab_mut().unwrap().move_focus_right();
+ }
+ ScreenInstruction::MoveFocusUp => {
+ screen.get_active_tab_mut().unwrap().move_focus_up();
+ }
+ ScreenInstruction::ScrollUp => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .scroll_active_terminal_up();
+ }
+ ScreenInstruction::ScrollDown => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .scroll_active_terminal_down();
+ }
+ ScreenInstruction::PageScrollUp => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .scroll_active_terminal_up_page();
+ }
+ ScreenInstruction::PageScrollDown => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .scroll_active_terminal_down_page();
+ }
+ ScreenInstruction::ClearScroll => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .clear_active_terminal_scroll();
+ }
+ ScreenInstruction::CloseFocusedPane => {
+ screen.get_active_tab_mut().unwrap().close_focused_pane();
+ screen.render();
+ }
+ ScreenInstruction::SetSelectable(id, selectable) => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .set_pane_selectable(id, selectable);
+ }
+ ScreenInstruction::SetMaxHeight(id, max_height) => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .set_pane_max_height(id, max_height);
+ }
+ ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .set_pane_invisible_borders(id, invisible_borders);
+ screen.render();
+ }
+ ScreenInstruction::ClosePane(id) => {
+ screen.get_active_tab_mut().unwrap().close_pane(id);
+ screen.render();
+ }
+ ScreenInstruction::ToggleActiveTerminalFullscreen => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .toggle_active_pane_fullscreen();
+ }
+ ScreenInstruction::NewTab(pane_id) => {
+ screen.new_tab(pane_id);
+ screen
+ .send_server_instructions
+ .send(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::SwitchTabNext => {
+ screen.switch_tab_next();
+ screen
+ .send_server_instructions
+ .send(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::SwitchTabPrev => {
+ screen.switch_tab_prev();
+ screen
+ .send_server_instructions
+ .send(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::CloseTab => {
+ screen.close_tab();
+ screen
+ .send_server_instructions
+ .send(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::ApplyLayout(layout, new_pane_pids) => {
+ screen.apply_layout(layout, new_pane_pids);
+ screen
+ .send_server_instructions
+ .send(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::GoToTab(tab_index) => {
+ screen.go_to_tab(tab_index as usize);
+ screen
+ .send_server_instructions
+ .send(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::UpdateTabName(c) => {
+ screen.update_active_tab_name(c);
+ screen
+ .send_server_instructions
+ .send(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::TerminalResize(new_size) => {
+ screen.resize_to_screen(new_size);
+ }
+ ScreenInstruction::ChangeMode(mode_info) => {
+ screen.change_mode(mode_info);
+ }
+ ScreenInstruction::ToggleActiveSyncPanes => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .toggle_sync_panes_is_active();
+ screen.update_tabs();
+ }
+ ScreenInstruction::Exit => {
+ break;
+ }
+ }
+ }
+ }
+ })
+ .unwrap();
+
+ let wasm_thread = thread::Builder::new()
+ .name("wasm".to_string())
+ .spawn({
+ let send_screen_instructions = send_screen_instructions.clone();
+ let send_pty_instructions = send_pty_instructions.clone();
+ let send_plugin_instructions = send_plugin_instructions.clone();
+
+ let store = Store::default();
+ let mut plugin_id = 0;
+ let mut plugin_map = HashMap::new();
+ move || loop {
+ let (event, mut err_ctx) = receive_plugin_instructions
+ .recv()
+ .expect("failed to receive event on channel");
+ err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
+ match event {
+ PluginInstruction::Load(pid_tx, path) => {
+ let plugin_dir = data_dir.join("plugins/");
+ let wasm_bytes = fs::read(&path)
+ .or_else(|_| fs::read(&path.with_extension("wasm")))
+ .or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm")))
+ .unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display()));
+
+ // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
+ let module = Module::new(&store, &wasm_bytes).unwrap();
+
+ let output = Pipe::new();
+ let input = Pipe::new();
+ let mut wasi_env = WasiState::new("Zellij")
+ .env("CLICOLOR_FORCE", "1")
+ .preopen(|p| {
+ p.directory(".") // FIXME: Change this to a more meaningful dir
+ .alias(".")
+ .read(true)
+ .write(true)
+ .create(true)
+ })
+ .unwrap()
+ .stdin(Box::new(input))
+ .stdout(Box::new(output))
+ .finalize()
+ .unwrap();
+
+ let wasi = wasi_env.import_object(&module).unwrap();
+
+ let plugin_env = PluginEnv {
+ plugin_id,
+ send_screen_instructions: send_screen_instructions.clone(),
+ send_pty_instructions: send_pty_instructions.clone(),
+ send_plugin_instructions: send_plugin_instructions.clone(),
+ wasi_env,
+ subscriptions: Arc::new(Mutex::new(HashSet::new())),
+ };
+
+ let zellij = zellij_exports(&store, &plugin_env);
+ let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
+
+ let start = instance.exports.get_function("_start").unwrap();
+
+ // This eventually calls the `.init()` method
+ start.call(&[]).unwrap();
+
+ plugin_map.insert(plugin_id, (instance, plugin_env));
+ pid_tx.send(plugin_id).unwrap();
+ plugin_id += 1;
+ }
+ PluginInstruction::Update(pid, event) => {
+ for (&i, (instance, plugin_env)) in &plugin_map {
+ let subs = plugin_env.subscriptions.lock().unwrap();
+ // FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType?
+ let event_type = EventType::from_str(&event.to_string()).unwrap();
+ if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) {
+ let update = instance.exports.get_function("update").unwrap();
+ wasi_write_object(&plugin_env.wasi_env, &event);
+ update.call(&[]).unwrap();
+ }
+ }
+ drop(send_screen_instructions.send(ScreenInstruction::Render));
+ }
+ PluginInstruction::Render(buf_tx, pid, rows, cols) => {
+ let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
+
+ let render = instance.exports.get_function("render").unwrap();
+
+ render
+ .call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
+ .unwrap();
+
+ buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap();
+ }
+ PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
+ PluginInstruction::Exit => break,
+ }
+ }
+ })
+ .unwrap();
+ SessionMetaData {
+ send_plugin_instructions,
+ send_screen_instructions,
+ send_pty_instructions,
+ screen_thread: Some(screen_thread),
+ pty_thread: Some(pty_thread),
+ wasm_thread: Some(wasm_thread),
+ }
+}
+
+fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn ServerOsApi) {
+ match action {
+ Action::Write(val) => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::ClearScroll)
+ .unwrap();
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::WriteCharacter(val))
+ .unwrap();
+ }
+ Action::SwitchToMode(mode) => {
+ let palette = os_input.load_palette();
+ session
+ .send_plugin_instructions
+ .send(PluginInstruction::Update(
+ None,
+ Event::ModeUpdate(get_mode_info(mode, palette)),
+ ))
+ .unwrap();
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::ChangeMode(get_mode_info(mode, palette)))
+ .unwrap();
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::Render)
+ .unwrap();
+ }
+ Action::Resize(direction) => {
+ let screen_instr = match direction {
+ Direction::Left => ScreenInstruction::ResizeLeft,
+ Direction::Right => ScreenInstruction::ResizeRight,
+ Direction::Up => ScreenInstruction::ResizeUp,
+ Direction::Down => ScreenInstruction::ResizeDown,
+ };
+ session.send_screen_instructions.send(screen_instr).unwrap();
+ }
+ Action::SwitchFocus => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::SwitchFocus)
+ .unwrap();
+ }
+ Action::FocusNextPane => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::FocusNextPane)
+ .unwrap();
+ }
+ Action::FocusPreviousPane => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::FocusPreviousPane)
+ .unwrap();
+ }
+ Action::MoveFocus(direction) => {
+ let screen_instr = match direction {
+ Direction::Left => ScreenInstruction::MoveFocusLeft,
+ Direction::Right => ScreenInstruction::MoveFocusRight,
+ Direction::Up => ScreenInstruction::MoveFocusUp,
+ Direction::Down => ScreenInstruction::MoveFocusDown,
+ };
+ session.send_screen_instructions.send(screen_instr).unwrap();
+ }
+ Action::ScrollUp => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::ScrollUp)
+ .unwrap();
+ }
+ Action::ScrollDown => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::ScrollDown)
+ .unwrap();
+ }
+ Action::PageScrollUp => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::PageScrollUp)
+ .unwrap();
+ }
+ Action::PageScrollDown => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::PageScrollDown)
+ .unwrap();
+ }
+ Action::ToggleFocusFullscreen => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::ToggleActiveTerminalFullscreen)
+ .unwrap();
+ }
+ Action::NewPane(direction) => {
+ let pty_instr = match direction {
+ Some(Direction::Left) => PtyInstruction::SpawnTerminalVertically(None),
+ Some(Direction::Right) => PtyInstruction::SpawnTerminalVertically(None),
+ Some(Direction::Up) => PtyInstruction::SpawnTerminalHorizontally(None),
+ Some(Direction::Down) => PtyInstruction::SpawnTerminalHorizontally(None),
+ // No direction specified - try to put it in the biggest available spot
+ None => PtyInstruction::SpawnTerminal(None),
+ };
+ session.send_pty_instructions.send(pty_instr).unwrap();
+ }
+ Action::CloseFocus => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::CloseFocusedPane)
+ .unwrap();
+ }
+ Action::NewTab => {
+ session
+ .send_pty_instructions
+ .send(PtyInstruction::NewTab)
+ .unwrap();
+ }
+ Action::GoToNextTab => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::SwitchTabNext)
+ .unwrap();
+ }
+ Action::GoToPreviousTab => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::SwitchTabPrev)
+ .unwrap();
+ }
+ Action::ToggleActiveSyncPanes => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::ToggleActiveSyncPanes)
+ .unwrap();
+ }
+ Action::CloseTab => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::CloseTab)
+ .unwrap();
+ }
+ Action::GoToTab(i) => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::GoToTab(i))
+ .unwrap();
+ }
+ Action::TabNameInput(c) => {
+ session
+ .send_screen_instructions
+ .send(ScreenInstruction::UpdateTabName(c))
+ .unwrap();
+ }
+ Action::NoOp => {}
+ Action::Quit => panic!("Received unexpected action"),
+ }
+}