Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/app/src/components/session/session-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { StatusPopover } from "../status-popover"
const OPEN_APPS = [
"vscode",
"cursor",
"github-desktop",
"zed",
"textmate",
"antigravity",
Expand All @@ -53,6 +54,12 @@ const MAC_APPS = [
openWith: "Visual Studio Code",
},
{ id: "cursor", label: "session.header.open.app.cursor", icon: "cursor", openWith: "Cursor" },
{
id: "github-desktop",
label: "session.header.open.app.githubDesktop",
icon: "github-desktop",
openWith: "GitHub Desktop",
},
{ id: "zed", label: "session.header.open.app.zed", icon: "zed", openWith: "Zed" },
{ id: "textmate", label: "session.header.open.app.textmate", icon: "textmate", openWith: "TextMate" },
{
Expand Down Expand Up @@ -83,6 +90,12 @@ const MAC_APPS = [
const WINDOWS_APPS = [
{ id: "vscode", label: "session.header.open.app.vscode", icon: "vscode", openWith: "code" },
{ id: "cursor", label: "session.header.open.app.cursor", icon: "cursor", openWith: "cursor" },
{
id: "github-desktop",
label: "session.header.open.app.githubDesktop",
icon: "github-desktop",
openWith: "GitHub Desktop",
},
{ id: "zed", label: "session.header.open.app.zed", icon: "zed", openWith: "zed" },
{
id: "powershell",
Expand All @@ -101,6 +114,12 @@ const WINDOWS_APPS = [
const LINUX_APPS = [
{ id: "vscode", label: "session.header.open.app.vscode", icon: "vscode", openWith: "code" },
{ id: "cursor", label: "session.header.open.app.cursor", icon: "cursor", openWith: "cursor" },
{
id: "github-desktop",
label: "session.header.open.app.githubDesktop",
icon: "github-desktop",
openWith: "github-desktop",
},
{ id: "zed", label: "session.header.open.app.zed", icon: "zed", openWith: "zed" },
{
id: "sublime-text",
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ export const dict = {
"session.header.open.fileManager": "File Manager",
"session.header.open.app.vscode": "VS Code",
"session.header.open.app.cursor": "Cursor",
"session.header.open.app.githubDesktop": "GitHub Desktop",
"session.header.open.app.zed": "Zed",
"session.header.open.app.textmate": "TextMate",
"session.header.open.app.antigravity": "Antigravity",
Expand Down
32 changes: 23 additions & 9 deletions packages/desktop-electron/src/main/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,34 @@ export function wslPath(path: string, mode: "windows" | "linux" | null): string
}
}

function aliases(appName: string) {
const trimmed = appName.trim()
if (!trimmed) return []

return [trimmed, trimmed.replaceAll(" ", ""), trimmed.replaceAll(" ", "-"), trimmed.replaceAll(" ", "_")].filter(
(name, index, list) => list.findIndex((item) => item.toLowerCase() === name.toLowerCase()) === index,
)
}

function checkMacosApp(appName: string) {
const locations = [`/Applications/${appName}.app`, `/System/Applications/${appName}.app`]
const names = aliases(appName)
if (!names.length) return false

const locs = names.flatMap((name) => [`/Applications/${name}.app`, `/System/Applications/${name}.app`])

const home = process.env.HOME
if (home) locations.push(`${home}/Applications/${appName}.app`)
if (home) locs.push(...names.map((name) => `${home}/Applications/${name}.app`))

if (locations.some((location) => existsSync(location))) return true
if (locs.some((path) => existsSync(path))) return true

try {
execFileSync("which", [appName])
return true
} catch {
return false
}
return names.some((name) => {
try {
execFileSync("which", [name])
return true
} catch {
return false
}
})
}

function resolveWindowsAppPath(appName: string): string | null {
Expand Down
81 changes: 68 additions & 13 deletions packages/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,35 +187,90 @@ fn open_path(_app: AppHandle, path: String, app_name: Option<String>) -> Result<
.map_err(|e| format!("Failed to open path: {e}"));
}

#[cfg(not(target_os = "windows"))]
#[cfg(target_os = "macos")]
{
if let Some(app_name) = app_name {
return Command::new("open")
.args(["-a", &app_name, &path])
.output()
.map_err(|e| format!("Failed to run open: {e}"))
.and_then(|output| {
if output.status.success() {
return Ok(());
}

Err(format!(
"Failed to open path: {}",
String::from_utf8_lossy(&output.stderr).trim()
))
});
}

return tauri_plugin_opener::open_path(path, Option::<&str>::None)
.map_err(|e| format!("Failed to open path: {e}"));
}

#[cfg(all(not(target_os = "windows"), not(target_os = "macos")))]
tauri_plugin_opener::open_path(path, app_name.as_deref())
.map_err(|e| format!("Failed to open path: {e}"))
}

#[cfg(target_os = "macos")]
fn check_macos_app(app_name: &str) -> bool {
fn names(app_name: &str) -> Vec<String> {
let trimmed = app_name.trim();
if trimmed.is_empty() {
return vec![];
}

let mut list = vec![trimmed.to_string()];
let mut push = |value: String| {
let value = value.trim().to_string();
if value.is_empty() || list.iter().any(|item| item.eq_ignore_ascii_case(&value)) {
return;
}
list.push(value);
};

push(trimmed.replace(' ', ""));
push(trimmed.replace(' ', "-"));
push(trimmed.replace(' ', "_"));

list
}

let names = names(app_name);

// Check common installation locations
let mut app_locations = vec![
format!("/Applications/{}.app", app_name),
format!("/System/Applications/{}.app", app_name),
];
let mut app_locations = names
.iter()
.flat_map(|name| {
[
format!("/Applications/{}.app", name),
format!("/System/Applications/{}.app", name),
]
})
.collect::<Vec<_>>();

if let Ok(home) = std::env::var("HOME") {
app_locations.push(format!("{}/Applications/{}.app", home, app_name));
app_locations.extend(names.iter().map(|name| format!("{}/Applications/{}.app", home, name)));
}

for location in app_locations {
if std::path::Path::new(&location).exists() {
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)
.output()
.map(|output| output.status.success())
.unwrap_or(false)
let out = names.iter().any(|name| {
Command::new("which")
.arg(name)
.output()
.map(|output| output.status.success())
.unwrap_or(false)
});
out
}

#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
Expand Down
43 changes: 43 additions & 0 deletions packages/ui/src/assets/icons/app/github-desktop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/ui/src/components/app-icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import cursor from "../assets/icons/app/cursor.svg"
import fileExplorer from "../assets/icons/app/file-explorer.svg"
import finder from "../assets/icons/app/finder.png"
import ghostty from "../assets/icons/app/ghostty.svg"
import githubDesktop from "../assets/icons/app/github-desktop.svg"
import iterm2 from "../assets/icons/app/iterm2.svg"
import powershell from "../assets/icons/app/powershell.svg"
import terminal from "../assets/icons/app/terminal.png"
Expand All @@ -22,6 +23,7 @@ import sublimetext from "../assets/icons/app/sublimetext.svg"
const icons = {
vscode,
cursor,
"github-desktop": githubDesktop,
zed,
"file-explorer": fileExplorer,
finder,
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/components/app-icons/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
export const iconNames = [
"vscode",
"cursor",
"github-desktop",
"zed",
"file-explorer",
"finder",
Expand Down
Loading