aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-server
diff options
context:
space:
mode:
authordivens <divens@users.noreply.github.com>2026-03-21 19:20:32 +0100
committerGitHub <noreply@github.com>2026-03-21 19:20:32 +0100
commit3fa7744906d5704bdec771c74829841bd06cf7fc (patch)
tree6c4f0bd64c2e6087ce069a466cca942f4669f513 /zellij-server
parentRelease notes and fixes (#4879) (diff)
downloadsl0ppij-3fa7744906d5704bdec771c74829841bd06cf7fc.tar.gz
sl0ppij-3fa7744906d5704bdec771c74829841bd06cf7fc.tar.xz
sl0ppij-3fa7744906d5704bdec771c74829841bd06cf7fc.zip
Add windows support in strider (#4854)
* Add Windows path support to strider plugin Strider is a WASM plugin where std::path::PathBuf uses Unix semantics, so Windows backslash paths break parent(), join(), strip_prefix(), etc. Fix by normalizing all incoming paths to forward slashes at plugin boundaries and converting back to native backslashes for display. A new platform.rs module detects the host OS at runtime from the initial_cwd format and centralizes all platform-aware logic. Also fix scan_host_folder on the server side: canonicalize plugin_host_folder so the starts_with security check works on Windows, where canonicalize() adds the \?\ prefix. * Add virtual root view for drive and WSL navigation in strider On Windows, navigating up from a drive root (e.g. C:\) now shows a "Computer" view listing all available drives and WSL distributions. Drives are enumerated via GetLogicalDriveStringsW and WSL distros via `wsl -l -q` (since read_dir on \wsl.localhost fails with OS error 64/67). Drive letters appear before WSL entries, which are displayed as "Ubuntu-24.04 (WSL)" etc. On non-Windows platforms, behavior is unchanged. * Rename ListHostEntries to ListWindowsVolumes and gate behind FullHdAccess The old name was misleading — on Unix it just returned `/`, which isn't "listing host entries." Rename to ListWindowsVolumes to make the Windows-specific intent explicit, gate behind FullHdAccess permission, and replace the Unix implementation with a log::error since the command is not meaningful on non-Windows platforms. * Replace direct array indexing with .get() in strider platform detection Use safe accessors and matches! macro instead of direct byte indexing for bounds-checked access patterns. --------- Co-authored-by: divens <divens.dev@gmail.com>
Diffstat (limited to 'zellij-server')
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs92
1 files changed, 90 insertions, 2 deletions
diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs
index 9b6f022f..026be937 100644
--- a/zellij-server/src/plugins/zellij_exports.rs
+++ b/zellij-server/src/plugins/zellij_exports.rs
@@ -437,6 +437,7 @@ fn host_run_plugin_command(mut caller: Caller<'_, PluginEnv>) {
scan_host_folder(env, folder_to_scan)
},
PluginCommand::WatchFilesystem => watch_filesystem(env),
+ PluginCommand::ListWindowsVolumes => list_windows_volumes(env),
PluginCommand::DumpSessionLayout { tab_index } => {
dump_session_layout(env, tab_index)
},
@@ -3725,7 +3726,10 @@ fn scan_host_folder(env: &PluginEnv, folder_to_scan: PathBuf) {
);
return;
}
- let plugin_host_folder = env.plugin_cwd.clone();
+ let plugin_host_folder = env
+ .plugin_cwd
+ .canonicalize()
+ .unwrap_or_else(|_| env.plugin_cwd.clone());
let folder_to_scan = plugin_host_folder.join(folder_to_scan.strip_prefix("/host").unwrap());
match folder_to_scan.canonicalize() {
Ok(folder_to_scan) => {
@@ -3784,6 +3788,88 @@ fn scan_host_folder(env: &PluginEnv, folder_to_scan: PathBuf) {
}
}
+#[cfg(not(windows))]
+fn list_windows_volumes(_env: &PluginEnv) {
+ log::error!("ListWindowsVolumes is only supported on Windows");
+}
+
+#[cfg(windows)]
+fn list_windows_volumes(env: &PluginEnv) {
+ let send_plugin_instructions = env.senders.to_plugin.clone();
+ let update_target = Some(env.plugin_id);
+ let client_id = env.client_id;
+ thread::spawn(move || {
+ let mut entries = enumerate_drives();
+ entries.extend(enumerate_wsl_distributions());
+ let _ = send_plugin_instructions
+ .ok_or(anyhow!("found no sender to send plugin instruction to"))
+ .map(|sender| {
+ let _ = sender.send(PluginInstruction::Update(vec![(
+ update_target,
+ Some(client_id),
+ Event::FileSystemUpdate(entries),
+ )]));
+ })
+ .non_fatal();
+ });
+}
+
+#[cfg(windows)]
+fn enumerate_drives() -> Vec<(PathBuf, Option<zellij_utils::data::FileMetadata>)> {
+ use windows_sys::Win32::Storage::FileSystem::GetLogicalDriveStringsW;
+ let mut buf = [0u16; 256];
+ let len = unsafe { GetLogicalDriveStringsW(buf.len() as u32, buf.as_mut_ptr()) };
+ if len == 0 {
+ return vec![];
+ }
+ let drive_strings = &buf[..len as usize];
+ let mut entries = vec![];
+ for chunk in drive_strings.split(|&c| c == 0) {
+ if chunk.is_empty() {
+ continue;
+ }
+ let drive = String::from_utf16_lossy(chunk);
+ let normalized = drive.replace('\\', "/");
+ let drive_path = PathBuf::from(&normalized);
+ let metadata = drive_path.metadata().ok().map(|m| m.into());
+ entries.push((drive_path, metadata));
+ }
+ entries
+}
+
+#[cfg(windows)]
+fn enumerate_wsl_distributions() -> Vec<(PathBuf, Option<zellij_utils::data::FileMetadata>)> {
+ // read_dir on \\wsl.localhost\ or \\wsl$\ can fail with OS error 64/67
+ // even when WSL is running, so enumerate distros via `wsl -l -q` instead.
+ let output = match std::process::Command::new("wsl")
+ .args(["-l", "-q"])
+ .output()
+ {
+ Ok(o) if o.status.success() => o,
+ _ => return vec![],
+ };
+ // wsl -l -q outputs UTF-16LE on Windows
+ let stdout: Vec<u16> = output
+ .stdout
+ .chunks(2)
+ .map(|c| u16::from_le_bytes([c[0], *c.get(1).unwrap_or(&0)]))
+ .collect();
+ let text = String::from_utf16_lossy(&stdout);
+ let mut entries = vec![];
+ for line in text.lines() {
+ let name = line.trim().trim_matches('\0');
+ if name.is_empty() {
+ continue;
+ }
+ // Verify the distro is reachable before listing it
+ let unc = format!("\\\\wsl.localhost\\{}", name);
+ let metadata = std::fs::metadata(&unc).ok().map(|m| m.into());
+ let wsl_path = PathBuf::from(format!("//wsl.localhost/{}/", name));
+ entries.push((wsl_path, metadata));
+ }
+ entries
+}
+
fn resize_pane_with_id(env: &PluginEnv, resize: ResizeStrategy, pane_id: PaneId) {
let _ = env
.senders
@@ -5218,7 +5304,9 @@ fn check_command_permission(
PluginCommand::RebindKeys { .. } | PluginCommand::Reconfigure(..) => {
PermissionType::Reconfigure
},
- PluginCommand::ChangeHostFolder(..) => PermissionType::FullHdAccess,
+ PluginCommand::ChangeHostFolder(..) | PluginCommand::ListWindowsVolumes => {
+ PermissionType::FullHdAccess
+ },
PluginCommand::ShareCurrentSession
| PluginCommand::StopSharingCurrentSession
| PluginCommand::StopWebServer