summaryrefslogtreecommitdiffhomepage
path: root/packages/desktop/src-tauri
diff options
context:
space:
mode:
authorFilip <[email protected]>2026-02-10 22:10:58 +0100
committerGitHub <[email protected]>2026-02-10 15:10:58 -0600
commitdce4c05fa9168ca78029142b06363c910cedd06c (patch)
tree1a1ddfa7649a758b8177d17e63935536d15886a6 /packages/desktop/src-tauri
parent8c56571ef95df398fff683253649414a1681c6f6 (diff)
downloadopencode-dce4c05fa9168ca78029142b06363c910cedd06c.tar.gz
opencode-dce4c05fa9168ca78029142b06363c910cedd06c.zip
fix(desktop): open apps with executables on Windows (#13022)
Diffstat (limited to 'packages/desktop/src-tauri')
-rw-r--r--packages/desktop/src-tauri/src/lib.rs169
1 files changed, 162 insertions, 7 deletions
diff --git a/packages/desktop/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs
index 84dd1859b..82f0441ad 100644
--- a/packages/desktop/src-tauri/src/lib.rs
+++ b/packages/desktop/src-tauri/src/lib.rs
@@ -20,9 +20,9 @@ use std::{
env,
net::TcpListener,
path::PathBuf,
+ process::Command,
sync::{Arc, Mutex},
time::Duration,
- process::Command,
};
use tauri::{AppHandle, Manager, RunEvent, State, ipc::Channel};
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
@@ -152,12 +152,12 @@ fn check_app_exists(app_name: &str) -> bool {
{
check_windows_app(app_name)
}
-
+
#[cfg(target_os = "macos")]
{
check_macos_app(app_name)
}
-
+
#[cfg(target_os = "linux")]
{
check_linux_app(app_name)
@@ -165,11 +165,165 @@ fn check_app_exists(app_name: &str) -> bool {
}
#[cfg(target_os = "windows")]
-fn check_windows_app(app_name: &str) -> bool {
+fn check_windows_app(_app_name: &str) -> bool {
// Check if command exists in PATH, including .exe
return true;
}
+#[cfg(target_os = "windows")]
+fn resolve_windows_app_path(app_name: &str) -> Option<String> {
+ use std::path::{Path, PathBuf};
+
+ // Try to find the command using 'where'
+ let output = Command::new("where").arg(app_name).output().ok()?;
+
+ if !output.status.success() {
+ return None;
+ }
+
+ let paths = String::from_utf8_lossy(&output.stdout)
+ .lines()
+ .map(str::trim)
+ .filter(|line| !line.is_empty())
+ .map(PathBuf::from)
+ .collect::<Vec<_>>();
+
+ let has_ext = |path: &Path, ext: &str| {
+ path.extension()
+ .and_then(|v| v.to_str())
+ .map(|v| v.eq_ignore_ascii_case(ext))
+ .unwrap_or(false)
+ };
+
+ if let Some(path) = paths.iter().find(|path| has_ext(path, "exe")) {
+ return Some(path.to_string_lossy().to_string());
+ }
+
+ let resolve_cmd = |path: &Path| -> Option<String> {
+ let content = std::fs::read_to_string(path).ok()?;
+
+ for token in content.split('"') {
+ let lower = token.to_ascii_lowercase();
+ if !lower.contains(".exe") {
+ continue;
+ }
+
+ if let Some(index) = lower.find("%~dp0") {
+ let base = path.parent()?;
+ let suffix = &token[index + 5..];
+ let mut resolved = PathBuf::from(base);
+
+ for part in suffix.replace('/', "\\").split('\\') {
+ if part.is_empty() || part == "." {
+ continue;
+ }
+ if part == ".." {
+ let _ = resolved.pop();
+ continue;
+ }
+ resolved.push(part);
+ }
+
+ if resolved.exists() {
+ return Some(resolved.to_string_lossy().to_string());
+ }
+ }
+
+ let resolved = PathBuf::from(token);
+ if resolved.exists() {
+ return Some(resolved.to_string_lossy().to_string());
+ }
+ }
+
+ None
+ };
+
+ for path in &paths {
+ if has_ext(path, "cmd") || has_ext(path, "bat") {
+ if let Some(resolved) = resolve_cmd(path) {
+ return Some(resolved);
+ }
+ }
+
+ if path.extension().is_none() {
+ let cmd = path.with_extension("cmd");
+ if cmd.exists() {
+ if let Some(resolved) = resolve_cmd(&cmd) {
+ return Some(resolved);
+ }
+ }
+
+ let bat = path.with_extension("bat");
+ if bat.exists() {
+ if let Some(resolved) = resolve_cmd(&bat) {
+ return Some(resolved);
+ }
+ }
+ }
+ }
+
+ let key = app_name
+ .chars()
+ .filter(|v| v.is_ascii_alphanumeric())
+ .flat_map(|v| v.to_lowercase())
+ .collect::<String>();
+
+ if !key.is_empty() {
+ for path in &paths {
+ let dirs = [
+ path.parent(),
+ path.parent().and_then(|dir| dir.parent()),
+ path.parent()
+ .and_then(|dir| dir.parent())
+ .and_then(|dir| dir.parent()),
+ ];
+
+ for dir in dirs.into_iter().flatten() {
+ if let Ok(entries) = std::fs::read_dir(dir) {
+ for entry in entries.flatten() {
+ let candidate = entry.path();
+ if !has_ext(&candidate, "exe") {
+ continue;
+ }
+
+ let Some(stem) = candidate.file_stem().and_then(|v| v.to_str()) else {
+ continue;
+ };
+
+ let name = stem
+ .chars()
+ .filter(|v| v.is_ascii_alphanumeric())
+ .flat_map(|v| v.to_lowercase())
+ .collect::<String>();
+
+ if name.contains(&key) || key.contains(&name) {
+ return Some(candidate.to_string_lossy().to_string());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ paths.first().map(|path| path.to_string_lossy().to_string())
+}
+
+#[tauri::command]
+#[specta::specta]
+fn resolve_app_path(app_name: &str) -> Option<String> {
+ #[cfg(target_os = "windows")]
+ {
+ resolve_windows_app_path(app_name)
+ }
+
+ #[cfg(not(target_os = "windows"))]
+ {
+ // On macOS/Linux, just return the app_name as-is since
+ // the opener plugin handles them correctly
+ Some(app_name.to_string())
+ }
+}
+
#[cfg(target_os = "macos")]
fn check_macos_app(app_name: &str) -> bool {
// Check common installation locations
@@ -181,13 +335,13 @@ fn check_macos_app(app_name: &str) -> bool {
if let Ok(home) = std::env::var("HOME") {
app_locations.push(format!("{}/Applications/{}.app", home, app_name));
}
-
+
for location in app_locations {
if std::path::Path::new(&location).exists() {
return true;
}
}
-
+
// Also check if command exists in PATH
Command::new("which")
.arg(app_name)
@@ -251,7 +405,8 @@ pub fn run() {
get_display_backend,
set_display_backend,
markdown::parse_markdown_command,
- check_app_exists
+ check_app_exists,
+ resolve_app_path
])
.events(tauri_specta::collect_events![LoadingWindowComplete])
.error_handling(tauri_specta::ErrorHandlingMode::Throw);