aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-server/src/plugins
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2024-02-06 14:26:14 +0100
committerGitHub <noreply@github.com>2024-02-06 14:26:14 +0100
commit6b20a958f4e8db5614fffb27fe5d32f07ddfe855 (patch)
treec8383fb7941b5e0c23cb348a655f2ea0fafd8668 /zellij-server/src/plugins
parentdocs(readme): add sponsor (diff)
downloadsl0ppij-6b20a958f4e8db5614fffb27fe5d32f07ddfe855.tar.gz
sl0ppij-6b20a958f4e8db5614fffb27fe5d32f07ddfe855.tar.xz
sl0ppij-6b20a958f4e8db5614fffb27fe5d32f07ddfe855.zip
feat(sessions): welcome screen (#3112)
* prototype - can send layout name for new session from session-manager * feat(sessions): ui for selecting layout for new session in the session-manager * fix: send available layouts to plugins * make tests compile * fix tests * improve ui * fix: respect built-in layouts * ui for built-in layouts * some cleanups * style(fmt): rustfmt * welcome screen ui * fix: make sure layout config is not shared between sessions * allow disconnecting other users from current session and killing other sessions * fix: respect default layout * add welcome screen layout * tests(plugins): new api methods * fix(session-manager): do not quit welcome screen on esc and break * fix(plugins): adjust permissions * style(fmt): rustfmt * style(fmt): fix warnings
Diffstat (limited to 'zellij-server/src/plugins')
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs240
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__disconnect_other_clients_plugins_command.snap10
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_session_plugin_command.snap18
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_session_with_layout_plugin_command.snap22
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs41
5 files changed, 328 insertions, 3 deletions
diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs
index 6791213c..6b6e345e 100644
--- a/zellij-server/src/plugins/unit/plugin_tests.rs
+++ b/zellij-server/src/plugins/unit/plugin_tests.rs
@@ -5970,3 +5970,243 @@ pub fn pipe_message_to_plugin_plugin_command() {
});
assert_snapshot!(format!("{:#?}", plugin_bytes_event));
}
+
+#[test]
+#[ignore]
+pub fn switch_session_plugin_command() {
+ let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
+ // destructor removes the directory
+ let plugin_host_folder = PathBuf::from(temp_folder.path());
+ let cache_path = plugin_host_folder.join("permissions_test.kdl");
+ let (plugin_thread_sender, server_receiver, screen_receiver, teardown) =
+ create_plugin_thread_with_server_receiver(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ configuration: Default::default(),
+ };
+ let tab_index = 1;
+ let client_id = 1;
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let received_screen_instructions = Arc::new(Mutex::new(vec![]));
+ let _screen_thread = grant_permissions_and_log_actions_in_thread_naked_variant!(
+ received_screen_instructions,
+ ScreenInstruction::Exit,
+ screen_receiver,
+ 1,
+ &PermissionType::ChangeApplicationState,
+ cache_path,
+ plugin_thread_sender,
+ client_id
+ );
+ let received_server_instruction = Arc::new(Mutex::new(vec![]));
+ let server_thread = log_actions_in_thread!(
+ received_server_instruction,
+ ServerInstruction::SwitchSession,
+ server_receiver,
+ 1
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ false,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ None,
+ client_id,
+ size,
+ None,
+ false,
+ ));
+ std::thread::sleep(std::time::Duration::from_millis(500));
+
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(Key::Ctrl('5')), // this triggers the enent in the fixture plugin
+ )]));
+ std::thread::sleep(std::time::Duration::from_millis(500));
+ teardown();
+ server_thread.join().unwrap(); // this might take a while if the cache is cold
+ let switch_session_event = received_server_instruction
+ .lock()
+ .unwrap()
+ .iter()
+ .rev()
+ .find_map(|i| {
+ if let ServerInstruction::SwitchSession(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", switch_session_event));
+}
+
+#[test]
+#[ignore]
+pub fn switch_session_with_layout_plugin_command() {
+ let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
+ // destructor removes the directory
+ let plugin_host_folder = PathBuf::from(temp_folder.path());
+ let cache_path = plugin_host_folder.join("permissions_test.kdl");
+ let (plugin_thread_sender, server_receiver, screen_receiver, teardown) =
+ create_plugin_thread_with_server_receiver(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ configuration: Default::default(),
+ };
+ let tab_index = 1;
+ let client_id = 1;
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let received_screen_instructions = Arc::new(Mutex::new(vec![]));
+ let _screen_thread = grant_permissions_and_log_actions_in_thread_naked_variant!(
+ received_screen_instructions,
+ ScreenInstruction::Exit,
+ screen_receiver,
+ 1,
+ &PermissionType::ChangeApplicationState,
+ cache_path,
+ plugin_thread_sender,
+ client_id
+ );
+ let received_server_instruction = Arc::new(Mutex::new(vec![]));
+ let server_thread = log_actions_in_thread!(
+ received_server_instruction,
+ ServerInstruction::SwitchSession,
+ server_receiver,
+ 1
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ false,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ None,
+ client_id,
+ size,
+ None,
+ false,
+ ));
+ std::thread::sleep(std::time::Duration::from_millis(500));
+
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(Key::Ctrl('7')), // this triggers the enent in the fixture plugin
+ )]));
+ std::thread::sleep(std::time::Duration::from_millis(500));
+ teardown();
+ server_thread.join().unwrap(); // this might take a while if the cache is cold
+ let switch_session_event = received_server_instruction
+ .lock()
+ .unwrap()
+ .iter()
+ .rev()
+ .find_map(|i| {
+ if let ServerInstruction::SwitchSession(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", switch_session_event));
+}
+
+#[test]
+#[ignore]
+pub fn disconnect_other_clients_plugins_command() {
+ let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
+ // destructor removes the directory
+ let plugin_host_folder = PathBuf::from(temp_folder.path());
+ let cache_path = plugin_host_folder.join("permissions_test.kdl");
+ let (plugin_thread_sender, server_receiver, screen_receiver, teardown) =
+ create_plugin_thread_with_server_receiver(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ configuration: Default::default(),
+ };
+ let tab_index = 1;
+ let client_id = 1;
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let received_screen_instructions = Arc::new(Mutex::new(vec![]));
+ let _screen_thread = grant_permissions_and_log_actions_in_thread_naked_variant!(
+ received_screen_instructions,
+ ScreenInstruction::Exit,
+ screen_receiver,
+ 1,
+ &PermissionType::ChangeApplicationState,
+ cache_path,
+ plugin_thread_sender,
+ client_id
+ );
+ let received_server_instruction = Arc::new(Mutex::new(vec![]));
+ let server_thread = log_actions_in_thread!(
+ received_server_instruction,
+ ServerInstruction::DisconnectAllClientsExcept,
+ server_receiver,
+ 1
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ false,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ None,
+ client_id,
+ size,
+ None,
+ false,
+ ));
+ std::thread::sleep(std::time::Duration::from_millis(500));
+
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(Key::Ctrl('6')), // this triggers the enent in the fixture plugin
+ )]));
+ std::thread::sleep(std::time::Duration::from_millis(500));
+ teardown();
+ server_thread.join().unwrap(); // this might take a while if the cache is cold
+ let switch_session_event = received_server_instruction
+ .lock()
+ .unwrap()
+ .iter()
+ .rev()
+ .find_map(|i| {
+ if let ServerInstruction::DisconnectAllClientsExcept(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", switch_session_event));
+}
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__disconnect_other_clients_plugins_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__disconnect_other_clients_plugins_command.snap
new file mode 100644
index 00000000..d463df1c
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__disconnect_other_clients_plugins_command.snap
@@ -0,0 +1,10 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 6131
+expression: "format!(\"{:#?}\", switch_session_event)"
+---
+Some(
+ DisconnectAllClientsExcept(
+ 1,
+ ),
+)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_session_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_session_plugin_command.snap
new file mode 100644
index 00000000..f254f039
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_session_plugin_command.snap
@@ -0,0 +1,18 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 6051
+expression: "format!(\"{:#?}\", switch_session_event)"
+---
+Some(
+ SwitchSession(
+ ConnectToSession {
+ name: Some(
+ "my_new_session",
+ ),
+ tab_position: None,
+ pane_id: None,
+ layout: None,
+ },
+ 1,
+ ),
+)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_session_with_layout_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_session_with_layout_plugin_command.snap
new file mode 100644
index 00000000..46f6819a
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_session_with_layout_plugin_command.snap
@@ -0,0 +1,22 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 6131
+expression: "format!(\"{:#?}\", switch_session_event)"
+---
+Some(
+ SwitchSession(
+ ConnectToSession {
+ name: Some(
+ "my_other_new_session",
+ ),
+ tab_position: None,
+ pane_id: None,
+ layout: Some(
+ BuiltIn(
+ "compact",
+ ),
+ ),
+ },
+ 1,
+ ),
+)
diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs
index 8791e128..e6787c24 100644
--- a/zellij-server/src/plugins/zellij_exports.rs
+++ b/zellij-server/src/plugins/zellij_exports.rs
@@ -18,10 +18,14 @@ use std::{
use wasmer::{imports, AsStoreMut, Function, FunctionEnv, FunctionEnvMut, Imports};
use wasmer_wasi::WasiEnv;
use zellij_utils::data::{
- CommandType, ConnectToSession, HttpVerb, MessageToPlugin, PermissionStatus, PermissionType,
- PluginPermission,
+ CommandType, ConnectToSession, HttpVerb, LayoutInfo, MessageToPlugin, PermissionStatus,
+ PermissionType, PluginPermission,
};
use zellij_utils::input::permission::PermissionCache;
+use zellij_utils::{
+ interprocess::local_socket::LocalSocketStream,
+ ipc::{ClientToServerMsg, IpcSenderWithContext},
+};
use url::Url;
@@ -225,6 +229,7 @@ fn host_run_plugin_command(env: FunctionEnvMut<ForeignFunctionEnv>) {
connect_to_session.name,
connect_to_session.tab_position,
connect_to_session.pane_id,
+ connect_to_session.layout,
)?,
PluginCommand::DeleteDeadSession(session_name) => {
delete_dead_session(session_name)?
@@ -252,6 +257,8 @@ fn host_run_plugin_command(env: FunctionEnvMut<ForeignFunctionEnv>) {
cli_pipe_output(env, pipe_name, output)?
},
PluginCommand::MessageToPlugin(message) => message_to_plugin(env, message)?,
+ PluginCommand::DisconnectOtherClients => disconnect_other_clients(env),
+ PluginCommand::KillSessions(session_list) => kill_sessions(session_list),
},
(PermissionStatus::Denied, permission) => {
log::error!(
@@ -900,6 +907,7 @@ fn switch_session(
session_name: Option<String>,
tab_position: Option<usize>,
pane_id: Option<(u32, bool)>,
+ layout: Option<LayoutInfo>,
) -> Result<()> {
// pane_id is (id, is_plugin)
let err_context = || format!("Failed to switch session");
@@ -909,6 +917,7 @@ fn switch_session(
name: session_name,
tab_position,
pane_id,
+ layout,
};
env.plugin_env
.senders
@@ -1278,6 +1287,30 @@ fn rename_session(env: &ForeignFunctionEnv, new_session_name: String) {
apply_action!(action, error_msg, env);
}
+fn disconnect_other_clients(env: &ForeignFunctionEnv) {
+ let _ = env
+ .plugin_env
+ .senders
+ .send_to_server(ServerInstruction::DisconnectAllClientsExcept(
+ env.plugin_env.client_id,
+ ))
+ .context("failed to send disconnect other clients instruction");
+}
+
+fn kill_sessions(session_names: Vec<String>) {
+ for session_name in session_names {
+ let path = &*ZELLIJ_SOCK_DIR.join(&session_name);
+ match LocalSocketStream::connect(path) {
+ Ok(stream) => {
+ let _ = IpcSenderWithContext::new(stream).send(ClientToServerMsg::KillSession);
+ },
+ Err(e) => {
+ log::error!("Failed to kill session {}: {:?}", session_name, e);
+ },
+ };
+ }
+}
+
// Custom panic handler for plugins.
//
// This is called when a panic occurs in a plugin. Since most panics will likely originate in the
@@ -1406,7 +1439,9 @@ fn check_command_permission(
| PluginCommand::DeleteDeadSession(..)
| PluginCommand::DeleteAllDeadSessions
| PluginCommand::RenameSession(..)
- | PluginCommand::RenameTab(..) => PermissionType::ChangeApplicationState,
+ | PluginCommand::RenameTab(..)
+ | PluginCommand::DisconnectOtherClients
+ | PluginCommand::KillSessions(..) => PermissionType::ChangeApplicationState,
PluginCommand::UnblockCliPipeInput(..)
| PluginCommand::BlockCliPipeInput(..)
| PluginCommand::CliPipeOutput(..) => PermissionType::ReadCliPipes,