fix(plugins): support multiple users (#930)
* fix(plugins): support multiple clients * fix(style): make clippy happy
This commit is contained in:
parent
56e85f87d6
commit
92bddf1b79
11 changed files with 305 additions and 91 deletions
|
|
@ -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
|
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.
|
||||||
|
|
|
||||||
|
|
@ -318,6 +318,14 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||||
} else {
|
} else {
|
||||||
spawn_tabs(None);
|
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) => {
|
ServerInstruction::AttachClient(attrs, options, client_id) => {
|
||||||
let rlock = session_data.read().unwrap();
|
let rlock = session_data.read().unwrap();
|
||||||
|
|
@ -339,6 +347,10 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||||
.senders
|
.senders
|
||||||
.send_to_screen(ScreenInstruction::AddClient(client_id))
|
.send_to_screen(ScreenInstruction::AddClient(client_id))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
session_data
|
||||||
|
.senders
|
||||||
|
.send_to_plugin(PluginInstruction::AddClient(client_id))
|
||||||
|
.unwrap();
|
||||||
let default_mode = options.default_mode.unwrap_or_default();
|
let default_mode = options.default_mode.unwrap_or_default();
|
||||||
let mode_info =
|
let mode_info =
|
||||||
get_mode_info(default_mode, attrs.palette, session_data.capabilities);
|
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
|
.senders
|
||||||
.send_to_plugin(PluginInstruction::Update(
|
.send_to_plugin(PluginInstruction::Update(
|
||||||
None,
|
None,
|
||||||
|
Some(client_id),
|
||||||
Event::ModeUpdate(mode_info),
|
Event::ModeUpdate(mode_info),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.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 => {
|
ServerInstruction::UnblockInputThread => {
|
||||||
for client_id in session_state.read().unwrap().clients.keys() {
|
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
|
.senders
|
||||||
.send_to_screen(ScreenInstruction::RemoveClient(client_id))
|
.send_to_screen(ScreenInstruction::RemoveClient(client_id))
|
||||||
.unwrap();
|
.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() {
|
if session_state.read().unwrap().clients.is_empty() {
|
||||||
*session_data.write().unwrap() = None;
|
*session_data.write().unwrap() = None;
|
||||||
|
|
@ -412,6 +431,14 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||||
.senders
|
.senders
|
||||||
.send_to_screen(ScreenInstruction::RemoveClient(client_id))
|
.send_to_screen(ScreenInstruction::RemoveClient(client_id))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
session_data
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.senders
|
||||||
|
.send_to_plugin(PluginInstruction::RemoveClient(client_id))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ServerInstruction::KillSession => {
|
ServerInstruction::KillSession => {
|
||||||
|
|
@ -444,6 +471,14 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||||
.senders
|
.senders
|
||||||
.send_to_screen(ScreenInstruction::RemoveClient(client_id))
|
.send_to_screen(ScreenInstruction::RemoveClient(client_id))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
session_data
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.senders
|
||||||
|
.send_to_plugin(PluginInstruction::RemoveClient(client_id))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ServerInstruction::Render(mut output) => {
|
ServerInstruction::Render(mut output) => {
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,10 @@ impl Pane for PluginPane {
|
||||||
fn set_selectable(&mut self, selectable: bool) {
|
fn set_selectable(&mut self, selectable: bool) {
|
||||||
self.selectable = selectable;
|
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 self.should_render {
|
||||||
if true {
|
if true {
|
||||||
// while checking should_render rather than rendering each pane every time
|
// while checking should_render rather than rendering each pane every time
|
||||||
|
|
@ -145,6 +148,7 @@ impl Pane for PluginPane {
|
||||||
.send(PluginInstruction::Render(
|
.send(PluginInstruction::Render(
|
||||||
buf_tx,
|
buf_tx,
|
||||||
self.pid,
|
self.pid,
|
||||||
|
client_id,
|
||||||
self.get_content_rows(),
|
self.get_content_rows(),
|
||||||
self.get_content_columns(),
|
self.get_content_columns(),
|
||||||
))
|
))
|
||||||
|
|
@ -274,6 +278,7 @@ impl Pane for PluginPane {
|
||||||
self.send_plugin_instructions
|
self.send_plugin_instructions
|
||||||
.send(PluginInstruction::Update(
|
.send(PluginInstruction::Update(
|
||||||
Some(self.pid),
|
Some(self.pid),
|
||||||
|
None,
|
||||||
Event::Mouse(Mouse::ScrollUp(count)),
|
Event::Mouse(Mouse::ScrollUp(count)),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -282,6 +287,7 @@ impl Pane for PluginPane {
|
||||||
self.send_plugin_instructions
|
self.send_plugin_instructions
|
||||||
.send(PluginInstruction::Update(
|
.send(PluginInstruction::Update(
|
||||||
Some(self.pid),
|
Some(self.pid),
|
||||||
|
None,
|
||||||
Event::Mouse(Mouse::ScrollDown(count)),
|
Event::Mouse(Mouse::ScrollDown(count)),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -293,6 +299,7 @@ impl Pane for PluginPane {
|
||||||
self.send_plugin_instructions
|
self.send_plugin_instructions
|
||||||
.send(PluginInstruction::Update(
|
.send(PluginInstruction::Update(
|
||||||
Some(self.pid),
|
Some(self.pid),
|
||||||
|
None,
|
||||||
Event::Mouse(Mouse::LeftClick(start.line.0, start.column.0)),
|
Event::Mouse(Mouse::LeftClick(start.line.0, start.column.0)),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -301,6 +308,7 @@ impl Pane for PluginPane {
|
||||||
self.send_plugin_instructions
|
self.send_plugin_instructions
|
||||||
.send(PluginInstruction::Update(
|
.send(PluginInstruction::Update(
|
||||||
Some(self.pid),
|
Some(self.pid),
|
||||||
|
None,
|
||||||
Event::Mouse(Mouse::Hold(position.line.0, position.column.0)),
|
Event::Mouse(Mouse::Hold(position.line.0, position.column.0)),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -309,6 +317,7 @@ impl Pane for PluginPane {
|
||||||
self.send_plugin_instructions
|
self.send_plugin_instructions
|
||||||
.send(PluginInstruction::Update(
|
.send(PluginInstruction::Update(
|
||||||
Some(self.pid),
|
Some(self.pid),
|
||||||
|
None,
|
||||||
Event::Mouse(Mouse::Release(
|
Event::Mouse(Mouse::Release(
|
||||||
end.map(|Position { line, column }| (line.0, column.0)),
|
end.map(|Position { line, column }| (line.0, column.0)),
|
||||||
)),
|
)),
|
||||||
|
|
@ -342,6 +351,7 @@ impl Pane for PluginPane {
|
||||||
self.send_plugin_instructions
|
self.send_plugin_instructions
|
||||||
.send(PluginInstruction::Update(
|
.send(PluginInstruction::Update(
|
||||||
Some(self.pid),
|
Some(self.pid),
|
||||||
|
None,
|
||||||
Event::Mouse(Mouse::RightClick(to.line.0, to.column.0)),
|
Event::Mouse(Mouse::RightClick(to.line.0, to.column.0)),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,8 @@ impl Pane for TerminalPane {
|
||||||
fn set_selectable(&mut self, selectable: bool) {
|
fn set_selectable(&mut self, selectable: bool) {
|
||||||
self.selectable = selectable;
|
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() {
|
if self.should_render() {
|
||||||
let mut vte_output = String::new();
|
let mut vte_output = String::new();
|
||||||
let mut character_styles = CharacterStyles::new();
|
let mut character_styles = CharacterStyles::new();
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,11 @@ fn route_action(
|
||||||
let mut should_break = false;
|
let mut should_break = false;
|
||||||
session
|
session
|
||||||
.senders
|
.senders
|
||||||
.send_to_plugin(PluginInstruction::Update(None, Event::InputReceived))
|
.send_to_plugin(PluginInstruction::Update(
|
||||||
|
None,
|
||||||
|
Some(client_id),
|
||||||
|
Event::InputReceived,
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
match action {
|
match action {
|
||||||
Action::ToggleTab => {
|
Action::ToggleTab => {
|
||||||
|
|
@ -70,6 +74,7 @@ fn route_action(
|
||||||
.senders
|
.senders
|
||||||
.send_to_plugin(PluginInstruction::Update(
|
.send_to_plugin(PluginInstruction::Update(
|
||||||
None,
|
None,
|
||||||
|
Some(client_id),
|
||||||
Event::ModeUpdate(get_mode_info(mode, palette, session.capabilities)),
|
Event::ModeUpdate(get_mode_info(mode, palette, session.capabilities)),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -379,12 +384,8 @@ pub(crate) fn route_thread_main(
|
||||||
ClientToServerMsg::Action(action) => {
|
ClientToServerMsg::Action(action) => {
|
||||||
if let Some(rlocked_sessions) = rlocked_sessions.as_ref() {
|
if let Some(rlocked_sessions) = rlocked_sessions.as_ref() {
|
||||||
if let Action::SwitchToMode(input_mode) = action {
|
if let Action::SwitchToMode(input_mode) = action {
|
||||||
for client_id in session_state.read().unwrap().clients.keys() {
|
os_input
|
||||||
os_input.send_to_client(
|
.send_to_client(client_id, ServerToClientMsg::SwitchToMode(input_mode));
|
||||||
*client_id,
|
|
||||||
ServerToClientMsg::SwitchToMode(input_mode),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if route_action(action, rlocked_sessions, &*os_input, &to_server, client_id) {
|
if route_action(action, rlocked_sessions, &*os_input, &to_server, client_id) {
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,8 @@ pub(crate) struct Screen {
|
||||||
/// The indices of this [`Screen`]'s active [`Tab`]s.
|
/// The indices of this [`Screen`]'s active [`Tab`]s.
|
||||||
active_tab_indices: BTreeMap<ClientId, usize>,
|
active_tab_indices: BTreeMap<ClientId, usize>,
|
||||||
tab_history: BTreeMap<ClientId, Vec<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,
|
colors: Palette,
|
||||||
draw_pane_frames: bool,
|
draw_pane_frames: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -206,7 +207,8 @@ impl Screen {
|
||||||
tabs: BTreeMap::new(),
|
tabs: BTreeMap::new(),
|
||||||
overlay: OverlayWindow::default(),
|
overlay: OverlayWindow::default(),
|
||||||
tab_history: BTreeMap::new(),
|
tab_history: BTreeMap::new(),
|
||||||
mode_info,
|
mode_info: BTreeMap::new(),
|
||||||
|
default_mode_info: mode_info,
|
||||||
draw_pane_frames,
|
draw_pane_frames,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -241,12 +243,15 @@ impl Screen {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn move_clients(&mut self, source_index: usize, destination_index: usize) {
|
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();
|
let source_tab = self.tabs.get_mut(&source_index).unwrap();
|
||||||
source_tab.drain_connected_clients()
|
source_tab.drain_connected_clients()
|
||||||
};
|
};
|
||||||
let destination_tab = self.tabs.get_mut(&destination_index).unwrap();
|
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.
|
/// 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) {
|
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) {
|
pub fn new_tab(&mut self, layout: Layout, new_pids: Vec<RawFd>, client_id: ClientId) {
|
||||||
let tab_index = self.get_new_tab_index();
|
let tab_index = self.get_new_tab_index();
|
||||||
let position = self.tabs.len();
|
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(
|
let mut tab = Tab::new(
|
||||||
tab_index,
|
tab_index,
|
||||||
position,
|
position,
|
||||||
|
|
@ -431,16 +441,20 @@ impl Screen {
|
||||||
self.bus.os_input.as_ref().unwrap().clone(),
|
self.bus.os_input.as_ref().unwrap().clone(),
|
||||||
self.bus.senders.clone(),
|
self.bus.senders.clone(),
|
||||||
self.max_panes,
|
self.max_panes,
|
||||||
self.mode_info.clone(),
|
client_mode_info,
|
||||||
self.colors,
|
self.colors,
|
||||||
self.draw_pane_frames,
|
self.draw_pane_frames,
|
||||||
client_id,
|
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) {
|
if let Some(active_tab) = self.get_active_tab_mut(client_id) {
|
||||||
active_tab.visible(false);
|
active_tab.visible(false);
|
||||||
let connected_clients = active_tab.drain_connected_clients();
|
let (connected_clients_in_source_tab, client_mode_infos_in_source_tab) =
|
||||||
tab.add_multiple_clients(&connected_clients);
|
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 {
|
for (client_id, tab_history) in &mut self.tab_history {
|
||||||
let old_active_index = self.active_tab_indices.remove(client_id).unwrap();
|
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.retain(|&e| e != tab_index);
|
||||||
tab_history.push(old_active_index);
|
tab_history.push(old_active_index);
|
||||||
}
|
}
|
||||||
|
tab.update_input_modes();
|
||||||
tab.visible(true);
|
tab.visible(true);
|
||||||
self.tabs.insert(tab_index, tab);
|
self.tabs.insert(tab_index, tab);
|
||||||
if !self.active_tab_indices.contains_key(&client_id) {
|
if !self.active_tab_indices.contains_key(&client_id) {
|
||||||
|
|
@ -503,7 +518,11 @@ impl Screen {
|
||||||
}
|
}
|
||||||
self.bus
|
self.bus
|
||||||
.senders
|
.senders
|
||||||
.send_to_plugin(PluginInstruction::Update(None, Event::TabUpdate(tab_data)))
|
.send_to_plugin(PluginInstruction::Update(
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Event::TabUpdate(tab_data),
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -526,7 +545,12 @@ impl Screen {
|
||||||
self.update_tabs();
|
self.update_tabs();
|
||||||
}
|
}
|
||||||
pub fn change_mode(&mut self, mode_info: ModeInfo, client_id: ClientId) {
|
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)
|
&& (mode_info.mode == InputMode::Normal || mode_info.mode == InputMode::Locked)
|
||||||
{
|
{
|
||||||
self.get_active_tab_mut(client_id)
|
self.get_active_tab_mut(client_id)
|
||||||
|
|
@ -534,9 +558,9 @@ impl Screen {
|
||||||
.clear_active_terminal_scroll(client_id);
|
.clear_active_terminal_scroll(client_id);
|
||||||
}
|
}
|
||||||
self.colors = mode_info.palette;
|
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() {
|
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);
|
tab.mark_active_pane_for_rerender(client_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1100,6 +1124,7 @@ pub(crate) fn screen_thread_main(
|
||||||
}
|
}
|
||||||
ScreenInstruction::AddClient(client_id) => {
|
ScreenInstruction::AddClient(client_id) => {
|
||||||
screen.add_client(client_id);
|
screen.add_client(client_id);
|
||||||
|
screen.update_tabs();
|
||||||
|
|
||||||
screen.render();
|
screen.render();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,8 @@ pub(crate) struct Tab {
|
||||||
pub senders: ThreadSenders,
|
pub senders: ThreadSenders,
|
||||||
synchronize_is_active: bool,
|
synchronize_is_active: bool,
|
||||||
should_clear_display_before_rendering: bool,
|
should_clear_display_before_rendering: bool,
|
||||||
pub mode_info: ModeInfo,
|
mode_info: HashMap<ClientId, ModeInfo>,
|
||||||
|
default_mode_info: ModeInfo,
|
||||||
pub colors: Palette,
|
pub colors: Palette,
|
||||||
connected_clients: HashSet<ClientId>,
|
connected_clients: HashSet<ClientId>,
|
||||||
draw_pane_frames: bool,
|
draw_pane_frames: bool,
|
||||||
|
|
@ -179,7 +180,7 @@ pub trait Pane {
|
||||||
fn set_should_render_boundaries(&mut self, _should_render: bool) {}
|
fn set_should_render_boundaries(&mut self, _should_render: bool) {}
|
||||||
fn selectable(&self) -> bool;
|
fn selectable(&self) -> bool;
|
||||||
fn set_selectable(&mut self, selectable: 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_frame(&mut self, client_id: ClientId, frame_params: FrameParams) -> Option<String>;
|
||||||
fn render_fake_cursor(
|
fn render_fake_cursor(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -342,7 +343,8 @@ impl Tab {
|
||||||
os_api,
|
os_api,
|
||||||
senders,
|
senders,
|
||||||
should_clear_display_before_rendering: false,
|
should_clear_display_before_rendering: false,
|
||||||
mode_info,
|
mode_info: HashMap::new(),
|
||||||
|
default_mode_info: mode_info,
|
||||||
colors,
|
colors,
|
||||||
draw_pane_frames,
|
draw_pane_frames,
|
||||||
// at the moment this is hard-coded while the feature is being developed
|
// 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
|
// TODO: this should be an attribute on Screen instead of full_screen_ws
|
||||||
let free_space = PaneGeom::default();
|
let free_space = PaneGeom::default();
|
||||||
self.panes_to_hide.clear();
|
self.panes_to_hide.clear();
|
||||||
|
|
@ -384,7 +392,7 @@ impl Tab {
|
||||||
let (pid_tx, pid_rx) = channel();
|
let (pid_tx, pid_rx) = channel();
|
||||||
let pane_title = run.location.to_string();
|
let pane_title = run.location.to_string();
|
||||||
self.senders
|
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();
|
.unwrap();
|
||||||
let pid = pid_rx.recv().unwrap();
|
let pid = pid_rx.recv().unwrap();
|
||||||
let mut new_plugin = PluginPane::new(
|
let mut new_plugin = PluginPane::new(
|
||||||
|
|
@ -395,13 +403,6 @@ impl Tab {
|
||||||
);
|
);
|
||||||
new_plugin.set_borderless(layout.borderless);
|
new_plugin.set_borderless(layout.borderless);
|
||||||
self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin));
|
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 {
|
} else {
|
||||||
// there are still panes left to fill, use the pids we received in this method
|
// 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
|
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) {
|
pub fn add_client(&mut self, client_id: ClientId) {
|
||||||
match self.connected_clients.iter().next() {
|
match self.connected_clients.iter().next() {
|
||||||
Some(first_client_id) => {
|
Some(first_client_id) => {
|
||||||
let first_active_pane_id = *self.active_panes.get(first_client_id).unwrap();
|
let first_active_pane_id = *self.active_panes.get(first_client_id).unwrap();
|
||||||
self.connected_clients.insert(client_id);
|
self.connected_clients.insert(client_id);
|
||||||
self.active_panes.insert(client_id, first_active_pane_id);
|
self.active_panes.insert(client_id, first_active_pane_id);
|
||||||
|
self.mode_info
|
||||||
|
.insert(client_id, self.default_mode_info.clone());
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let mut pane_ids: Vec<PaneId> = self.panes.keys().copied().collect();
|
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();
|
let first_pane_id = pane_ids.get(0).unwrap();
|
||||||
self.connected_clients.insert(client_id);
|
self.connected_clients.insert(client_id);
|
||||||
self.active_panes.insert(client_id, *first_pane_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
|
// TODO: we might be able to avoid this, we do this so that newly connected clients will
|
||||||
// necessarily get a full render
|
// necessarily get a full render
|
||||||
self.set_force_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 {
|
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) {
|
pub fn remove_client(&mut self, client_id: ClientId) {
|
||||||
|
|
@ -498,8 +534,12 @@ impl Tab {
|
||||||
self.active_panes.remove(&client_id);
|
self.active_panes.remove(&client_id);
|
||||||
self.set_force_render();
|
self.set_force_render();
|
||||||
}
|
}
|
||||||
pub fn drain_connected_clients(&mut self) -> Vec<ClientId> {
|
pub fn drain_connected_clients(&mut self) -> (Vec<ClientId>, Vec<(ClientId, ModeInfo)>) {
|
||||||
self.connected_clients.drain().collect()
|
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>) {
|
pub fn new_pane(&mut self, pid: PaneId, client_id: Option<ClientId>) {
|
||||||
self.close_down_to_max_terminals();
|
self.close_down_to_max_terminals();
|
||||||
|
|
@ -750,7 +790,7 @@ impl Tab {
|
||||||
PaneId::Plugin(pid) => {
|
PaneId::Plugin(pid) => {
|
||||||
for key in parse_keys(&input_bytes) {
|
for key in parse_keys(&input_bytes) {
|
||||||
self.senders
|
self.senders
|
||||||
.send_to_plugin(PluginInstruction::Update(Some(pid), Event::Key(key)))
|
.send_to_plugin(PluginInstruction::Update(Some(pid), None, Event::Key(key)))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -926,25 +966,35 @@ impl Tab {
|
||||||
let mut client_id_to_boundaries: HashMap<ClientId, Boundaries> = HashMap::new();
|
let mut client_id_to_boundaries: HashMap<ClientId, Boundaries> = HashMap::new();
|
||||||
self.hide_cursor_and_clear_display_as_needed(output);
|
self.hide_cursor_and_clear_display_as_needed(output);
|
||||||
// render panes and their frames
|
// 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()) {
|
if !self.panes_to_hide.contains(&pane.pid()) {
|
||||||
let mut pane_contents_and_ui = PaneContentsAndUi::new(
|
let mut pane_contents_and_ui =
|
||||||
pane,
|
PaneContentsAndUi::new(pane, output, self.colors, &self.active_panes);
|
||||||
output,
|
if let PaneId::Terminal(..) = kind {
|
||||||
self.colors,
|
pane_contents_and_ui.render_pane_contents_for_all_clients();
|
||||||
&self.active_panes,
|
}
|
||||||
self.mode_info.mode,
|
|
||||||
);
|
|
||||||
pane_contents_and_ui.render_pane_contents_for_all_clients();
|
|
||||||
for &client_id in &self.connected_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 {
|
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 {
|
} else {
|
||||||
let boundaries = client_id_to_boundaries
|
let boundaries = client_id_to_boundaries
|
||||||
.entry(client_id)
|
.entry(client_id)
|
||||||
.or_insert_with(|| Boundaries::new(self.viewport));
|
.or_insert_with(|| Boundaries::new(self.viewport));
|
||||||
pane_contents_and_ui.render_pane_boundaries(
|
pane_contents_and_ui.render_pane_boundaries(
|
||||||
client_id,
|
client_id,
|
||||||
|
client_mode,
|
||||||
boundaries,
|
boundaries,
|
||||||
self.session_is_mirrored,
|
self.session_is_mirrored,
|
||||||
);
|
);
|
||||||
|
|
@ -3325,7 +3375,11 @@ impl Tab {
|
||||||
if let Some(selected_text) = selected_text {
|
if let Some(selected_text) = selected_text {
|
||||||
self.write_selection_to_clipboard(&selected_text);
|
self.write_selection_to_clipboard(&selected_text);
|
||||||
self.senders
|
self.senders
|
||||||
.send_to_plugin(PluginInstruction::Update(None, Event::CopyToClipboard))
|
.send_to_plugin(PluginInstruction::Update(
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Event::CopyToClipboard,
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3343,7 +3397,11 @@ impl Tab {
|
||||||
.send_to_server(ServerInstruction::Render(Some(output)))
|
.send_to_server(ServerInstruction::Render(Some(output)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self.senders
|
self.senders
|
||||||
.send_to_plugin(PluginInstruction::Update(None, Event::CopyToClipboard))
|
.send_to_plugin(PluginInstruction::Update(
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Event::CopyToClipboard,
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
fn is_inside_viewport(&self, pane_id: &PaneId) -> bool {
|
fn is_inside_viewport(&self, pane_id: &PaneId) -> bool {
|
||||||
|
|
@ -3387,6 +3445,7 @@ impl Tab {
|
||||||
self.senders
|
self.senders
|
||||||
.send_to_plugin(PluginInstruction::Update(
|
.send_to_plugin(PluginInstruction::Update(
|
||||||
Some(*pid),
|
Some(*pid),
|
||||||
|
None,
|
||||||
Event::Visible(visible),
|
Event::Visible(visible),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ pub struct PaneContentsAndUi<'a> {
|
||||||
colors: Palette,
|
colors: Palette,
|
||||||
focused_clients: Vec<ClientId>,
|
focused_clients: Vec<ClientId>,
|
||||||
multiple_users_exist_in_session: bool,
|
multiple_users_exist_in_session: bool,
|
||||||
mode: InputMode, // TODO: per client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PaneContentsAndUi<'a> {
|
impl<'a> PaneContentsAndUi<'a> {
|
||||||
|
|
@ -22,7 +21,6 @@ impl<'a> PaneContentsAndUi<'a> {
|
||||||
output: &'a mut Output,
|
output: &'a mut Output,
|
||||||
colors: Palette,
|
colors: Palette,
|
||||||
active_panes: &HashMap<ClientId, PaneId>,
|
active_panes: &HashMap<ClientId, PaneId>,
|
||||||
mode: InputMode,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let focused_clients: Vec<ClientId> = active_panes
|
let focused_clients: Vec<ClientId> = active_panes
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -36,11 +34,10 @@ impl<'a> PaneContentsAndUi<'a> {
|
||||||
colors,
|
colors,
|
||||||
focused_clients,
|
focused_clients,
|
||||||
multiple_users_exist_in_session,
|
multiple_users_exist_in_session,
|
||||||
mode,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn render_pane_contents_for_all_clients(&mut self) {
|
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?
|
// FIXME: Use Termion for cursor and style clearing?
|
||||||
self.output.push_str_to_all_clients(&format!(
|
self.output.push_str_to_all_clients(&format!(
|
||||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
"\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) {
|
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_client_id = self.focused_clients.contains(&client_id);
|
||||||
let pane_focused_for_different_client = self
|
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 pane_focused_for_client_id = self.focused_clients.contains(&client_id);
|
||||||
let other_focused_clients: Vec<ClientId> = self
|
let other_focused_clients: Vec<ClientId> = self
|
||||||
.focused_clients
|
.focused_clients
|
||||||
|
|
@ -89,7 +105,7 @@ impl<'a> PaneContentsAndUi<'a> {
|
||||||
.collect();
|
.collect();
|
||||||
let pane_focused_for_differet_client = !other_focused_clients.is_empty();
|
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 {
|
let focused_client = if pane_focused_for_client_id {
|
||||||
Some(client_id)
|
Some(client_id)
|
||||||
} else if pane_focused_for_differet_client {
|
} else if pane_focused_for_differet_client {
|
||||||
|
|
@ -132,10 +148,11 @@ impl<'a> PaneContentsAndUi<'a> {
|
||||||
pub fn render_pane_boundaries(
|
pub fn render_pane_boundaries(
|
||||||
&self,
|
&self,
|
||||||
client_id: ClientId,
|
client_id: ClientId,
|
||||||
|
client_mode: InputMode,
|
||||||
boundaries: &mut Boundaries,
|
boundaries: &mut Boundaries,
|
||||||
session_is_mirrored: bool,
|
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);
|
boundaries.add_rect(self.pane.as_ref(), color);
|
||||||
}
|
}
|
||||||
fn frame_color(
|
fn frame_color(
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ fn create_new_tab(size: Size) -> Tab {
|
||||||
LayoutTemplate::default().try_into().unwrap(),
|
LayoutTemplate::default().try_into().unwrap(),
|
||||||
vec![1],
|
vec![1],
|
||||||
index,
|
index,
|
||||||
|
client_id,
|
||||||
);
|
);
|
||||||
tab
|
tab
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ use crate::{
|
||||||
pty::{ClientOrTabIndex, PtyInstruction},
|
pty::{ClientOrTabIndex, PtyInstruction},
|
||||||
screen::ScreenInstruction,
|
screen::ScreenInstruction,
|
||||||
thread_bus::{Bus, ThreadSenders},
|
thread_bus::{Bus, ThreadSenders},
|
||||||
|
ClientId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
|
|
@ -40,10 +41,12 @@ use zellij_utils::{
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) enum PluginInstruction {
|
pub(crate) enum PluginInstruction {
|
||||||
Load(Sender<u32>, RunPlugin, usize), // tx_pid, plugin metadata, tab_index
|
Load(Sender<u32>, RunPlugin, usize, ClientId), // tx_pid, plugin metadata, tab_index, client_ids
|
||||||
Update(Option<u32>, Event), // Focused plugin / broadcast, event data
|
Update(Option<u32>, Option<ClientId>, Event), // Focused plugin / broadcast, client_id, event data
|
||||||
Render(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols
|
Render(Sender<String>, u32, ClientId, usize, usize), // String buffer, plugin id, client_id, rows, cols
|
||||||
Unload(u32),
|
Unload(u32), // plugin_id
|
||||||
|
AddClient(ClientId),
|
||||||
|
RemoveClient(ClientId),
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,8 +56,10 @@ impl From<&PluginInstruction> for PluginContext {
|
||||||
PluginInstruction::Load(..) => PluginContext::Load,
|
PluginInstruction::Load(..) => PluginContext::Load,
|
||||||
PluginInstruction::Update(..) => PluginContext::Update,
|
PluginInstruction::Update(..) => PluginContext::Update,
|
||||||
PluginInstruction::Render(..) => PluginContext::Render,
|
PluginInstruction::Render(..) => PluginContext::Render,
|
||||||
PluginInstruction::Unload(_) => PluginContext::Unload,
|
PluginInstruction::Unload(..) => PluginContext::Unload,
|
||||||
PluginInstruction::Exit => PluginContext::Exit,
|
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 wasi_env: WasiEnv,
|
||||||
pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
|
pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
|
||||||
pub tab_index: usize,
|
pub tab_index: usize,
|
||||||
|
pub client_id: ClientId,
|
||||||
plugin_own_data_dir: PathBuf,
|
plugin_own_data_dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,38 +86,25 @@ pub(crate) fn wasm_thread_main(
|
||||||
info!("Wasm main thread starts");
|
info!("Wasm main thread starts");
|
||||||
|
|
||||||
let mut plugin_id = 0;
|
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_dir = data_dir.join("plugins/");
|
||||||
let plugin_global_data_dir = plugin_dir.join("data");
|
let plugin_global_data_dir = plugin_dir.join("data");
|
||||||
fs::create_dir_all(&plugin_global_data_dir).unwrap();
|
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 {
|
loop {
|
||||||
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
|
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
|
||||||
err_ctx.add_call(ContextType::Plugin((&event).into()));
|
err_ctx.add_call(ContextType::Plugin((&event).into()));
|
||||||
match event {
|
match event {
|
||||||
PluginInstruction::Load(pid_tx, run, tab_index) => {
|
PluginInstruction::Load(pid_tx, run, tab_index, client_id) => {
|
||||||
let plugin = plugins
|
let plugin = plugins
|
||||||
.get(&run)
|
.get(&run)
|
||||||
.unwrap_or_else(|| panic!("Plugin {:?} could not be resolved", run));
|
.unwrap_or_else(|| panic!("Plugin {:?} could not be resolved", run));
|
||||||
|
|
||||||
let (instance, plugin_env) = start_plugin(
|
let (instance, plugin_env) = start_plugin(
|
||||||
plugin_id,
|
plugin_id,
|
||||||
|
client_id,
|
||||||
&plugin,
|
&plugin,
|
||||||
tab_index,
|
tab_index,
|
||||||
&bus,
|
&bus,
|
||||||
|
|
@ -120,16 +113,34 @@ pub(crate) fn wasm_thread_main(
|
||||||
&plugin_global_data_dir,
|
&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();
|
pid_tx.send(plugin_id).unwrap();
|
||||||
plugin_id += 1;
|
plugin_id += 1;
|
||||||
}
|
}
|
||||||
PluginInstruction::Update(pid, event) => {
|
PluginInstruction::Update(pid, cid, event) => {
|
||||||
for (&i, (instance, plugin_env)) in &plugin_map {
|
for (&(plugin_id, client_id), (instance, plugin_env)) in &plugin_map {
|
||||||
let subs = plugin_env.subscriptions.lock().unwrap();
|
let subs = plugin_env.subscriptions.lock().unwrap();
|
||||||
// FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType?
|
// 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();
|
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();
|
let update = instance.exports.get_function("update").unwrap();
|
||||||
wasi_write_object(&plugin_env.wasi_env, &event);
|
wasi_write_object(&plugin_env.wasi_env, &event);
|
||||||
update.call(&[]).unwrap();
|
update.call(&[]).unwrap();
|
||||||
|
|
@ -137,11 +148,11 @@ pub(crate) fn wasm_thread_main(
|
||||||
}
|
}
|
||||||
drop(bus.senders.send_to_screen(ScreenInstruction::Render));
|
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 {
|
if rows == 0 || cols == 0 {
|
||||||
buf_tx.send(String::new()).unwrap();
|
buf_tx.send(String::new()).unwrap();
|
||||||
} else {
|
} 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();
|
let render = instance.exports.get_function("render").unwrap();
|
||||||
|
|
||||||
render
|
render
|
||||||
|
|
@ -154,7 +165,54 @@ pub(crate) fn wasm_thread_main(
|
||||||
PluginInstruction::Unload(pid) => {
|
PluginInstruction::Unload(pid) => {
|
||||||
info!("Bye from plugin {}", &pid);
|
info!("Bye from plugin {}", &pid);
|
||||||
// TODO: remove plugin's own data directory
|
// 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,
|
PluginInstruction::Exit => break,
|
||||||
}
|
}
|
||||||
|
|
@ -163,8 +221,10 @@ pub(crate) fn wasm_thread_main(
|
||||||
fs::remove_dir_all(&plugin_global_data_dir).unwrap();
|
fs::remove_dir_all(&plugin_global_data_dir).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn start_plugin(
|
fn start_plugin(
|
||||||
plugin_id: u32,
|
plugin_id: u32,
|
||||||
|
client_id: ClientId,
|
||||||
plugin: &PluginConfig,
|
plugin: &PluginConfig,
|
||||||
tab_index: usize,
|
tab_index: usize,
|
||||||
bus: &Bus<PluginInstruction>,
|
bus: &Bus<PluginInstruction>,
|
||||||
|
|
@ -224,6 +284,7 @@ fn start_plugin(
|
||||||
|
|
||||||
let plugin_env = PluginEnv {
|
let plugin_env = PluginEnv {
|
||||||
plugin_id,
|
plugin_id,
|
||||||
|
client_id,
|
||||||
plugin,
|
plugin,
|
||||||
senders: bus.senders.clone(),
|
senders: bus.senders.clone(),
|
||||||
wasi_env,
|
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:
|
// But that's a lot of code, and this is a few lines:
|
||||||
let send_plugin_instructions = plugin_env.senders.to_plugin.clone();
|
let send_plugin_instructions = plugin_env.senders.to_plugin.clone();
|
||||||
let update_target = Some(plugin_env.plugin_id);
|
let update_target = Some(plugin_env.plugin_id);
|
||||||
|
let client_id = plugin_env.client_id;
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
thread::sleep(Duration::from_secs_f64(secs));
|
thread::sleep(Duration::from_secs_f64(secs));
|
||||||
|
|
@ -357,6 +419,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.send(PluginInstruction::Update(
|
.send(PluginInstruction::Update(
|
||||||
update_target,
|
update_target,
|
||||||
|
Some(client_id),
|
||||||
Event::Timer(elapsed_time),
|
Event::Timer(elapsed_time),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,8 @@ pub enum PluginContext {
|
||||||
Render,
|
Render,
|
||||||
Unload,
|
Unload,
|
||||||
Exit,
|
Exit,
|
||||||
|
AddClient,
|
||||||
|
RemoveClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stack call representations corresponding to the different types of [`ClientInstruction`]s.
|
/// Stack call representations corresponding to the different types of [`ClientInstruction`]s.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue