fix(plugins): support multiple users (#930)

* fix(plugins): support multiple clients

* fix(style): make clippy happy
This commit is contained in:
Aram Drevekenin 2021-12-08 18:41:41 +01:00 committed by GitHub
parent 56e85f87d6
commit 92bddf1b79
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 305 additions and 91 deletions

View file

@ -26,4 +26,4 @@ expression: first_runner_snapshot
│ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SCROLL  <o> SESSION  <q> QUIT 
<←↓↑→> Move focus / <n> New / <x> Close / <r> Rename / <s> Sync / <Tab> Toggle / <ENTER> Select pane
Tip: Alt + <n> => new pane. Alt + <[] or hjkl> => navigate. Alt + <+-> => resize pane.

View file

@ -318,6 +318,14 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
} else {
spawn_tabs(None);
}
session_data
.read()
.unwrap()
.as_ref()
.unwrap()
.senders
.send_to_plugin(PluginInstruction::AddClient(client_id))
.unwrap();
}
ServerInstruction::AttachClient(attrs, options, client_id) => {
let rlock = session_data.read().unwrap();
@ -339,6 +347,10 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.senders
.send_to_screen(ScreenInstruction::AddClient(client_id))
.unwrap();
session_data
.senders
.send_to_plugin(PluginInstruction::AddClient(client_id))
.unwrap();
let default_mode = options.default_mode.unwrap_or_default();
let mode_info =
get_mode_info(default_mode, attrs.palette, session_data.capabilities);
@ -351,12 +363,11 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.senders
.send_to_plugin(PluginInstruction::Update(
None,
Some(client_id),
Event::ModeUpdate(mode_info),
))
.unwrap();
for client_id in session_state.read().unwrap().clients.keys() {
os_input.send_to_client(*client_id, ServerToClientMsg::SwitchToMode(mode));
}
os_input.send_to_client(client_id, ServerToClientMsg::SwitchToMode(mode));
}
ServerInstruction::UnblockInputThread => {
for client_id in session_state.read().unwrap().clients.keys() {
@ -385,6 +396,14 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.senders
.send_to_screen(ScreenInstruction::RemoveClient(client_id))
.unwrap();
session_data
.write()
.unwrap()
.as_ref()
.unwrap()
.senders
.send_to_plugin(PluginInstruction::RemoveClient(client_id))
.unwrap();
}
if session_state.read().unwrap().clients.is_empty() {
*session_data.write().unwrap() = None;
@ -412,6 +431,14 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.senders
.send_to_screen(ScreenInstruction::RemoveClient(client_id))
.unwrap();
session_data
.write()
.unwrap()
.as_ref()
.unwrap()
.senders
.send_to_plugin(PluginInstruction::RemoveClient(client_id))
.unwrap();
}
}
ServerInstruction::KillSession => {
@ -444,6 +471,14 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.senders
.send_to_screen(ScreenInstruction::RemoveClient(client_id))
.unwrap();
session_data
.write()
.unwrap()
.as_ref()
.unwrap()
.senders
.send_to_plugin(PluginInstruction::RemoveClient(client_id))
.unwrap();
}
}
ServerInstruction::Render(mut output) => {

View file

@ -131,7 +131,10 @@ impl Pane for PluginPane {
fn set_selectable(&mut self, selectable: bool) {
self.selectable = selectable;
}
fn render(&mut self) -> Option<String> {
fn render(&mut self, client_id: Option<ClientId>) -> Option<String> {
// this is a bit of a hack but works in a pinch
client_id?;
let client_id = client_id.unwrap();
// if self.should_render {
if true {
// while checking should_render rather than rendering each pane every time
@ -145,6 +148,7 @@ impl Pane for PluginPane {
.send(PluginInstruction::Render(
buf_tx,
self.pid,
client_id,
self.get_content_rows(),
self.get_content_columns(),
))
@ -274,6 +278,7 @@ impl Pane for PluginPane {
self.send_plugin_instructions
.send(PluginInstruction::Update(
Some(self.pid),
None,
Event::Mouse(Mouse::ScrollUp(count)),
))
.unwrap();
@ -282,6 +287,7 @@ impl Pane for PluginPane {
self.send_plugin_instructions
.send(PluginInstruction::Update(
Some(self.pid),
None,
Event::Mouse(Mouse::ScrollDown(count)),
))
.unwrap();
@ -293,6 +299,7 @@ impl Pane for PluginPane {
self.send_plugin_instructions
.send(PluginInstruction::Update(
Some(self.pid),
None,
Event::Mouse(Mouse::LeftClick(start.line.0, start.column.0)),
))
.unwrap();
@ -301,6 +308,7 @@ impl Pane for PluginPane {
self.send_plugin_instructions
.send(PluginInstruction::Update(
Some(self.pid),
None,
Event::Mouse(Mouse::Hold(position.line.0, position.column.0)),
))
.unwrap();
@ -309,6 +317,7 @@ impl Pane for PluginPane {
self.send_plugin_instructions
.send(PluginInstruction::Update(
Some(self.pid),
None,
Event::Mouse(Mouse::Release(
end.map(|Position { line, column }| (line.0, column.0)),
)),
@ -342,6 +351,7 @@ impl Pane for PluginPane {
self.send_plugin_instructions
.send(PluginInstruction::Update(
Some(self.pid),
None,
Event::Mouse(Mouse::RightClick(to.line.0, to.column.0)),
))
.unwrap();

View file

@ -183,7 +183,8 @@ impl Pane for TerminalPane {
fn set_selectable(&mut self, selectable: bool) {
self.selectable = selectable;
}
fn render(&mut self) -> Option<String> {
fn render(&mut self, _client_id: Option<ClientId>) -> Option<String> {
// we don't use client_id because terminal panes render the same for all users
if self.should_render() {
let mut vte_output = String::new();
let mut character_styles = CharacterStyles::new();

View file

@ -31,7 +31,11 @@ fn route_action(
let mut should_break = false;
session
.senders
.send_to_plugin(PluginInstruction::Update(None, Event::InputReceived))
.send_to_plugin(PluginInstruction::Update(
None,
Some(client_id),
Event::InputReceived,
))
.unwrap();
match action {
Action::ToggleTab => {
@ -70,6 +74,7 @@ fn route_action(
.senders
.send_to_plugin(PluginInstruction::Update(
None,
Some(client_id),
Event::ModeUpdate(get_mode_info(mode, palette, session.capabilities)),
))
.unwrap();
@ -379,12 +384,8 @@ pub(crate) fn route_thread_main(
ClientToServerMsg::Action(action) => {
if let Some(rlocked_sessions) = rlocked_sessions.as_ref() {
if let Action::SwitchToMode(input_mode) = action {
for client_id in session_state.read().unwrap().clients.keys() {
os_input.send_to_client(
*client_id,
ServerToClientMsg::SwitchToMode(input_mode),
);
}
os_input
.send_to_client(client_id, ServerToClientMsg::SwitchToMode(input_mode));
}
if route_action(action, rlocked_sessions, &*os_input, &to_server, client_id) {
break;

View file

@ -183,7 +183,8 @@ pub(crate) struct Screen {
/// The indices of this [`Screen`]'s active [`Tab`]s.
active_tab_indices: BTreeMap<ClientId, usize>,
tab_history: BTreeMap<ClientId, Vec<usize>>,
mode_info: ModeInfo,
mode_info: BTreeMap<ClientId, ModeInfo>,
default_mode_info: ModeInfo, // TODO: restructure ModeInfo to prevent this duplication
colors: Palette,
draw_pane_frames: bool,
}
@ -206,7 +207,8 @@ impl Screen {
tabs: BTreeMap::new(),
overlay: OverlayWindow::default(),
tab_history: BTreeMap::new(),
mode_info,
mode_info: BTreeMap::new(),
default_mode_info: mode_info,
draw_pane_frames,
}
}
@ -241,12 +243,15 @@ impl Screen {
}
}
fn move_clients(&mut self, source_index: usize, destination_index: usize) {
let connected_clients_in_source_tab = {
let (connected_clients_in_source_tab, client_mode_infos_in_source_tab) = {
let source_tab = self.tabs.get_mut(&source_index).unwrap();
source_tab.drain_connected_clients()
};
let destination_tab = self.tabs.get_mut(&destination_index).unwrap();
destination_tab.add_multiple_clients(&connected_clients_in_source_tab);
destination_tab.add_multiple_clients(
connected_clients_in_source_tab,
client_mode_infos_in_source_tab,
);
}
/// A helper function to switch to a new tab at specified position.
fn switch_active_tab(&mut self, new_tab_pos: usize, client_id: ClientId) {
@ -423,6 +428,11 @@ impl Screen {
pub fn new_tab(&mut self, layout: Layout, new_pids: Vec<RawFd>, client_id: ClientId) {
let tab_index = self.get_new_tab_index();
let position = self.tabs.len();
let client_mode_info = self
.mode_info
.get(&client_id)
.unwrap_or(&self.default_mode_info)
.clone();
let mut tab = Tab::new(
tab_index,
position,
@ -431,16 +441,20 @@ impl Screen {
self.bus.os_input.as_ref().unwrap().clone(),
self.bus.senders.clone(),
self.max_panes,
self.mode_info.clone(),
client_mode_info,
self.colors,
self.draw_pane_frames,
client_id,
);
tab.apply_layout(layout, new_pids, tab_index);
tab.apply_layout(layout, new_pids, tab_index, client_id);
if let Some(active_tab) = self.get_active_tab_mut(client_id) {
active_tab.visible(false);
let connected_clients = active_tab.drain_connected_clients();
tab.add_multiple_clients(&connected_clients);
let (connected_clients_in_source_tab, client_mode_infos_in_source_tab) =
active_tab.drain_connected_clients();
tab.add_multiple_clients(
connected_clients_in_source_tab,
client_mode_infos_in_source_tab,
);
}
for (client_id, tab_history) in &mut self.tab_history {
let old_active_index = self.active_tab_indices.remove(client_id).unwrap();
@ -448,6 +462,7 @@ impl Screen {
tab_history.retain(|&e| e != tab_index);
tab_history.push(old_active_index);
}
tab.update_input_modes();
tab.visible(true);
self.tabs.insert(tab_index, tab);
if !self.active_tab_indices.contains_key(&client_id) {
@ -503,7 +518,11 @@ impl Screen {
}
self.bus
.senders
.send_to_plugin(PluginInstruction::Update(None, Event::TabUpdate(tab_data)))
.send_to_plugin(PluginInstruction::Update(
None,
None,
Event::TabUpdate(tab_data),
))
.unwrap();
}
}
@ -526,7 +545,12 @@ impl Screen {
self.update_tabs();
}
pub fn change_mode(&mut self, mode_info: ModeInfo, client_id: ClientId) {
if self.mode_info.mode == InputMode::Scroll
let previous_mode = self
.mode_info
.get(&client_id)
.unwrap_or(&self.default_mode_info)
.mode;
if previous_mode == InputMode::Scroll
&& (mode_info.mode == InputMode::Normal || mode_info.mode == InputMode::Locked)
{
self.get_active_tab_mut(client_id)
@ -534,9 +558,9 @@ impl Screen {
.clear_active_terminal_scroll(client_id);
}
self.colors = mode_info.palette;
self.mode_info = mode_info;
self.mode_info.insert(client_id, mode_info.clone());
for tab in self.tabs.values_mut() {
tab.mode_info = self.mode_info.clone();
tab.change_mode_info(mode_info.clone(), client_id);
tab.mark_active_pane_for_rerender(client_id);
}
}
@ -1100,6 +1124,7 @@ pub(crate) fn screen_thread_main(
}
ScreenInstruction::AddClient(client_id) => {
screen.add_client(client_id);
screen.update_tabs();
screen.render();
}

View file

@ -137,7 +137,8 @@ pub(crate) struct Tab {
pub senders: ThreadSenders,
synchronize_is_active: bool,
should_clear_display_before_rendering: bool,
pub mode_info: ModeInfo,
mode_info: HashMap<ClientId, ModeInfo>,
default_mode_info: ModeInfo,
pub colors: Palette,
connected_clients: HashSet<ClientId>,
draw_pane_frames: bool,
@ -179,7 +180,7 @@ pub trait Pane {
fn set_should_render_boundaries(&mut self, _should_render: bool) {}
fn selectable(&self) -> bool;
fn set_selectable(&mut self, selectable: bool);
fn render(&mut self) -> Option<String>;
fn render(&mut self, client_id: Option<ClientId>) -> Option<String>;
fn render_frame(&mut self, client_id: ClientId, frame_params: FrameParams) -> Option<String>;
fn render_fake_cursor(
&mut self,
@ -342,7 +343,8 @@ impl Tab {
os_api,
senders,
should_clear_display_before_rendering: false,
mode_info,
mode_info: HashMap::new(),
default_mode_info: mode_info,
colors,
draw_pane_frames,
// at the moment this is hard-coded while the feature is being developed
@ -353,7 +355,13 @@ impl Tab {
}
}
pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec<RawFd>, tab_index: usize) {
pub fn apply_layout(
&mut self,
layout: Layout,
new_pids: Vec<RawFd>,
tab_index: usize,
client_id: ClientId,
) {
// TODO: this should be an attribute on Screen instead of full_screen_ws
let free_space = PaneGeom::default();
self.panes_to_hide.clear();
@ -384,7 +392,7 @@ impl Tab {
let (pid_tx, pid_rx) = channel();
let pane_title = run.location.to_string();
self.senders
.send_to_plugin(PluginInstruction::Load(pid_tx, run, tab_index))
.send_to_plugin(PluginInstruction::Load(pid_tx, run, tab_index, client_id))
.unwrap();
let pid = pid_rx.recv().unwrap();
let mut new_plugin = PluginPane::new(
@ -395,13 +403,6 @@ impl Tab {
);
new_plugin.set_borderless(layout.borderless);
self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin));
// Send an initial mode update to the newly loaded plugin only!
self.senders
.send_to_plugin(PluginInstruction::Update(
Some(pid),
Event::ModeUpdate(self.mode_info.clone()),
))
.unwrap();
} else {
// there are still panes left to fill, use the pids we received in this method
let pid = new_pids.next().unwrap(); // if this crashes it means we got less pids than there are panes in this layout
@ -465,12 +466,30 @@ impl Tab {
}
}
}
pub fn update_input_modes(&mut self) {
// this updates all plugins with the client's input mode
for client_id in &self.connected_clients {
let mode_info = self
.mode_info
.get(client_id)
.unwrap_or(&self.default_mode_info);
self.senders
.send_to_plugin(PluginInstruction::Update(
None,
Some(*client_id),
Event::ModeUpdate(mode_info.clone()),
))
.unwrap();
}
}
pub fn add_client(&mut self, client_id: ClientId) {
match self.connected_clients.iter().next() {
Some(first_client_id) => {
let first_active_pane_id = *self.active_panes.get(first_client_id).unwrap();
self.connected_clients.insert(client_id);
self.active_panes.insert(client_id, first_active_pane_id);
self.mode_info
.insert(client_id, self.default_mode_info.clone());
}
None => {
let mut pane_ids: Vec<PaneId> = self.panes.keys().copied().collect();
@ -482,15 +501,32 @@ impl Tab {
let first_pane_id = pane_ids.get(0).unwrap();
self.connected_clients.insert(client_id);
self.active_panes.insert(client_id, *first_pane_id);
self.mode_info
.insert(client_id, self.default_mode_info.clone());
}
}
// TODO: we might be able to avoid this, we do this so that newly connected clients will
// necessarily get a full render
self.set_force_render();
}
pub fn add_multiple_clients(&mut self, client_ids: &[ClientId]) {
pub fn change_mode_info(&mut self, mode_info: ModeInfo, client_id: ClientId) {
self.mode_info.insert(client_id, mode_info);
}
pub fn add_multiple_clients(
&mut self,
client_ids: Vec<ClientId>,
client_mode_infos: Vec<(ClientId, ModeInfo)>,
) {
for client_id in client_ids {
self.add_client(*client_id);
self.add_client(client_id);
}
for (client_id, client_mode_info) in client_mode_infos {
log::info!(
"add_multiple_clients: client_id: {:?}, mode: {:?}",
client_id,
client_mode_info.mode
);
self.mode_info.insert(client_id, client_mode_info);
}
}
pub fn remove_client(&mut self, client_id: ClientId) {
@ -498,8 +534,12 @@ impl Tab {
self.active_panes.remove(&client_id);
self.set_force_render();
}
pub fn drain_connected_clients(&mut self) -> Vec<ClientId> {
self.connected_clients.drain().collect()
pub fn drain_connected_clients(&mut self) -> (Vec<ClientId>, Vec<(ClientId, ModeInfo)>) {
let client_mode_info = self.mode_info.drain();
(
self.connected_clients.drain().collect(),
client_mode_info.collect(),
)
}
pub fn new_pane(&mut self, pid: PaneId, client_id: Option<ClientId>) {
self.close_down_to_max_terminals();
@ -750,7 +790,7 @@ impl Tab {
PaneId::Plugin(pid) => {
for key in parse_keys(&input_bytes) {
self.senders
.send_to_plugin(PluginInstruction::Update(Some(pid), Event::Key(key)))
.send_to_plugin(PluginInstruction::Update(Some(pid), None, Event::Key(key)))
.unwrap()
}
}
@ -926,25 +966,35 @@ impl Tab {
let mut client_id_to_boundaries: HashMap<ClientId, Boundaries> = HashMap::new();
self.hide_cursor_and_clear_display_as_needed(output);
// render panes and their frames
for pane in self.panes.values_mut() {
for (kind, pane) in self.panes.iter_mut() {
if !self.panes_to_hide.contains(&pane.pid()) {
let mut pane_contents_and_ui = PaneContentsAndUi::new(
pane,
output,
self.colors,
&self.active_panes,
self.mode_info.mode,
);
pane_contents_and_ui.render_pane_contents_for_all_clients();
let mut pane_contents_and_ui =
PaneContentsAndUi::new(pane, output, self.colors, &self.active_panes);
if let PaneId::Terminal(..) = kind {
pane_contents_and_ui.render_pane_contents_for_all_clients();
}
for &client_id in &self.connected_clients {
let client_mode = self
.mode_info
.get(&client_id)
.unwrap_or(&self.default_mode_info)
.mode;
if let PaneId::Plugin(..) = kind {
pane_contents_and_ui.render_pane_contents_for_client(client_id);
}
if self.draw_pane_frames {
pane_contents_and_ui.render_pane_frame(client_id, self.session_is_mirrored);
pane_contents_and_ui.render_pane_frame(
client_id,
client_mode,
self.session_is_mirrored,
);
} else {
let boundaries = client_id_to_boundaries
.entry(client_id)
.or_insert_with(|| Boundaries::new(self.viewport));
pane_contents_and_ui.render_pane_boundaries(
client_id,
client_mode,
boundaries,
self.session_is_mirrored,
);
@ -3325,7 +3375,11 @@ impl Tab {
if let Some(selected_text) = selected_text {
self.write_selection_to_clipboard(&selected_text);
self.senders
.send_to_plugin(PluginInstruction::Update(None, Event::CopyToClipboard))
.send_to_plugin(PluginInstruction::Update(
None,
None,
Event::CopyToClipboard,
))
.unwrap();
}
}
@ -3343,7 +3397,11 @@ impl Tab {
.send_to_server(ServerInstruction::Render(Some(output)))
.unwrap();
self.senders
.send_to_plugin(PluginInstruction::Update(None, Event::CopyToClipboard))
.send_to_plugin(PluginInstruction::Update(
None,
None,
Event::CopyToClipboard,
))
.unwrap();
}
fn is_inside_viewport(&self, pane_id: &PaneId) -> bool {
@ -3387,6 +3445,7 @@ impl Tab {
self.senders
.send_to_plugin(PluginInstruction::Update(
Some(*pid),
None,
Event::Visible(visible),
))
.unwrap();

View file

@ -13,7 +13,6 @@ pub struct PaneContentsAndUi<'a> {
colors: Palette,
focused_clients: Vec<ClientId>,
multiple_users_exist_in_session: bool,
mode: InputMode, // TODO: per client
}
impl<'a> PaneContentsAndUi<'a> {
@ -22,7 +21,6 @@ impl<'a> PaneContentsAndUi<'a> {
output: &'a mut Output,
colors: Palette,
active_panes: &HashMap<ClientId, PaneId>,
mode: InputMode,
) -> Self {
let focused_clients: Vec<ClientId> = active_panes
.iter()
@ -36,11 +34,10 @@ impl<'a> PaneContentsAndUi<'a> {
colors,
focused_clients,
multiple_users_exist_in_session,
mode,
}
}
pub fn render_pane_contents_for_all_clients(&mut self) {
if let Some(vte_output) = self.pane.render() {
if let Some(vte_output) = self.pane.render(None) {
// FIXME: Use Termion for cursor and style clearing?
self.output.push_str_to_all_clients(&format!(
"\u{1b}[{};{}H\u{1b}[m{}",
@ -50,6 +47,20 @@ impl<'a> PaneContentsAndUi<'a> {
));
}
}
pub fn render_pane_contents_for_client(&mut self, client_id: ClientId) {
if let Some(vte_output) = self.pane.render(Some(client_id)) {
// FIXME: Use Termion for cursor and style clearing?
self.output.push_to_client(
client_id,
&format!(
"\u{1b}[{};{}H\u{1b}[m{}",
self.pane.y() + 1,
self.pane.x() + 1,
vte_output
),
);
}
}
pub fn render_fake_cursor_if_needed(&mut self, client_id: ClientId) {
let pane_focused_for_client_id = self.focused_clients.contains(&client_id);
let pane_focused_for_different_client = self
@ -79,7 +90,12 @@ impl<'a> PaneContentsAndUi<'a> {
}
}
}
pub fn render_pane_frame(&mut self, client_id: ClientId, session_is_mirrored: bool) {
pub fn render_pane_frame(
&mut self,
client_id: ClientId,
client_mode: InputMode,
session_is_mirrored: bool,
) {
let pane_focused_for_client_id = self.focused_clients.contains(&client_id);
let other_focused_clients: Vec<ClientId> = self
.focused_clients
@ -89,7 +105,7 @@ impl<'a> PaneContentsAndUi<'a> {
.collect();
let pane_focused_for_differet_client = !other_focused_clients.is_empty();
let frame_color = self.frame_color(client_id, self.mode, session_is_mirrored);
let frame_color = self.frame_color(client_id, client_mode, session_is_mirrored);
let focused_client = if pane_focused_for_client_id {
Some(client_id)
} else if pane_focused_for_differet_client {
@ -132,10 +148,11 @@ impl<'a> PaneContentsAndUi<'a> {
pub fn render_pane_boundaries(
&self,
client_id: ClientId,
client_mode: InputMode,
boundaries: &mut Boundaries,
session_is_mirrored: bool,
) {
let color = self.frame_color(client_id, self.mode, session_is_mirrored);
let color = self.frame_color(client_id, client_mode, session_is_mirrored);
boundaries.add_rect(self.pane.as_ref(), color);
}
fn frame_color(

View file

@ -106,6 +106,7 @@ fn create_new_tab(size: Size) -> Tab {
LayoutTemplate::default().try_into().unwrap(),
vec![1],
index,
client_id,
);
tab
}

View file

@ -25,6 +25,7 @@ use crate::{
pty::{ClientOrTabIndex, PtyInstruction},
screen::ScreenInstruction,
thread_bus::{Bus, ThreadSenders},
ClientId,
};
use zellij_utils::{
@ -40,10 +41,12 @@ use zellij_utils::{
#[derive(Clone, Debug)]
pub(crate) enum PluginInstruction {
Load(Sender<u32>, RunPlugin, usize), // tx_pid, plugin metadata, tab_index
Update(Option<u32>, Event), // Focused plugin / broadcast, event data
Render(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols
Unload(u32),
Load(Sender<u32>, RunPlugin, usize, ClientId), // tx_pid, plugin metadata, tab_index, client_ids
Update(Option<u32>, Option<ClientId>, Event), // Focused plugin / broadcast, client_id, event data
Render(Sender<String>, u32, ClientId, usize, usize), // String buffer, plugin id, client_id, rows, cols
Unload(u32), // plugin_id
AddClient(ClientId),
RemoveClient(ClientId),
Exit,
}
@ -53,8 +56,10 @@ impl From<&PluginInstruction> for PluginContext {
PluginInstruction::Load(..) => PluginContext::Load,
PluginInstruction::Update(..) => PluginContext::Update,
PluginInstruction::Render(..) => PluginContext::Render,
PluginInstruction::Unload(_) => PluginContext::Unload,
PluginInstruction::Unload(..) => PluginContext::Unload,
PluginInstruction::Exit => PluginContext::Exit,
PluginInstruction::AddClient(_) => PluginContext::AddClient,
PluginInstruction::RemoveClient(_) => PluginContext::RemoveClient,
}
}
}
@ -67,6 +72,7 @@ pub(crate) struct PluginEnv {
pub wasi_env: WasiEnv,
pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
pub tab_index: usize,
pub client_id: ClientId,
plugin_own_data_dir: PathBuf,
}
@ -80,38 +86,25 @@ pub(crate) fn wasm_thread_main(
info!("Wasm main thread starts");
let mut plugin_id = 0;
let mut plugin_map = HashMap::new();
let mut headless_plugins = HashMap::new();
let mut plugin_map: HashMap<(u32, ClientId), (Instance, PluginEnv)> = HashMap::new(); // u32 => pid
let mut connected_clients: Vec<ClientId> = vec![];
let plugin_dir = data_dir.join("plugins/");
let plugin_global_data_dir = plugin_dir.join("data");
fs::create_dir_all(&plugin_global_data_dir).unwrap();
for plugin in plugins.iter() {
if let PluginType::Headless = plugin.run {
let (instance, plugin_env) = start_plugin(
plugin_id,
plugin,
0,
&bus,
&store,
&data_dir,
&plugin_global_data_dir,
);
plugin_map.insert(plugin_id, (instance, plugin_env));
plugin_id += 1;
}
}
loop {
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Plugin((&event).into()));
match event {
PluginInstruction::Load(pid_tx, run, tab_index) => {
PluginInstruction::Load(pid_tx, run, tab_index, client_id) => {
let plugin = plugins
.get(&run)
.unwrap_or_else(|| panic!("Plugin {:?} could not be resolved", run));
let (instance, plugin_env) = start_plugin(
plugin_id,
client_id,
&plugin,
tab_index,
&bus,
@ -120,16 +113,34 @@ pub(crate) fn wasm_thread_main(
&plugin_global_data_dir,
);
plugin_map.insert(plugin_id, (instance, plugin_env));
plugin_map.insert(
(plugin_id, client_id),
(instance.clone(), plugin_env.clone()),
);
// clone plugins for the rest of the client ids if they exist
for client_id in connected_clients.iter() {
let mut new_plugin_env = plugin_env.clone();
new_plugin_env.client_id = *client_id;
let module = instance.module().clone();
let wasi = new_plugin_env.wasi_env.import_object(&module).unwrap();
let zellij = zellij_exports(&store, &new_plugin_env);
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
plugin_map.insert((plugin_id, *client_id), (instance, new_plugin_env));
}
pid_tx.send(plugin_id).unwrap();
plugin_id += 1;
}
PluginInstruction::Update(pid, event) => {
for (&i, (instance, plugin_env)) in &plugin_map {
PluginInstruction::Update(pid, cid, event) => {
for (&(plugin_id, client_id), (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) {
if subs.contains(&event_type)
&& ((pid.is_none() && cid.is_none())
|| (pid.is_none() && cid == Some(client_id))
|| (cid.is_none() && pid == Some(plugin_id)))
{
let update = instance.exports.get_function("update").unwrap();
wasi_write_object(&plugin_env.wasi_env, &event);
update.call(&[]).unwrap();
@ -137,11 +148,11 @@ pub(crate) fn wasm_thread_main(
}
drop(bus.senders.send_to_screen(ScreenInstruction::Render));
}
PluginInstruction::Render(buf_tx, pid, rows, cols) => {
PluginInstruction::Render(buf_tx, pid, cid, rows, cols) => {
if rows == 0 || cols == 0 {
buf_tx.send(String::new()).unwrap();
} else {
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
let (instance, plugin_env) = plugin_map.get(&(pid, cid)).unwrap();
let render = instance.exports.get_function("render").unwrap();
render
@ -154,7 +165,54 @@ pub(crate) fn wasm_thread_main(
PluginInstruction::Unload(pid) => {
info!("Bye from plugin {}", &pid);
// TODO: remove plugin's own data directory
drop(plugin_map.remove(&pid));
let ids_in_plugin_map: Vec<(u32, ClientId)> = plugin_map.keys().copied().collect();
for (plugin_id, client_id) in ids_in_plugin_map {
if pid == plugin_id {
drop(plugin_map.remove(&(plugin_id, client_id)));
}
}
}
PluginInstruction::AddClient(client_id) => {
connected_clients.push(client_id);
let mut seen = HashSet::new();
let mut new_plugins = HashMap::new();
for (&(plugin_id, client_id), (instance, plugin_env)) in &plugin_map {
if seen.contains(&plugin_id) {
continue;
} else {
seen.insert(plugin_id);
let mut new_plugin_env = plugin_env.clone();
new_plugin_env.client_id = client_id;
new_plugins.insert(plugin_id, (instance.module().clone(), new_plugin_env));
}
}
for (plugin_id, (module, mut new_plugin_env)) in new_plugins.drain() {
let wasi = new_plugin_env.wasi_env.import_object(&module).unwrap();
let zellij = zellij_exports(&store, &new_plugin_env);
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
plugin_map.insert((plugin_id, client_id), (instance, new_plugin_env));
}
// load headless plugins
for plugin in plugins.iter() {
if let PluginType::Headless = plugin.run {
let (instance, plugin_env) = start_plugin(
plugin_id,
client_id,
plugin,
0,
&bus,
&store,
&data_dir,
&plugin_global_data_dir,
);
headless_plugins.insert(plugin_id, (instance, plugin_env));
plugin_id += 1;
}
}
}
PluginInstruction::RemoveClient(client_id) => {
connected_clients.retain(|c| c != &client_id);
}
PluginInstruction::Exit => break,
}
@ -163,8 +221,10 @@ pub(crate) fn wasm_thread_main(
fs::remove_dir_all(&plugin_global_data_dir).unwrap();
}
#[allow(clippy::too_many_arguments)]
fn start_plugin(
plugin_id: u32,
client_id: ClientId,
plugin: &PluginConfig,
tab_index: usize,
bus: &Bus<PluginInstruction>,
@ -224,6 +284,7 @@ fn start_plugin(
let plugin_env = PluginEnv {
plugin_id,
client_id,
plugin,
senders: bus.senders.clone(),
wasi_env,
@ -346,6 +407,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
// But that's a lot of code, and this is a few lines:
let send_plugin_instructions = plugin_env.senders.to_plugin.clone();
let update_target = Some(plugin_env.plugin_id);
let client_id = plugin_env.client_id;
thread::spawn(move || {
let start_time = Instant::now();
thread::sleep(Duration::from_secs_f64(secs));
@ -357,6 +419,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
.unwrap()
.send(PluginInstruction::Update(
update_target,
Some(client_id),
Event::Timer(elapsed_time),
))
.unwrap();

View file

@ -294,6 +294,8 @@ pub enum PluginContext {
Render,
Unload,
Exit,
AddClient,
RemoveClient,
}
/// Stack call representations corresponding to the different types of [`ClientInstruction`]s.