aboutsummaryrefslogtreecommitdiff
path: root/src/platform/windows.rs
diff options
context:
space:
mode:
authorJJ Liebig <jonathan.liebig@gmail.com>2026-09-05 21:56:17 +0200
committerGitHub <noreply@github.com>2026-09-05 21:56:17 +0200
commit6f57f2f62c5d2591647262caf15bc3c962b9c9f8 (patch)
treeaeece6f488a6ec55e15369151fed74f0137a69bb /src/platform/windows.rs
parent50ce4ec2ddb8ec621a77d8773aa6df6d9e7441ff (diff)
fix: resolve Windows plugin pane launch paths (#3666)
* fix: resolve Windows plugin pane launch paths refs #3024 * test: compare resolved Windows plugin probe paths refs #3024 * test: compare resolved Windows plugin working directories refs #3024 * test: wait for Windows plugin fixture completion refs #3024
Diffstat (limited to 'src/platform/windows.rs')
-rw-r--r--src/platform/windows.rs107
1 files changed, 106 insertions, 1 deletions
diff --git a/src/platform/windows.rs b/src/platform/windows.rs
index 10fbb6f0..1c5c4afb 100644
--- a/src/platform/windows.rs
+++ b/src/platform/windows.rs
@@ -30,7 +30,7 @@ use windows_sys::{
Win32::{
Foundation::{
CloseHandle, GlobalFree, LocalFree, FILETIME, HANDLE, HWND, INVALID_HANDLE_VALUE,
- NTSTATUS, STATUS_SUCCESS, UNICODE_STRING,
+ MAX_PATH, NTSTATUS, STATUS_SUCCESS, UNICODE_STRING,
},
Globalization::{CompareStringOrdinal, CSTR_EQUAL, CSTR_GREATER_THAN, CSTR_LESS_THAN},
Security::SECURITY_ATTRIBUTES,
@@ -105,6 +105,44 @@ pub(crate) fn prepare_paste_text_for_pty_platform(text: String) -> String {
text.replace("\r\n", "\n").replace('\n', "\r\n")
}
+pub(crate) fn plugin_runtime_path_platform(path: &std::path::Path) -> PathBuf {
+ use std::os::windows::ffi::OsStrExt;
+
+ let Some(candidate) = standard_windows_path(path) else {
+ return path.to_path_buf();
+ };
+ // Rust can canonicalize a long standard path by adding its own verbatim prefix, but native
+ // process consumers still need the original prefix when the plugin root exceeds MAX_PATH.
+ if candidate.join("").as_os_str().encode_wide().count() >= MAX_PATH as usize {
+ return path.to_path_buf();
+ }
+ match candidate.canonicalize() {
+ Ok(canonical) if canonical == path => candidate,
+ _ => path.to_path_buf(),
+ }
+}
+
+fn standard_windows_path(path: &std::path::Path) -> Option<PathBuf> {
+ use std::path::{Component, Prefix};
+
+ let mut components = path.components();
+ let Component::Prefix(prefix) = components.next()? else {
+ return None;
+ };
+ let mut candidate = match prefix.kind() {
+ Prefix::VerbatimDisk(drive) => PathBuf::from(format!("{}:", char::from(drive))),
+ Prefix::VerbatimUNC(server, share) => {
+ let mut candidate = PathBuf::from(r"\\");
+ candidate.push(server);
+ candidate.push(share);
+ candidate
+ }
+ _ => return None,
+ };
+ candidate.push(components.as_path());
+ Some(candidate)
+}
+
/// Resolves against the current foreground layout because asynchronous console
/// records do not retain the layout that was active when the key was pressed.
pub(crate) fn resolve_base_printable_key(vk: u16, scan: u16) -> Option<char> {
@@ -2570,6 +2608,73 @@ mod tests {
};
#[test]
+ fn windows_standard_plugin_runtime_paths_drop_only_disk_and_unc_verbatim_prefixes() {
+ assert_eq!(
+ super::standard_windows_path(std::path::Path::new(r"\\?\C:\plugins\example")),
+ Some(std::path::PathBuf::from(r"C:\plugins\example"))
+ );
+ assert_eq!(
+ super::standard_windows_path(std::path::Path::new(
+ r"\\?\UNC\server\share\plugins\example"
+ )),
+ Some(std::path::PathBuf::from(r"\\server\share\plugins\example"))
+ );
+ assert_eq!(
+ super::standard_windows_path(std::path::Path::new(
+ r"\\?\Volume{01234567-89ab-cdef-0123-456789abcdef}\plugins"
+ )),
+ None
+ );
+ }
+
+ #[test]
+ fn windows_plugin_runtime_path_keeps_extended_path_when_normal_form_is_not_equivalent() {
+ let path = std::path::PathBuf::from(format!(
+ r"\\?\C:\herdr-missing-plugin-runtime-path-{}",
+ std::process::id()
+ ));
+ assert_eq!(super::plugin_runtime_path_platform(&path), path);
+ }
+
+ #[test]
+ fn windows_plugin_runtime_path_keeps_verbatim_root_beyond_max_path() {
+ use std::os::windows::ffi::OsStrExt;
+
+ let base = std::env::temp_dir().join(format!(
+ "herdr-plugin-runtime-path-limit-test-{}",
+ std::process::id()
+ ));
+ fs::create_dir_all(&base).expect("create test base");
+ let extended_base = base.canonicalize().expect("canonicalize test base");
+ let normal_base = super::standard_windows_path(&extended_base)
+ .expect("test base has a standard drive path");
+ let normal_base_len = normal_base.as_os_str().encode_wide().count();
+ let root_at_length = |length| {
+ let component_len = length - normal_base_len - 1;
+ let path = extended_base.join("é".repeat(component_len));
+ fs::create_dir(&path).expect("create length-boundary test root");
+ path.canonicalize()
+ .expect("canonicalize length-boundary test root")
+ };
+
+ let at_limit = root_at_length(windows_sys::Win32::Foundation::MAX_PATH as usize - 2);
+ let at_limit_normal =
+ super::standard_windows_path(&at_limit).expect("convert root at MAX_PATH boundary");
+ assert_eq!(
+ super::plugin_runtime_path_platform(&at_limit),
+ at_limit_normal
+ );
+
+ let beyond_limit = root_at_length(windows_sys::Win32::Foundation::MAX_PATH as usize - 1);
+ assert_eq!(
+ super::plugin_runtime_path_platform(&beyond_limit),
+ beyond_limit
+ );
+
+ fs::remove_dir_all(base).expect("remove test directory");
+ }
+
+ #[test]
fn paste_text_uses_windows_line_endings() {
assert_eq!(
super::prepare_paste_text_for_pty_platform("one\ntwo\r\nthree\rfour".to_owned()),