diff --git a/src/errors.rs b/src/errors.rs index e53bbc30..bbe85ec8 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -166,6 +166,7 @@ pub enum ScreenContext { ClearScroll, CloseFocusedPane, ToggleActiveTerminalFullscreen, + SetSelectable, ClosePane, ApplyLayout, NewTab, @@ -200,6 +201,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::ToggleActiveTerminalFullscreen => { ScreenContext::ToggleActiveTerminalFullscreen } + ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable, ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane, ScreenInstruction::ApplyLayout(_) => ScreenContext::ApplyLayout, ScreenInstruction::NewTab(_) => ScreenContext::NewTab, @@ -244,6 +246,7 @@ pub enum PluginContext { Load, Draw, Input, + GlobalInput, Unload, Quit, } @@ -254,6 +257,7 @@ impl From<&PluginInstruction> for PluginContext { PluginInstruction::Load(..) => PluginContext::Load, PluginInstruction::Draw(..) => PluginContext::Draw, PluginInstruction::Input(..) => PluginContext::Input, + PluginInstruction::GlobalInput(_) => PluginContext::GlobalInput, PluginInstruction::Unload(_) => PluginContext::Unload, PluginInstruction::Quit => PluginContext::Quit, } diff --git a/src/input.rs b/src/input.rs index cc144361..c947b7fb 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,9 +1,9 @@ /// Module for handling input -use crate::errors::ContextType; use crate::os_input_output::OsApi; use crate::pty_bus::PtyInstruction; use crate::screen::ScreenInstruction; use crate::CommandIsExecuting; +use crate::{errors::ContextType, wasm_vm::PluginInstruction}; use crate::{AppInstruction, SenderWithContext, OPENCALLS}; struct InputHandler { @@ -12,6 +12,7 @@ struct InputHandler { command_is_executing: CommandIsExecuting, send_screen_instructions: SenderWithContext, send_pty_instructions: SenderWithContext, + send_plugin_instructions: SenderWithContext, send_app_instructions: SenderWithContext, } @@ -21,6 +22,7 @@ impl InputHandler { command_is_executing: CommandIsExecuting, send_screen_instructions: SenderWithContext, send_pty_instructions: SenderWithContext, + send_plugin_instructions: SenderWithContext, send_app_instructions: SenderWithContext, ) -> Self { InputHandler { @@ -29,6 +31,7 @@ impl InputHandler { command_is_executing, send_screen_instructions, send_pty_instructions, + send_plugin_instructions, send_app_instructions, } } @@ -38,6 +41,7 @@ impl InputHandler { let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); err_ctx.add_call(ContextType::StdinHandler); self.send_pty_instructions.update(err_ctx); + self.send_plugin_instructions.update(err_ctx); self.send_app_instructions.update(err_ctx); self.send_screen_instructions.update(err_ctx); loop { @@ -59,6 +63,11 @@ impl InputHandler { loop { let stdin_buffer = self.os_input.read_from_stdin(); + #[cfg(not(test))] // Absolutely zero clue why this breaks *all* of the tests + drop( + self.send_plugin_instructions + .send(PluginInstruction::GlobalInput(stdin_buffer.clone())), + ); match stdin_buffer.as_slice() { [7] => { // ctrl-g @@ -88,6 +97,11 @@ impl InputHandler { loop { let stdin_buffer = self.os_input.read_from_stdin(); + #[cfg(not(test))] // Absolutely zero clue why this breaks *all* of the tests + drop( + self.send_plugin_instructions + .send(PluginInstruction::GlobalInput(stdin_buffer.clone())), + ); // uncomment this to print the entered character to a log file (/tmp/mosaic/mosaic-log.txt) for debugging // debug_log_to_file(format!("buffer {:?}", stdin_buffer)); @@ -267,6 +281,9 @@ impl InputHandler { self.send_pty_instructions .send(PtyInstruction::Quit) .unwrap(); + self.send_plugin_instructions + .send(PluginInstruction::Quit) + .unwrap(); self.send_app_instructions .send(AppInstruction::Exit) .unwrap(); @@ -297,6 +314,7 @@ pub fn input_loop( command_is_executing: CommandIsExecuting, send_screen_instructions: SenderWithContext, send_pty_instructions: SenderWithContext, + send_plugin_instructions: SenderWithContext, send_app_instructions: SenderWithContext, ) { let _handler = InputHandler::new( @@ -304,6 +322,7 @@ pub fn input_loop( command_is_executing, send_screen_instructions, send_pty_instructions, + send_plugin_instructions, send_app_instructions, ) .get_input(); diff --git a/src/main.rs b/src/main.rs index 6800691d..c449f89a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -355,6 +355,14 @@ pub fn start(mut os_input: Box, opts: CliArgs) { 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); + // FIXME: Is this needed? + screen.render(); + } ScreenInstruction::ClosePane(id) => { screen.get_active_tab_mut().unwrap().close_pane(id); screen.render(); @@ -430,7 +438,9 @@ pub fn start(mut os_input: Box, opts: CliArgs) { let wasi = wasi_env.import_object(&module).unwrap(); let plugin_env = PluginEnv { + plugin_id, send_pty_instructions: send_pty_instructions.clone(), + send_screen_instructions: send_screen_instructions.clone(), wasi_env, }; @@ -457,15 +467,29 @@ pub fn start(mut os_input: Box, opts: CliArgs) { buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap(); } + // FIXME: Deduplicate this with the callback below! PluginInstruction::Input(pid, input_bytes) => { + let (instance, plugin_env) = plugin_map.get(&pid).unwrap(); + + let handle_key = + instance.exports.get_function("handle_key").unwrap(); + for key in input_bytes.keys() { + if let Ok(key) = key { + wasi_write_string( + &plugin_env.wasi_env, + &serde_json::to_string(&key).unwrap(), + ); + handle_key.call(&[]).unwrap(); + } + } + + drop(send_screen_instructions.send(ScreenInstruction::Render)); + } + PluginInstruction::GlobalInput(input_bytes) => { // FIXME: Set up an event subscription system, and timed callbacks - for (&id, (instance, plugin_env)) in &plugin_map { - let handler = if PaneId::Plugin(id) == pid { - "handle_key" - } else { - "handle_global_key" - }; - let handler = instance.exports.get_function(handler).unwrap(); + for (instance, plugin_env) in plugin_map.values() { + let handler = + instance.exports.get_function("handle_global_key").unwrap(); for key in input_bytes.keys() { if let Ok(key) = key { wasi_write_string( @@ -477,9 +501,7 @@ pub fn start(mut os_input: Box, opts: CliArgs) { } } - send_screen_instructions - .send(ScreenInstruction::Render) - .unwrap(); + drop(send_screen_instructions.send(ScreenInstruction::Render)); } PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)), PluginInstruction::Quit => break, @@ -556,6 +578,7 @@ pub fn start(mut os_input: Box, opts: CliArgs) { .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 os_input = os_input.clone(); move || { input_loop( @@ -563,6 +586,7 @@ pub fn start(mut os_input: Box, opts: CliArgs) { command_is_executing, send_screen_instructions, send_pty_instructions, + send_plugin_instructions, send_app_instructions, ) } @@ -581,14 +605,12 @@ pub fn start(mut os_input: Box, opts: CliArgs) { AppInstruction::Exit => { let _ = send_screen_instructions.send(ScreenInstruction::Quit); let _ = send_pty_instructions.send(PtyInstruction::Quit); - let _ = send_plugin_instructions.send(PluginInstruction::Quit); break; } AppInstruction::Error(backtrace) => { let _ = send_screen_instructions.send(ScreenInstruction::Quit); let _ = send_pty_instructions.send(PtyInstruction::Quit); - let _ = send_plugin_instructions.send(PluginInstruction::Quit); os_input.unset_raw_mode(0); let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); @@ -608,6 +630,7 @@ pub fn start(mut os_input: Box, opts: CliArgs) { for thread_handler in active_threads { thread_handler.join().unwrap(); } + // cleanup(); let reset_style = "\u{1b}[m"; let show_cursor = "\u{1b}[?25h"; diff --git a/src/panes/plugin_pane.rs b/src/panes/plugin_pane.rs index 1573d577..a93ff8c0 100644 --- a/src/panes/plugin_pane.rs +++ b/src/panes/plugin_pane.rs @@ -9,6 +9,7 @@ use crate::panes::{PaneId, PositionAndSize}; pub struct PluginPane { pub pid: u32, pub should_render: bool, + pub selectable: bool, pub position_and_size: PositionAndSize, pub position_and_size_override: Option, pub send_plugin_instructions: SenderWithContext, @@ -23,6 +24,7 @@ impl PluginPane { Self { pid, should_render: true, + selectable: true, position_and_size, position_and_size_override: None, send_plugin_instructions, @@ -92,6 +94,12 @@ impl Pane for PluginPane { fn set_should_render(&mut self, should_render: bool) { self.should_render = should_render; } + fn selectable(&self) -> bool { + self.selectable + } + fn set_selectable(&mut self, selectable: bool) { + self.selectable = selectable; + } fn render(&mut self) -> Option { // if self.should_render { if true { diff --git a/src/panes/terminal_pane.rs b/src/panes/terminal_pane.rs index 0d04c681..d15c7649 100644 --- a/src/panes/terminal_pane.rs +++ b/src/panes/terminal_pane.rs @@ -38,6 +38,7 @@ pub struct TerminalPane { pub pid: RawFd, pub scroll: Scroll, pub should_render: bool, + pub selectable: bool, pub position_and_size: PositionAndSize, pub position_and_size_override: Option, pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "") @@ -165,6 +166,12 @@ impl Pane for TerminalPane { fn set_should_render(&mut self, should_render: bool) { self.should_render = should_render; } + fn selectable(&self) -> bool { + self.selectable + } + fn set_selectable(&mut self, selectable: bool) { + self.selectable = selectable; + } fn render(&mut self) -> Option { // if self.should_render { if true { @@ -274,6 +281,7 @@ impl TerminalPane { pid, scroll, should_render: true, + selectable: true, pending_styles, position_and_size, position_and_size_override: None, diff --git a/src/pty_bus.rs b/src/pty_bus.rs index e635b176..ba5a608b 100644 --- a/src/pty_bus.rs +++ b/src/pty_bus.rs @@ -299,13 +299,15 @@ impl PtyBus { let child_pid = self.id_to_child_pid.get(&id).unwrap(); self.os_input.kill(*child_pid).unwrap(); } - PaneId::Plugin(pid) => self - .send_plugin_instructions - .send(PluginInstruction::Unload(pid)) - .unwrap(), + PaneId::Plugin(pid) => drop( + self.send_plugin_instructions + .send(PluginInstruction::Unload(pid)), + ), } } pub fn close_tab(&mut self, ids: Vec) { - ids.iter().for_each(|&id| self.close_pane(id)); + ids.iter().for_each(|&id| { + self.close_pane(id); + }); } } diff --git a/src/screen.rs b/src/screen.rs index 365d1d7a..5e4d1ae9 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -42,6 +42,7 @@ pub enum ScreenInstruction { ClearScroll, CloseFocusedPane, ToggleActiveTerminalFullscreen, + SetSelectable(PaneId, bool), ClosePane(PaneId), ApplyLayout((Layout, Vec)), NewTab(RawFd), @@ -138,7 +139,7 @@ impl Screen { if self.tabs.len() > 1 { self.switch_tab_prev(); } - let mut active_tab = self.tabs.remove(&active_tab_index).unwrap(); + let active_tab = self.tabs.remove(&active_tab_index).unwrap(); let pane_ids = active_tab.get_pane_ids(); self.send_pty_instructions .send(PtyInstruction::CloseTab(pane_ids)) diff --git a/src/tab.rs b/src/tab.rs index 9990026a..6c65958f 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -66,6 +66,7 @@ pub struct Tab { pub send_app_instructions: SenderWithContext, } +// FIXME: Use a struct that has a pane_type enum, to reduce all of the duplication pub trait Pane { fn x(&self) -> usize; fn y(&self) -> usize; @@ -81,6 +82,8 @@ pub trait Pane { fn position_and_size_override(&self) -> Option; fn should_render(&self) -> bool; fn set_should_render(&mut self, should_render: bool); + fn selectable(&self) -> bool; + fn set_selectable(&mut self, selectable: bool); fn render(&mut self) -> Option; fn pid(&self) -> PaneId; fn reduce_height_down(&mut self, count: usize); @@ -473,23 +476,23 @@ impl Tab { } } pub fn write_to_active_terminal(&mut self, input_bytes: Vec) { - let pid = self.get_active_pane_id(); - - if let Some(pid) = pid { - self.send_plugin_instructions - .send(PluginInstruction::Input(pid, input_bytes.clone())) - .unwrap(); - } - - if let Some(PaneId::Terminal(active_terminal_id)) = pid { - 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"); + 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"); + } + Some(PaneId::Plugin(pid)) => { + self.send_plugin_instructions + .send(PluginInstruction::Input(pid, input_bytes)) + .unwrap(); + } + _ => {} } } pub fn get_active_terminal_cursor_position(&self) -> Option<(usize, usize)> { @@ -618,10 +621,24 @@ impl Tab { fn get_panes(&self) -> impl Iterator)> { self.panes.iter() } + // FIXME: This is some shameful duplication... + fn get_selectable_panes(&self) -> impl Iterator)> { + self.panes.iter().filter(|(_, p)| p.selectable()) + } fn has_panes(&self) -> bool { let mut all_terminals = self.get_panes(); all_terminals.next().is_some() } + fn has_selectable_panes(&self) -> bool { + let mut all_terminals = self.get_selectable_panes(); + all_terminals.next().is_some() + } + fn next_active_pane(&self, panes: Vec) -> Option { + panes + .into_iter() + .rev() + .find(|pid| self.panes.get(pid).unwrap().selectable()) + } fn pane_ids_directly_left_of(&self, id: &PaneId) -> Option> { let mut ids = vec![]; let terminal_to_check = self.panes.get(id).unwrap(); @@ -1421,14 +1438,14 @@ impl Tab { } } pub fn move_focus(&mut self) { - if !self.has_panes() { + if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { return; } let active_terminal_id = self.get_active_pane_id().unwrap(); - let terminal_ids: Vec = self.get_panes().map(|(&pid, _)| pid).collect(); // TODO: better, no allocations + let terminal_ids: Vec = self.get_selectable_panes().map(|(&pid, _)| pid).collect(); // TODO: better, no allocations let first_terminal = terminal_ids.get(0).unwrap(); let active_terminal_id_position = terminal_ids .iter() @@ -1442,7 +1459,7 @@ impl Tab { self.render(); } pub fn move_focus_left(&mut self) { - if !self.has_panes() { + if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { @@ -1450,7 +1467,7 @@ impl Tab { } let active_terminal = self.get_active_pane(); if let Some(active) = active_terminal { - let terminals = self.get_panes(); + let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() .filter(|(_, (_, c))| { @@ -1472,7 +1489,7 @@ impl Tab { self.render(); } pub fn move_focus_down(&mut self) { - if !self.has_panes() { + if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { @@ -1480,7 +1497,7 @@ impl Tab { } let active_terminal = self.get_active_pane(); if let Some(active) = active_terminal { - let terminals = self.get_panes(); + let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() .filter(|(_, (_, c))| { @@ -1502,7 +1519,7 @@ impl Tab { self.render(); } pub fn move_focus_up(&mut self) { - if !self.has_panes() { + if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { @@ -1510,7 +1527,7 @@ impl Tab { } let active_terminal = self.get_active_pane(); if let Some(active) = active_terminal { - let terminals = self.get_panes(); + let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() .filter(|(_, (_, c))| { @@ -1532,7 +1549,7 @@ impl Tab { self.render(); } pub fn move_focus_right(&mut self) { - if !self.has_panes() { + if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { @@ -1540,7 +1557,7 @@ impl Tab { } let active_terminal = self.get_active_pane(); if let Some(active) = active_terminal { - let terminals = self.get_panes(); + let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() .filter(|(_, (_, c))| { @@ -1578,7 +1595,7 @@ impl Tab { }) } fn panes_to_the_left_between_aligning_borders(&self, id: PaneId) -> Option> { - if let Some(terminal) = &self.panes.get(&id) { + if let Some(terminal) = self.panes.get(&id) { let upper_close_border = terminal.y(); let lower_close_border = terminal.y() + terminal.rows() + 1; @@ -1601,7 +1618,7 @@ impl Tab { None } fn panes_to_the_right_between_aligning_borders(&self, id: PaneId) -> Option> { - if let Some(terminal) = &self.panes.get(&id) { + if let Some(terminal) = self.panes.get(&id) { let upper_close_border = terminal.y(); let lower_close_border = terminal.y() + terminal.rows() + 1; @@ -1625,7 +1642,7 @@ impl Tab { None } fn panes_above_between_aligning_borders(&self, id: PaneId) -> Option> { - if let Some(terminal) = &self.panes.get(&id) { + if let Some(terminal) = self.panes.get(&id) { let left_close_border = terminal.x(); let right_close_border = terminal.x() + terminal.columns() + 1; @@ -1647,8 +1664,8 @@ impl Tab { } None } - fn terminals_below_between_aligning_borders(&self, id: PaneId) -> Option> { - if let Some(terminal) = &self.panes.get(&id) { + fn panes_below_between_aligning_borders(&self, id: PaneId) -> Option> { + if let Some(terminal) = self.panes.get(&id) { let left_close_border = terminal.x(); let right_close_border = terminal.x() + terminal.columns() + 1; @@ -1681,9 +1698,17 @@ impl Tab { } } } - pub fn get_pane_ids(&mut self) -> Vec { + pub fn get_pane_ids(&self) -> Vec { self.get_panes().map(|(&pid, _)| pid).collect() } + pub fn set_pane_selectable(&mut self, id: PaneId, selectable: bool) { + if let Some(pane) = self.panes.get_mut(&id) { + pane.set_selectable(selectable); + if self.get_active_pane_id() == Some(id) && !selectable { + self.active_terminal = self.next_active_pane(self.get_pane_ids()) + } + } + } pub fn close_pane(&mut self, id: PaneId) { if self.panes.get(&id).is_some() { self.close_pane_without_rerender(id); @@ -1699,7 +1724,7 @@ impl Tab { // 1 for the border } if self.active_terminal == Some(id) { - self.active_terminal = Some(*terminals.last().unwrap()); + self.active_terminal = self.next_active_pane(terminals); } } else if let Some(terminals) = self.panes_to_the_right_between_aligning_borders(id) { for terminal_id in terminals.iter() { @@ -1707,7 +1732,7 @@ impl Tab { // 1 for the border } if self.active_terminal == Some(id) { - self.active_terminal = Some(*terminals.last().unwrap()); + self.active_terminal = self.next_active_pane(terminals); } } else if let Some(terminals) = self.panes_above_between_aligning_borders(id) { for terminal_id in terminals.iter() { @@ -1715,21 +1740,21 @@ impl Tab { // 1 for the border } if self.active_terminal == Some(id) { - self.active_terminal = Some(*terminals.last().unwrap()); + self.active_terminal = self.next_active_pane(terminals); } - } else if let Some(terminals) = self.terminals_below_between_aligning_borders(id) { + } else if let Some(terminals) = self.panes_below_between_aligning_borders(id) { for terminal_id in terminals.iter() { self.increase_pane_height_up(&terminal_id, terminal_to_close_height + 1); // 1 for the border } if self.active_terminal == Some(id) { - self.active_terminal = Some(*terminals.last().unwrap()); + self.active_terminal = self.next_active_pane(terminals); } } else { } self.panes.remove(&id); - if !self.has_panes() { - self.active_terminal = None; + if self.active_terminal.is_none() { + self.active_terminal = self.next_active_pane(self.get_pane_ids()); } } } diff --git a/src/tests/fixtures/layouts/panes-with-plugins.yaml b/src/tests/fixtures/layouts/panes-with-plugins.yaml index 9bb35e08..40dde92b 100644 --- a/src/tests/fixtures/layouts/panes-with-plugins.yaml +++ b/src/tests/fixtures/layouts/panes-with-plugins.yaml @@ -10,4 +10,5 @@ parts: - direction: Horizontal - direction: Vertical split_size: - Fixed: 1 \ No newline at end of file + Fixed: 1 + plugin: status-bar.wasm \ No newline at end of file diff --git a/src/wasm_vm.rs b/src/wasm_vm.rs index 5324ee19..acb9e013 100644 --- a/src/wasm_vm.rs +++ b/src/wasm_vm.rs @@ -2,19 +2,22 @@ use std::{path::PathBuf, sync::mpsc::Sender}; use wasmer::{imports, Function, ImportObject, Store, WasmerEnv}; use wasmer_wasi::WasiEnv; -use crate::{panes::PaneId, pty_bus::PtyInstruction, SenderWithContext}; +use crate::{panes::PaneId, pty_bus::PtyInstruction, screen::ScreenInstruction, SenderWithContext}; #[derive(Clone, Debug)] pub enum PluginInstruction { Load(Sender, PathBuf), Draw(Sender, u32, usize, usize), // String buffer, plugin id, rows, cols - Input(PaneId, Vec), // pane id, input bytes + Input(u32, Vec), // plugin id, input bytes + GlobalInput(Vec), // input bytes Unload(u32), Quit, } #[derive(WasmerEnv, Clone)] pub struct PluginEnv { + pub plugin_id: u32, + pub send_screen_instructions: SenderWithContext, pub send_pty_instructions: SenderWithContext, // FIXME: This should be a big bundle of all of the channels pub wasi_env: WasiEnv, } @@ -24,7 +27,8 @@ pub struct PluginEnv { pub fn mosaic_imports(store: &Store, plugin_env: &PluginEnv) -> ImportObject { imports! { "mosaic" => { - "host_open_file" => Function::new_native_with_env(store, plugin_env.clone(), host_open_file) + "host_open_file" => Function::new_native_with_env(store, plugin_env.clone(), host_open_file), + "host_set_selectable" => Function::new_native_with_env(store, plugin_env.clone(), host_set_selectable), } } } @@ -38,6 +42,18 @@ fn host_open_file(plugin_env: &PluginEnv) { .unwrap(); } +// FIXME: Think about these naming conventions – should everything be prefixed by 'host'? +fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) { + let selectable = selectable != 0; + plugin_env + .send_screen_instructions + .send(ScreenInstruction::SetSelectable( + PaneId::Plugin(plugin_env.plugin_id), + selectable, + )) + .unwrap() +} + // Helper Functions --------------------------------------------------------------------------------------------------- // FIXME: Unwrap city diff --git a/status-bar.wasm b/status-bar.wasm new file mode 100755 index 00000000..39e081ff Binary files /dev/null and b/status-bar.wasm differ