feat: convert WSL Linux paths to Windows UNC paths when opening binary files

Adds open_binary_file Tauri command that translates WSL Linux-style paths
(e.g. /tmp/mcp_output_abc123.pdf) to Windows UNC paths via wslpath -w
before opening, so binary file links work correctly on Windows/WSL.
Non-Windows platforms pass the path through unchanged. Markdown.svelte
now invokes this command instead of calling openPath directly.
This commit is contained in:
2026-03-13 00:02:16 -07:00
committed by Naomi Carrigan
parent 968e1d5a15
commit d5a4324160
4 changed files with 67 additions and 3 deletions
+61
View File
@@ -2578,6 +2578,32 @@ pub async fn scan_project(working_dir: String) -> Result<ProjectScan, String> {
})
}
#[tauri::command]
pub async fn open_binary_file(app: AppHandle, path: String) -> Result<(), String> {
use tauri_plugin_opener::OpenerExt;
#[cfg(target_os = "windows")]
{
// Convert the WSL Linux path (e.g. /tmp/file.pdf) to a Windows UNC path
// (e.g. \\wsl.localhost\Ubuntu\tmp\file.pdf) so the Windows shell can open it.
let output = std::process::Command::new("wsl")
.args(["wslpath", "-w", &path])
.output()
.map_err(|e| e.to_string())?;
let windows_path = String::from_utf8_lossy(&output.stdout).trim().to_string();
app.opener()
.open_path(windows_path, None::<&str>)
.map_err(|e| e.to_string())
}
#[cfg(not(target_os = "windows"))]
{
app.opener()
.open_path(path, None::<&str>)
.map_err(|e| e.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -3292,4 +3318,39 @@ gitea: gitea-mcp -t stdio (STDIO) - ✓ Connected"#;
Some("Indented Heading".to_string())
);
}
// ==================== open_binary_file E2E path conversion tests ====================
/// Build the wslpath command structure without executing it, for cross-platform CI testing.
#[cfg(test)]
fn build_wslpath_command(path: &str) -> (String, Vec<String>) {
(
"wsl".to_string(),
vec!["wslpath".to_string(), "-w".to_string(), path.to_string()],
)
}
#[test]
fn test_e2e_wslpath_command_structure_pdf() {
let (command, args) = build_wslpath_command("/tmp/mcp_output_abc123.pdf");
assert_eq!(command, "wsl");
assert_eq!(args.len(), 3);
assert_eq!(args[0], "wslpath");
assert_eq!(args[1], "-w");
assert_eq!(args[2], "/tmp/mcp_output_abc123.pdf");
}
#[test]
fn test_e2e_wslpath_command_structure_audio() {
let (command, args) = build_wslpath_command("/tmp/mcp_output_xyz789.mp3");
assert_eq!(command, "wsl");
assert_eq!(args[2], "/tmp/mcp_output_xyz789.mp3");
}
#[test]
fn test_e2e_wslpath_command_structure_preserves_path() {
let path = "/home/naomi/documents/report with spaces.pdf";
let (_, args) = build_wslpath_command(path);
assert_eq!(args[2], path);
}
}
+1
View File
@@ -223,6 +223,7 @@ pub fn run() {
delete_draft,
delete_all_drafts,
scan_project,
open_binary_file,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
+3 -2
View File
@@ -2,7 +2,8 @@
import { marked } from "marked";
import hljs from "highlight.js";
import { onMount } from "svelte";
import { openUrl, openPath } from "@tauri-apps/plugin-opener";
import { openUrl } from "@tauri-apps/plugin-opener";
import { invoke } from "@tauri-apps/api/core";
import { clipboardStore } from "$lib/stores/clipboard";
import { linkifyFilePaths } from "$lib/utils/filePaths";
@@ -148,7 +149,7 @@
const filePath = anchor.dataset.filepath;
if (filePath) {
void openPath(filePath);
void invoke("open_binary_file", { path: filePath });
return;
}
+2 -1
View File
@@ -9,7 +9,8 @@
* - [ ] Code blocks render with syntax highlighting and a copy button
* - [ ] ||spoiler text|| renders as a hidden span revealed on click
* - [ ] Search query highlights matching text in non-code content
* - [ ] Links open in the system browser via the Tauri opener
* - [ ] Regular links open in the system browser via the Tauri opener
* - [ ] Binary file links invoke open_binary_file (WSL-path-aware) instead of openPath
*/
import { describe, it, expect } from "vitest";