diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs index de6ae028b2..fa1fabcec2 100644 --- a/zellij-server/src/plugins/mod.rs +++ b/zellij-server/src/plugins/mod.rs @@ -158,6 +158,7 @@ pub enum PluginInstruction { }, WatchFilesystem, ListClientsToPlugin(SessionLayoutMetadata, PluginId, ClientId), + ChangePluginHostDir(PathBuf, PluginId, ClientId), Exit, } @@ -204,6 +205,7 @@ impl From<&PluginInstruction> for PluginContext { PluginContext::FailedToWriteConfigToDisk }, PluginInstruction::ListClientsToPlugin(..) => PluginContext::ListClientsToPlugin, + PluginInstruction::ChangePluginHostDir(..) => PluginContext::ChangePluginHostDir, } } } @@ -886,6 +888,11 @@ pub(crate) fn plugin_thread_main( PluginInstruction::WatchFilesystem => { wasm_bridge.start_fs_watcher_if_not_started(); }, + PluginInstruction::ChangePluginHostDir(new_host_folder, plugin_id, client_id) => { + wasm_bridge + .change_plugin_host_dir(new_host_folder, plugin_id, client_id) + .non_fatal(); + }, PluginInstruction::Exit => { break; }, diff --git a/zellij-server/src/plugins/plugin_loader.rs b/zellij-server/src/plugins/plugin_loader.rs index 760824d78e..56ae678632 100644 --- a/zellij-server/src/plugins/plugin_loader.rs +++ b/zellij-server/src/plugins/plugin_loader.rs @@ -14,7 +14,7 @@ use std::{ }; use url::Url; use wasmtime::{Engine, Instance, Linker, Module, Store}; -use wasmtime_wasi::{DirPerms, FilePerms, WasiCtxBuilder}; +use wasmtime_wasi::{preview1::WasiP1Ctx, DirPerms, FilePerms, WasiCtxBuilder}; use zellij_utils::consts::ZELLIJ_PLUGIN_ARTIFACT_DIR; use zellij_utils::prost::Message; @@ -64,7 +64,7 @@ pub struct PluginLoader<'a> { size: Size, wasm_blob_on_hd: Option<(Vec, PathBuf)>, path_to_default_shell: PathBuf, - zellij_cwd: PathBuf, + plugin_cwd: PathBuf, capabilities: PluginCapabilities, client_attributes: ClientAttributes, default_shell: Option, @@ -85,7 +85,7 @@ impl<'a> PluginLoader<'a> { connected_clients: Arc>>, loading_indication: &mut LoadingIndication, path_to_default_shell: PathBuf, - zellij_cwd: PathBuf, + plugin_cwd: PathBuf, capabilities: PluginCapabilities, client_attributes: ClientAttributes, default_shell: Option, @@ -110,7 +110,7 @@ impl<'a> PluginLoader<'a> { engine, &plugin_dir, path_to_default_shell, - Some(zellij_cwd), + Some(plugin_cwd), capabilities, client_attributes, default_shell, @@ -145,7 +145,7 @@ impl<'a> PluginLoader<'a> { connected_clients: Arc>>, loading_indication: &mut LoadingIndication, path_to_default_shell: PathBuf, - zellij_cwd: PathBuf, + plugin_cwd: PathBuf, capabilities: PluginCapabilities, client_attributes: ClientAttributes, default_shell: Option, @@ -168,7 +168,7 @@ impl<'a> PluginLoader<'a> { tab_index, size, path_to_default_shell, - zellij_cwd, + plugin_cwd, capabilities, client_attributes, default_shell, @@ -222,7 +222,7 @@ impl<'a> PluginLoader<'a> { connected_clients: Arc>>, loading_indication: &mut LoadingIndication, path_to_default_shell: PathBuf, - zellij_cwd: PathBuf, + plugin_cwd: PathBuf, capabilities: PluginCapabilities, client_attributes: ClientAttributes, default_shell: Option, @@ -246,7 +246,7 @@ impl<'a> PluginLoader<'a> { engine.clone(), &plugin_dir, path_to_default_shell.clone(), - zellij_cwd.clone(), + plugin_cwd.clone(), capabilities.clone(), client_attributes.clone(), default_shell.clone(), @@ -333,7 +333,7 @@ impl<'a> PluginLoader<'a> { tab_index: Option, size: Size, path_to_default_shell: PathBuf, - zellij_cwd: PathBuf, + plugin_cwd: PathBuf, capabilities: PluginCapabilities, client_attributes: ClientAttributes, default_shell: Option, @@ -366,7 +366,7 @@ impl<'a> PluginLoader<'a> { size, wasm_blob_on_hd: None, path_to_default_shell, - zellij_cwd, + plugin_cwd, capabilities, client_attributes, default_shell, @@ -412,7 +412,7 @@ impl<'a> PluginLoader<'a> { // prefer the explicitly given cwd, otherwise copy it from the running plugin // (when reloading a plugin, we want to copy it, when starting a new plugin instance from // meomory, we want to reset it) - let zellij_cwd = cwd.unwrap_or_else(|| running_plugin.store.data().plugin_cwd.clone()); + let plugin_cwd = cwd.unwrap_or_else(|| running_plugin.store.data().plugin_cwd.clone()); loading_indication.set_name(running_plugin.store.data().name()); PluginLoader::new( plugin_cache, @@ -426,7 +426,7 @@ impl<'a> PluginLoader<'a> { tab_index, size, path_to_default_shell, - zellij_cwd, + plugin_cwd, capabilities, client_attributes, default_shell, @@ -446,7 +446,7 @@ impl<'a> PluginLoader<'a> { engine: Engine, plugin_dir: &'a PathBuf, path_to_default_shell: PathBuf, - zellij_cwd: PathBuf, + plugin_cwd: PathBuf, capabilities: PluginCapabilities, client_attributes: ClientAttributes, default_shell: Option, @@ -483,7 +483,7 @@ impl<'a> PluginLoader<'a> { tab_index, size, path_to_default_shell, - zellij_cwd, + plugin_cwd, capabilities, client_attributes, default_shell, @@ -736,7 +736,7 @@ impl<'a> PluginLoader<'a> { self.engine.clone(), &self.plugin_dir, self.path_to_default_shell.clone(), - self.zellij_cwd.clone(), + self.plugin_cwd.clone(), self.capabilities.clone(), self.client_attributes.clone(), self.default_shell.clone(), @@ -784,18 +784,22 @@ impl<'a> PluginLoader<'a> { }, } } - fn create_plugin_instance_env(&self, module: &Module) -> Result<(Store, Instance)> { - let err_context = || { - format!( - "Failed to create instance, plugin env and subscriptions for plugin {}", - self.plugin_id - ) - }; + pub fn create_wasi_ctx( + host_dir: &PathBuf, + data_dir: &PathBuf, + cache_dir: &PathBuf, + tmp_dir: &PathBuf, + plugin_url: &String, + plugin_id: PluginId, + stdin_pipe: Arc>>, + stdout_pipe: Arc>>, + ) -> Result { + let err_context = || format!("Failed to create wasi_ctx"); let dirs = vec![ - ("/host".to_owned(), self.zellij_cwd.clone()), - ("/data".to_owned(), self.plugin_own_data_dir.clone()), - ("/cache".to_owned(), self.plugin_own_cache_dir.clone()), - ("/tmp".to_owned(), ZELLIJ_TMP_DIR.clone()), + ("/host".to_owned(), host_dir.clone()), + ("/data".to_owned(), data_dir.clone()), + ("/cache".to_owned(), cache_dir.clone()), + ("/tmp".to_owned(), tmp_dir.clone()), ]; let dirs = dirs.into_iter().filter(|(_dir_name, dir)| { // note that this does not protect against TOCTOU errors @@ -812,16 +816,35 @@ impl<'a> PluginLoader<'a> { .preopened_dir(host_path, guest_path, DirPerms::all(), FilePerms::all()) .with_context(err_context)?; } - let stdin_pipe = Arc::new(Mutex::new(VecDeque::new())); - let stdout_pipe = Arc::new(Mutex::new(VecDeque::new())); wasi_ctx_builder .stdin(VecDequeInputStream(stdin_pipe.clone())) .stdout(WriteOutputStream(stdout_pipe.clone())) .stderr(WriteOutputStream(Arc::new(Mutex::new(LoggingPipe::new( - &self.plugin.location.to_string(), - self.plugin_id, + plugin_url, plugin_id, ))))); let wasi_ctx = wasi_ctx_builder.build_p1(); + Ok(wasi_ctx) + } + fn create_plugin_instance_env(&self, module: &Module) -> Result<(Store, Instance)> { + let err_context = || { + format!( + "Failed to create instance, plugin env and subscriptions for plugin {}", + self.plugin_id + ) + }; + let stdin_pipe = Arc::new(Mutex::new(VecDeque::new())); + let stdout_pipe = Arc::new(Mutex::new(VecDeque::new())); + + let wasi_ctx = PluginLoader::create_wasi_ctx( + &self.plugin_cwd, + &self.plugin_own_data_dir, + &self.plugin_own_cache_dir, + &ZELLIJ_TMP_DIR, + &self.plugin.location.to_string(), + self.plugin_id, + stdin_pipe.clone(), + stdout_pipe.clone(), + )?; let plugin = self.plugin.clone(); let plugin_env = PluginEnv { plugin_id: self.plugin_id, @@ -838,7 +861,7 @@ impl<'a> PluginLoader<'a> { client_attributes: self.client_attributes.clone(), default_shell: self.default_shell.clone(), default_layout: self.default_layout.clone(), - plugin_cwd: self.zellij_cwd.clone(), + plugin_cwd: self.plugin_cwd.clone(), input_pipes_to_unblock: Arc::new(Mutex::new(HashSet::new())), input_pipes_to_block: Arc::new(Mutex::new(HashSet::new())), layout_dir: self.layout_dir.clone(), diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs index eceed20211..6096cbd6e0 100644 --- a/zellij-server/src/plugins/wasm_bridge.rs +++ b/zellij-server/src/plugins/wasm_bridge.rs @@ -4,6 +4,7 @@ use crate::plugins::pipes::{ }; use crate::plugins::plugin_loader::PluginLoader; use crate::plugins::plugin_map::{AtomicEvent, PluginEnv, PluginMap, RunningPlugin, Subscriptions}; + use crate::plugins::plugin_worker::MessageToWorker; use crate::plugins::watch_filesystem::watch_filesystem; use crate::plugins::zellij_exports::{wasi_read_string, wasi_write_object}; @@ -18,7 +19,7 @@ use std::{ use wasmtime::{Engine, Module}; use zellij_utils::async_channel::Sender; use zellij_utils::async_std::task::{self, JoinHandle}; -use zellij_utils::consts::ZELLIJ_CACHE_DIR; +use zellij_utils::consts::{ZELLIJ_CACHE_DIR, ZELLIJ_TMP_DIR}; use zellij_utils::data::{InputMode, PermissionStatus, PermissionType, PipeMessage, PipeSource}; use zellij_utils::downloader::Downloader; use zellij_utils::input::keybinds::Keybinds; @@ -736,6 +737,107 @@ impl WasmBridge { } Ok(()) } + pub fn change_plugin_host_dir( + &mut self, + new_host_dir: PathBuf, + plugin_id_to_update: PluginId, + client_id_to_update: ClientId, + ) -> Result<()> { + let plugins_to_change: Vec<( + PluginId, + ClientId, + Arc>, + Arc>, + )> = self + .plugin_map + .lock() + .unwrap() + .running_plugins_and_subscriptions() + .iter() + .cloned() + .filter(|(plugin_id, _client_id, _running_plugin, _subscriptions)| { + // TODO: cache this somehow in this case... + !&self + .cached_events_for_pending_plugins + .contains_key(&plugin_id) + }) + .collect(); + task::spawn({ + let senders = self.senders.clone(); + async move { + match new_host_dir.try_exists() { + Ok(false) => { + log::error!( + "Failed to change folder to {},: folder does not exist", + new_host_dir.display() + ); + let _ = senders.send_to_plugin(PluginInstruction::Update(vec![( + Some(plugin_id_to_update), + Some(client_id_to_update), + Event::FailedToChangeHostFolder(Some(format!( + "Folder {} does not exist", + new_host_dir.display() + ))), + )])); + return; + }, + Err(e) => { + log::error!( + "Failed to change folder to {},: {}", + new_host_dir.display(), + e + ); + let _ = senders.send_to_plugin(PluginInstruction::Update(vec![( + Some(plugin_id_to_update), + Some(client_id_to_update), + Event::FailedToChangeHostFolder(Some(e.to_string())), + )])); + return; + }, + _ => {}, + } + for (plugin_id, client_id, running_plugin, _subscriptions) in &plugins_to_change { + if plugin_id == &plugin_id_to_update && client_id == &client_id_to_update { + let mut running_plugin = running_plugin.lock().unwrap(); + let plugin_env = running_plugin.store.data_mut(); + let stdin_pipe = plugin_env.stdin_pipe.clone(); + let stdout_pipe = plugin_env.stdout_pipe.clone(); + let wasi_ctx = PluginLoader::create_wasi_ctx( + &new_host_dir, + &plugin_env.plugin_own_data_dir, + &plugin_env.plugin_own_cache_dir, + &ZELLIJ_TMP_DIR, + &plugin_env.plugin.location.to_string(), + plugin_env.plugin_id, + stdin_pipe.clone(), + stdout_pipe.clone(), + ); + match wasi_ctx { + Ok(wasi_ctx) => { + drop(std::mem::replace(&mut plugin_env.wasi_ctx, wasi_ctx)); + plugin_env.plugin_cwd = new_host_dir.clone(); + + let _ = senders.send_to_plugin(PluginInstruction::Update(vec![( + Some(*plugin_id), + Some(*client_id), + Event::HostFolderChanged(new_host_dir.clone()), + )])); + }, + Err(e) => { + let _ = senders.send_to_plugin(PluginInstruction::Update(vec![( + Some(*plugin_id), + Some(*client_id), + Event::FailedToChangeHostFolder(Some(e.to_string())), + )])); + log::error!("Failed to create wasi ctx: {}", e); + }, + } + } + } + } + }); + Ok(()) + } pub fn pipe_messages( &mut self, mut messages: Vec<(Option, Option, PipeMessage)>, diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index d308689b9f..aadf28270f 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -352,6 +352,9 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) { write_config_to_disk, } => rebind_keys(env, keys_to_rebind, keys_to_unbind, write_config_to_disk)?, PluginCommand::ListClients => list_clients(env), + PluginCommand::ChangeHostFolder(new_host_folder) => { + change_host_folder(env, new_host_folder) + }, }, (PermissionStatus::Denied, permission) => { log::error!( @@ -1484,6 +1487,16 @@ fn list_clients(env: &PluginEnv) { }); } +fn change_host_folder(env: &PluginEnv, new_host_folder: PathBuf) { + let _ = env.senders.to_plugin.as_ref().map(|sender| { + sender.send(PluginInstruction::ChangePluginHostDir( + new_host_folder, + env.plugin_id, + env.client_id, + )) + }); +} + fn scan_host_folder(env: &PluginEnv, folder_to_scan: PathBuf) { if !folder_to_scan.starts_with("/host") { log::error!( @@ -1913,6 +1926,7 @@ fn check_command_permission( PluginCommand::RebindKeys { .. } | PluginCommand::Reconfigure(..) => { PermissionType::Reconfigure }, + PluginCommand::ChangeHostFolder(..) => PermissionType::FullHdAccess, _ => return (PermissionStatus::Granted, None), }; diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs index bc1b4844b1..1e116f1315 100644 --- a/zellij-tile/src/shim.rs +++ b/zellij-tile/src/shim.rs @@ -1128,6 +1128,13 @@ pub fn rebind_keys( unsafe { host_run_plugin_command() }; } +pub fn change_host_folder(new_host_folder: PathBuf) { + let plugin_command = PluginCommand::ChangeHostFolder(new_host_folder); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + // Utility Functions #[allow(unused)] diff --git a/zellij-utils/assets/prost/api.event.rs b/zellij-utils/assets/prost/api.event.rs index 02d709e64e..b6572a4156 100644 --- a/zellij-utils/assets/prost/api.event.rs +++ b/zellij-utils/assets/prost/api.event.rs @@ -11,7 +11,7 @@ pub struct Event { pub name: i32, #[prost( oneof = "event::Payload", - tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23" + tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25" )] pub payload: ::core::option::Option, } @@ -64,10 +64,26 @@ pub mod event { FailedToWriteConfigToDiskPayload(super::FailedToWriteConfigToDiskPayload), #[prost(message, tag = "23")] ListClientsPayload(super::ListClientsPayload), + #[prost(message, tag = "24")] + HostFolderChangedPayload(super::HostFolderChangedPayload), + #[prost(message, tag = "25")] + FailedToChangeHostFolderPayload(super::FailedToChangeHostFolderPayload), } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct FailedToChangeHostFolderPayload { + #[prost(string, optional, tag = "1")] + pub error_message: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HostFolderChangedPayload { + #[prost(string, tag = "1")] + pub new_host_folder_path: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ListClientsPayload { #[prost(message, repeated, tag = "1")] pub client_info: ::prost::alloc::vec::Vec, @@ -468,6 +484,8 @@ pub enum EventType { CommandPaneReRun = 24, FailedToWriteConfigToDisk = 25, ListClients = 26, + HostFolderChanged = 27, + FailedToChangeHostFolder = 28, } impl EventType { /// String value of the enum field names used in the ProtoBuf definition. @@ -503,6 +521,8 @@ impl EventType { EventType::CommandPaneReRun => "CommandPaneReRun", EventType::FailedToWriteConfigToDisk => "FailedToWriteConfigToDisk", EventType::ListClients => "ListClients", + EventType::HostFolderChanged => "HostFolderChanged", + EventType::FailedToChangeHostFolder => "FailedToChangeHostFolder", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -535,6 +555,8 @@ impl EventType { "CommandPaneReRun" => Some(Self::CommandPaneReRun), "FailedToWriteConfigToDisk" => Some(Self::FailedToWriteConfigToDisk), "ListClients" => Some(Self::ListClients), + "HostFolderChanged" => Some(Self::HostFolderChanged), + "FailedToChangeHostFolder" => Some(Self::FailedToChangeHostFolder), _ => None, } } diff --git a/zellij-utils/assets/prost/api.plugin_command.rs b/zellij-utils/assets/prost/api.plugin_command.rs index 8b3b6752eb..d4604c98f3 100644 --- a/zellij-utils/assets/prost/api.plugin_command.rs +++ b/zellij-utils/assets/prost/api.plugin_command.rs @@ -5,7 +5,7 @@ pub struct PluginCommand { pub name: i32, #[prost( oneof = "plugin_command::Payload", - tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88" + tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89" )] pub payload: ::core::option::Option, } @@ -174,10 +174,18 @@ pub mod plugin_command { LoadNewPluginPayload(super::LoadNewPluginPayload), #[prost(message, tag = "88")] RebindKeysPayload(super::RebindKeysPayload), + #[prost(message, tag = "89")] + ChangeHostFolderPayload(super::ChangeHostFolderPayload), } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct ChangeHostFolderPayload { + #[prost(string, tag = "1")] + pub new_host_folder: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct RebindKeysPayload { #[prost(message, repeated, tag = "1")] pub keys_to_rebind: ::prost::alloc::vec::Vec, @@ -716,6 +724,7 @@ pub enum CommandName { LoadNewPlugin = 111, RebindKeys = 112, ListClients = 113, + ChangeHostFolder = 114, } impl CommandName { /// String value of the enum field names used in the ProtoBuf definition. @@ -840,6 +849,7 @@ impl CommandName { CommandName::LoadNewPlugin => "LoadNewPlugin", CommandName::RebindKeys => "RebindKeys", CommandName::ListClients => "ListClients", + CommandName::ChangeHostFolder => "ChangeHostFolder", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -961,6 +971,7 @@ impl CommandName { "LoadNewPlugin" => Some(Self::LoadNewPlugin), "RebindKeys" => Some(Self::RebindKeys), "ListClients" => Some(Self::ListClients), + "ChangeHostFolder" => Some(Self::ChangeHostFolder), _ => None, } } diff --git a/zellij-utils/assets/prost/api.plugin_permission.rs b/zellij-utils/assets/prost/api.plugin_permission.rs index 013732d9bc..54b7de32e2 100644 --- a/zellij-utils/assets/prost/api.plugin_permission.rs +++ b/zellij-utils/assets/prost/api.plugin_permission.rs @@ -11,6 +11,7 @@ pub enum PermissionType { ReadCliPipes = 7, MessageAndLaunchOtherPlugins = 8, Reconfigure = 9, + FullHdAccess = 10, } impl PermissionType { /// String value of the enum field names used in the ProtoBuf definition. @@ -31,6 +32,7 @@ impl PermissionType { "MessageAndLaunchOtherPlugins" } PermissionType::Reconfigure => "Reconfigure", + PermissionType::FullHdAccess => "FullHdAccess", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -46,6 +48,7 @@ impl PermissionType { "ReadCliPipes" => Some(Self::ReadCliPipes), "MessageAndLaunchOtherPlugins" => Some(Self::MessageAndLaunchOtherPlugins), "Reconfigure" => Some(Self::Reconfigure), + "FullHdAccess" => Some(Self::FullHdAccess), _ => None, } } diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 94b74c4d21..1d0da685e3 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -912,6 +912,8 @@ pub enum Event { CommandPaneReRun(u32, Context), // u32 - terminal_pane_id, Option - FailedToWriteConfigToDisk(Option), // String -> the file path we failed to write ListClients(Vec), + HostFolderChanged(PathBuf), // PathBuf -> new host folder + FailedToChangeHostFolder(Option), // String -> the error we got when changing } #[derive( @@ -942,6 +944,7 @@ pub enum Permission { ReadCliPipes, MessageAndLaunchOtherPlugins, Reconfigure, + FullHdAccess, } impl PermissionType { @@ -963,6 +966,7 @@ impl PermissionType { "Send messages to and launch other plugins".to_owned() }, PermissionType::Reconfigure => "Change Zellij runtime configuration".to_owned(), + PermissionType::FullHdAccess => "Full access to the hard-drive".to_owned(), } } } @@ -1893,4 +1897,5 @@ pub enum PluginCommand { write_config_to_disk: bool, }, ListClients, + ChangeHostFolder(PathBuf), } diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 43deff3b7f..ffabec2647 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -435,6 +435,7 @@ pub enum PluginContext { Reconfigure, FailedToWriteConfigToDisk, ListClientsToPlugin, + ChangePluginHostDir, } /// Stack call representations corresponding to the different types of [`ClientInstruction`]s. diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto index 607ba448b5..be09507ef3 100644 --- a/zellij-utils/src/plugin_api/event.proto +++ b/zellij-utils/src/plugin_api/event.proto @@ -50,6 +50,8 @@ enum EventType { CommandPaneReRun = 24; FailedToWriteConfigToDisk = 25; ListClients = 26; + HostFolderChanged = 27; + FailedToChangeHostFolder = 28; } message EventNameList { @@ -81,9 +83,19 @@ message Event { CommandPaneReRunPayload command_pane_rerun_payload = 21; FailedToWriteConfigToDiskPayload failed_to_write_config_to_disk_payload = 22; ListClientsPayload list_clients_payload = 23; + HostFolderChangedPayload host_folder_changed_payload = 24; + FailedToChangeHostFolderPayload failed_to_change_host_folder_payload = 25; } } +message FailedToChangeHostFolderPayload { + optional string error_message = 1; +} + +message HostFolderChangedPayload { + string new_host_folder_path = 1; +} + message ListClientsPayload { repeated ClientInfo client_info = 1; } diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs index d78f556771..b1ab0e8e24 100644 --- a/zellij-utils/src/plugin_api/event.rs +++ b/zellij-utils/src/plugin_api/event.rs @@ -333,6 +333,22 @@ impl TryFrom for Event { }, _ => Err("Malformed payload for the FailedToWriteConfigToDisk Event"), }, + Some(ProtobufEventType::HostFolderChanged) => match protobuf_event.payload { + Some(ProtobufEventPayload::HostFolderChangedPayload( + host_folder_changed_payload, + )) => Ok(Event::HostFolderChanged(PathBuf::from( + host_folder_changed_payload.new_host_folder_path, + ))), + _ => Err("Malformed payload for the HostFolderChanged Event"), + }, + Some(ProtobufEventType::FailedToChangeHostFolder) => match protobuf_event.payload { + Some(ProtobufEventPayload::FailedToChangeHostFolderPayload( + failed_to_change_host_folder_payload, + )) => Ok(Event::FailedToChangeHostFolder( + failed_to_change_host_folder_payload.error_message, + )), + _ => Err("Malformed payload for the FailedToChangeHostFolder Event"), + }, None => Err("Unknown Protobuf Event"), } } @@ -683,6 +699,20 @@ impl TryFrom for ProtobufEvent { .collect(), })), }), + Event::HostFolderChanged(new_host_folder_path) => Ok(ProtobufEvent { + name: ProtobufEventType::HostFolderChanged as i32, + payload: Some(event::Payload::HostFolderChangedPayload( + HostFolderChangedPayload { + new_host_folder_path: new_host_folder_path.display().to_string(), + }, + )), + }), + Event::FailedToChangeHostFolder(error_message) => Ok(ProtobufEvent { + name: ProtobufEventType::FailedToChangeHostFolder as i32, + payload: Some(event::Payload::FailedToChangeHostFolderPayload( + FailedToChangeHostFolderPayload { error_message }, + )), + }), } } } @@ -1228,6 +1258,8 @@ impl TryFrom for EventType { ProtobufEventType::CommandPaneReRun => EventType::CommandPaneReRun, ProtobufEventType::FailedToWriteConfigToDisk => EventType::FailedToWriteConfigToDisk, ProtobufEventType::ListClients => EventType::ListClients, + ProtobufEventType::HostFolderChanged => EventType::HostFolderChanged, + ProtobufEventType::FailedToChangeHostFolder => EventType::FailedToChangeHostFolder, }) } } @@ -1263,6 +1295,8 @@ impl TryFrom for ProtobufEventType { EventType::CommandPaneReRun => ProtobufEventType::CommandPaneReRun, EventType::FailedToWriteConfigToDisk => ProtobufEventType::FailedToWriteConfigToDisk, EventType::ListClients => ProtobufEventType::ListClients, + EventType::HostFolderChanged => ProtobufEventType::HostFolderChanged, + EventType::FailedToChangeHostFolder => ProtobufEventType::FailedToChangeHostFolder, }) } } diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index 5b58de1433..4cc5c10c8e 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -127,6 +127,7 @@ enum CommandName { LoadNewPlugin = 111; RebindKeys = 112; ListClients = 113; + ChangeHostFolder = 114; } message PluginCommand { @@ -210,9 +211,14 @@ message PluginCommand { ReloadPluginPayload reload_plugin_payload = 86; LoadNewPluginPayload load_new_plugin_payload = 87; RebindKeysPayload rebind_keys_payload = 88; + ChangeHostFolderPayload change_host_folder_payload = 89; } } +message ChangeHostFolderPayload { + string new_host_folder = 1; +} + message RebindKeysPayload { repeated KeyToRebind keys_to_rebind = 1; repeated KeyToUnbind keys_to_unbind = 2; diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index 0668797141..4317a02e97 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -4,9 +4,9 @@ pub use super::generated_api::api::{ input_mode::InputMode as ProtobufInputMode, plugin_command::{ plugin_command::Payload, BreakPanesToNewTabPayload, BreakPanesToTabWithIndexPayload, - ClearScreenForPaneIdPayload, CliPipeOutputPayload, CloseTabWithIndexPayload, CommandName, - ContextItem, EditScrollbackForPaneWithIdPayload, EnvVariable, ExecCmdPayload, - FixedOrPercent as ProtobufFixedOrPercent, + ChangeHostFolderPayload, ClearScreenForPaneIdPayload, CliPipeOutputPayload, + CloseTabWithIndexPayload, CommandName, ContextItem, EditScrollbackForPaneWithIdPayload, + EnvVariable, ExecCmdPayload, FixedOrPercent as ProtobufFixedOrPercent, FixedOrPercentValue as ProtobufFixedOrPercentValue, FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, HidePaneWithIdPayload, HttpVerb as ProtobufHttpVerb, IdAndNewName, KeyToRebind, KeyToUnbind, KillSessionsPayload, @@ -1303,6 +1303,14 @@ impl TryFrom for PluginCommand { Some(_) => Err("ListClients should have no payload, found a payload"), None => Ok(PluginCommand::ListClients), }, + Some(CommandName::ChangeHostFolder) => match protobuf_plugin_command.payload { + Some(Payload::ChangeHostFolderPayload(change_host_folder_payload)) => { + Ok(PluginCommand::ChangeHostFolder(PathBuf::from( + change_host_folder_payload.new_host_folder, + ))) + }, + _ => Err("Mismatched payload for ChangeHostFolder"), + }, None => Err("Unrecognized plugin command"), } } @@ -2130,6 +2138,12 @@ impl TryFrom for ProtobufPluginCommand { name: CommandName::ListClients as i32, payload: None, }), + PluginCommand::ChangeHostFolder(new_host_folder) => Ok(ProtobufPluginCommand { + name: CommandName::ChangeHostFolder as i32, + payload: Some(Payload::ChangeHostFolderPayload(ChangeHostFolderPayload { + new_host_folder: new_host_folder.display().to_string(), // TODO: not accurate? + })), + }), } } } diff --git a/zellij-utils/src/plugin_api/plugin_permission.proto b/zellij-utils/src/plugin_api/plugin_permission.proto index 399ebba528..4f1f90f9ac 100644 --- a/zellij-utils/src/plugin_api/plugin_permission.proto +++ b/zellij-utils/src/plugin_api/plugin_permission.proto @@ -13,4 +13,5 @@ enum PermissionType { ReadCliPipes = 7; MessageAndLaunchOtherPlugins = 8; Reconfigure = 9; + FullHdAccess = 10; } diff --git a/zellij-utils/src/plugin_api/plugin_permission.rs b/zellij-utils/src/plugin_api/plugin_permission.rs index 5ea70157a2..5b6a831fb0 100644 --- a/zellij-utils/src/plugin_api/plugin_permission.rs +++ b/zellij-utils/src/plugin_api/plugin_permission.rs @@ -25,6 +25,7 @@ impl TryFrom for PermissionType { Ok(PermissionType::MessageAndLaunchOtherPlugins) }, ProtobufPermissionType::Reconfigure => Ok(PermissionType::Reconfigure), + ProtobufPermissionType::FullHdAccess => Ok(PermissionType::FullHdAccess), } } } @@ -51,6 +52,7 @@ impl TryFrom for ProtobufPermissionType { Ok(ProtobufPermissionType::MessageAndLaunchOtherPlugins) }, PermissionType::Reconfigure => Ok(ProtobufPermissionType::Reconfigure), + PermissionType::FullHdAccess => Ok(ProtobufPermissionType::FullHdAccess), } } }