Added the set_selectable plugin API function

This commit is contained in:
Brooks J Rady 2021-01-11 07:33:35 +00:00
parent 7e308515e5
commit efcd36a52c
11 changed files with 170 additions and 63 deletions

View file

@ -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,
}

View file

@ -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<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
}
@ -21,6 +22,7 @@ impl InputHandler {
command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
) -> 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<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
) {
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();

View file

@ -355,6 +355,14 @@ pub fn start(mut os_input: Box<dyn OsApi>, 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<dyn OsApi>, 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<dyn OsApi>, 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<dyn OsApi>, 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<dyn OsApi>, 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<dyn OsApi>, 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<dyn OsApi>, 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<dyn OsApi>, 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";

View file

@ -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<PositionAndSize>,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
@ -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<String> {
// if self.should_render {
if true {

View file

@ -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<PositionAndSize>,
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<String> {
// 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,

View file

@ -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<PaneId>) {
ids.iter().for_each(|&id| self.close_pane(id));
ids.iter().for_each(|&id| {
self.close_pane(id);
});
}
}

View file

@ -42,6 +42,7 @@ pub enum ScreenInstruction {
ClearScroll,
CloseFocusedPane,
ToggleActiveTerminalFullscreen,
SetSelectable(PaneId, bool),
ClosePane(PaneId),
ApplyLayout((Layout, Vec<RawFd>)),
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))

View file

@ -66,6 +66,7 @@ pub struct Tab {
pub send_app_instructions: SenderWithContext<AppInstruction>,
}
// 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<PositionAndSize>;
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<String>;
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<u8>) {
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<Item = (&PaneId, &Box<dyn Pane>)> {
self.panes.iter()
}
// FIXME: This is some shameful duplication...
fn get_selectable_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
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<PaneId>) -> Option<PaneId> {
panes
.into_iter()
.rev()
.find(|pid| self.panes.get(pid).unwrap().selectable())
}
fn pane_ids_directly_left_of(&self, id: &PaneId) -> Option<Vec<PaneId>> {
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<PaneId> = self.get_panes().map(|(&pid, _)| pid).collect(); // TODO: better, no allocations
let terminal_ids: Vec<PaneId> = 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<Vec<PaneId>> {
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<Vec<PaneId>> {
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<Vec<PaneId>> {
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<Vec<PaneId>> {
if let Some(terminal) = &self.panes.get(&id) {
fn panes_below_between_aligning_borders(&self, id: PaneId) -> Option<Vec<PaneId>> {
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<PaneId> {
pub fn get_pane_ids(&self) -> Vec<PaneId> {
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());
}
}
}

View file

@ -10,4 +10,5 @@ parts:
- direction: Horizontal
- direction: Vertical
split_size:
Fixed: 1
Fixed: 1
plugin: status-bar.wasm

View file

@ -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<u32>, PathBuf),
Draw(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols
Input(PaneId, Vec<u8>), // pane id, input bytes
Input(u32, Vec<u8>), // plugin id, input bytes
GlobalInput(Vec<u8>), // input bytes
Unload(u32),
Quit,
}
#[derive(WasmerEnv, Clone)]
pub struct PluginEnv {
pub plugin_id: u32,
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
pub send_pty_instructions: SenderWithContext<PtyInstruction>, // 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

BIN
status-bar.wasm Executable file

Binary file not shown.