diff options
| author | divens <divens@users.noreply.github.com> | 2026-03-21 19:20:32 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-21 19:20:32 +0100 |
| commit | 3fa7744906d5704bdec771c74829841bd06cf7fc (patch) | |
| tree | 6c4f0bd64c2e6087ce069a466cca942f4669f513 /zellij-server | |
| parent | Release notes and fixes (#4879) (diff) | |
| download | sl0ppij-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.rs | 92 |
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 |
