summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/app/src/app.tsx2
-rw-r--r--packages/app/src/components/settings-general.tsx28
-rw-r--r--packages/app/src/context/platform.tsx6
-rw-r--r--packages/app/src/i18n/ar.ts3
-rw-r--r--packages/app/src/i18n/br.ts3
-rw-r--r--packages/app/src/i18n/bs.ts3
-rw-r--r--packages/app/src/i18n/da.ts3
-rw-r--r--packages/app/src/i18n/de.ts3
-rw-r--r--packages/app/src/i18n/en.ts3
-rw-r--r--packages/app/src/i18n/es.ts3
-rw-r--r--packages/app/src/i18n/fr.ts3
-rw-r--r--packages/app/src/i18n/ja.ts3
-rw-r--r--packages/app/src/i18n/ko.ts3
-rw-r--r--packages/app/src/i18n/no.ts3
-rw-r--r--packages/app/src/i18n/pl.ts3
-rw-r--r--packages/app/src/i18n/ru.ts3
-rw-r--r--packages/app/src/i18n/th.ts3
-rw-r--r--packages/app/src/i18n/zh.ts3
-rw-r--r--packages/app/src/i18n/zht.ts3
-rw-r--r--packages/desktop/AGENTS.md4
-rw-r--r--packages/desktop/src-tauri/src/cli.rs138
-rw-r--r--packages/desktop/src-tauri/src/constants.rs1
-rw-r--r--packages/desktop/src-tauri/src/lib.rs107
-rw-r--r--packages/desktop/src-tauri/src/server.rs45
-rw-r--r--packages/desktop/src-tauri/src/windows.rs11
-rw-r--r--packages/desktop/src/bindings.ts9
-rw-r--r--packages/desktop/src/index.tsx638
27 files changed, 351 insertions, 686 deletions
diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx
index e49b725a1..5bbe86e20 100644
--- a/packages/app/src/app.tsx
+++ b/packages/app/src/app.tsx
@@ -43,7 +43,7 @@ function UiI18nBridge(props: ParentProps) {
declare global {
interface Window {
- __OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string; deepLinks?: string[]; wsl?: boolean }
+ __OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string; deepLinks?: string[] }
}
}
diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx
index 72135c342..db057a4c4 100644
--- a/packages/app/src/components/settings-general.tsx
+++ b/packages/app/src/components/settings-general.tsx
@@ -367,34 +367,6 @@ export const SettingsGeneral: Component = () => {
</div>
</div>
- <Show when={platform.platform === "desktop" && platform.os === "windows" && platform.getWslEnabled}>
- {(_) => {
- const [enabledResource, actions] = createResource(() => platform.getWslEnabled?.())
- const enabled = () => (enabledResource.state === "pending" ? undefined : enabledResource.latest)
-
- return (
- <div class="flex flex-col gap-1">
- <h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.desktop.section.wsl")}</h3>
-
- <div class="bg-surface-raised-base px-4 rounded-lg">
- <SettingsRow
- title={language.t("settings.desktop.wsl.title")}
- description={language.t("settings.desktop.wsl.description")}
- >
- <div data-action="settings-wsl">
- <Switch
- checked={enabled() ?? false}
- disabled={enabledResource.state === "pending"}
- onChange={(checked) => platform.setWslEnabled?.(checked)?.finally(() => actions.refetch())}
- />
- </div>
- </SettingsRow>
- </div>
- </div>
- )
- }}
- </Show>
-
{/* Updates Section */}
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.updates")}</h3>
diff --git a/packages/app/src/context/platform.tsx b/packages/app/src/context/platform.tsx
index e260c1977..7aa6c6554 100644
--- a/packages/app/src/context/platform.tsx
+++ b/packages/app/src/context/platform.tsx
@@ -57,12 +57,6 @@ export type Platform = {
/** Set the default server URL to use on app startup (platform-specific) */
setDefaultServerUrl?(url: string | null): Promise<void> | void
- /** Get the configured WSL integration (desktop only) */
- getWslEnabled?(): Promise<boolean>
-
- /** Set the configured WSL integration (desktop only) */
- setWslEnabled?(config: boolean): Promise<void> | void
-
/** Get the preferred display backend (desktop only) */
getDisplayBackend?(): Promise<DisplayBackend | null> | DisplayBackend | null
diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts
index 7a09edc51..201d63660 100644
--- a/packages/app/src/i18n/ar.ts
+++ b/packages/app/src/i18n/ar.ts
@@ -508,9 +508,6 @@ export const dict = {
"settings.section.server": "الخادم",
"settings.tab.general": "عام",
"settings.tab.shortcuts": "اختصارات",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "المظهر",
"settings.general.section.notifications": "إشعارات النظام",
diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts
index ba09fbe03..b7f2d7485 100644
--- a/packages/app/src/i18n/br.ts
+++ b/packages/app/src/i18n/br.ts
@@ -512,9 +512,6 @@ export const dict = {
"settings.section.server": "Servidor",
"settings.tab.general": "Geral",
"settings.tab.shortcuts": "Atalhos",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "Aparência",
"settings.general.section.notifications": "Notificações do sistema",
diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts
index 38d6b79c9..05eca1628 100644
--- a/packages/app/src/i18n/bs.ts
+++ b/packages/app/src/i18n/bs.ts
@@ -539,9 +539,6 @@ export const dict = {
"settings.section.server": "Server",
"settings.tab.general": "Opšte",
"settings.tab.shortcuts": "Prečice",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "Izgled",
"settings.general.section.notifications": "Sistemske obavijesti",
diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts
index e36fb16d5..8ea4907c1 100644
--- a/packages/app/src/i18n/da.ts
+++ b/packages/app/src/i18n/da.ts
@@ -512,9 +512,6 @@ export const dict = {
"settings.section.server": "Server",
"settings.tab.general": "Generelt",
"settings.tab.shortcuts": "Genveje",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "Udseende",
"settings.general.section.notifications": "Systemmeddelelser",
diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts
index 633d51d05..a4884a103 100644
--- a/packages/app/src/i18n/de.ts
+++ b/packages/app/src/i18n/de.ts
@@ -556,9 +556,6 @@ export const dict = {
"settings.section.server": "Server",
"settings.tab.general": "Allgemein",
"settings.tab.shortcuts": "Tastenkombinationen",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "Erscheinungsbild",
"settings.general.section.notifications": "Systembenachrichtigungen",
diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts
index c138c7b61..b0ffa70f8 100644
--- a/packages/app/src/i18n/en.ts
+++ b/packages/app/src/i18n/en.ts
@@ -583,9 +583,6 @@ export const dict = {
"settings.section.server": "Server",
"settings.tab.general": "General",
"settings.tab.shortcuts": "Shortcuts",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "Appearance",
"settings.general.section.notifications": "System notifications",
diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts
index ff4198228..50d906070 100644
--- a/packages/app/src/i18n/es.ts
+++ b/packages/app/src/i18n/es.ts
@@ -515,9 +515,6 @@ export const dict = {
"settings.section.server": "Servidor",
"settings.tab.general": "General",
"settings.tab.shortcuts": "Atajos",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "Apariencia",
"settings.general.section.notifications": "Notificaciones del sistema",
diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts
index 402c095ba..7ad39f340 100644
--- a/packages/app/src/i18n/fr.ts
+++ b/packages/app/src/i18n/fr.ts
@@ -522,9 +522,6 @@ export const dict = {
"settings.section.server": "Serveur",
"settings.tab.general": "Général",
"settings.tab.shortcuts": "Raccourcis",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "Apparence",
"settings.general.section.notifications": "Notifications système",
diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts
index 312ac3262..a39bfbaf3 100644
--- a/packages/app/src/i18n/ja.ts
+++ b/packages/app/src/i18n/ja.ts
@@ -507,9 +507,6 @@ export const dict = {
"settings.section.server": "サーバー",
"settings.tab.general": "一般",
"settings.tab.shortcuts": "ショートカット",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "外観",
"settings.general.section.notifications": "システム通知",
diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts
index b162ab391..b5927b210 100644
--- a/packages/app/src/i18n/ko.ts
+++ b/packages/app/src/i18n/ko.ts
@@ -513,9 +513,6 @@ export const dict = {
"settings.section.server": "서버",
"settings.tab.general": "일반",
"settings.tab.shortcuts": "단축키",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "모양",
"settings.general.section.notifications": "시스템 알림",
diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts
index 001b9eda6..7d8cdd27f 100644
--- a/packages/app/src/i18n/no.ts
+++ b/packages/app/src/i18n/no.ts
@@ -515,9 +515,6 @@ export const dict = {
"settings.section.server": "Server",
"settings.tab.general": "Generelt",
"settings.tab.shortcuts": "Snarveier",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "Utseende",
"settings.general.section.notifications": "Systemvarsler",
diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts
index 2a20cd57e..76a47ea26 100644
--- a/packages/app/src/i18n/pl.ts
+++ b/packages/app/src/i18n/pl.ts
@@ -514,9 +514,6 @@ export const dict = {
"settings.section.server": "Serwer",
"settings.tab.general": "Ogólne",
"settings.tab.shortcuts": "Skróty",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "Wygląd",
"settings.general.section.notifications": "Powiadomienia systemowe",
diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts
index 698c8db58..e83ce3761 100644
--- a/packages/app/src/i18n/ru.ts
+++ b/packages/app/src/i18n/ru.ts
@@ -517,9 +517,6 @@ export const dict = {
"settings.section.server": "Сервер",
"settings.tab.general": "Основные",
"settings.tab.shortcuts": "Горячие клавиши",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "Внешний вид",
"settings.general.section.notifications": "Системные уведомления",
diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts
index 161f37f3b..2be19d15b 100644
--- a/packages/app/src/i18n/th.ts
+++ b/packages/app/src/i18n/th.ts
@@ -516,9 +516,6 @@ export const dict = {
"settings.section.server": "เซิร์ฟเวอร์",
"settings.tab.general": "ทั่วไป",
"settings.tab.shortcuts": "ทางลัด",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "รูปลักษณ์",
"settings.general.section.notifications": "การแจ้งเตือนระบบ",
diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts
index a2931cf98..a48f9e549 100644
--- a/packages/app/src/i18n/zh.ts
+++ b/packages/app/src/i18n/zh.ts
@@ -548,9 +548,6 @@ export const dict = {
"settings.section.server": "服务器",
"settings.tab.general": "通用",
"settings.tab.shortcuts": "快捷键",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "外观",
"settings.general.section.notifications": "系统通知",
diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts
index cae0c75b4..60363fc99 100644
--- a/packages/app/src/i18n/zht.ts
+++ b/packages/app/src/i18n/zht.ts
@@ -545,9 +545,6 @@ export const dict = {
"settings.section.server": "伺服器",
"settings.tab.general": "一般",
"settings.tab.shortcuts": "快速鍵",
- "settings.desktop.section.wsl": "WSL",
- "settings.desktop.wsl.title": "WSL integration",
- "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "外觀",
"settings.general.section.notifications": "系統通知",
diff --git a/packages/desktop/AGENTS.md b/packages/desktop/AGENTS.md
deleted file mode 100644
index 3839db1a9..000000000
--- a/packages/desktop/AGENTS.md
+++ /dev/null
@@ -1,4 +0,0 @@
-# Desktop package notes
-
-- Never call `invoke` manually in this package.
-- Use the generated bindings in `packages/desktop/src/bindings.ts` for core commands/events.
diff --git a/packages/desktop/src-tauri/src/cli.rs b/packages/desktop/src-tauri/src/cli.rs
index b9e1ed4bd..48d9276a1 100644
--- a/packages/desktop/src-tauri/src/cli.rs
+++ b/packages/desktop/src-tauri/src/cli.rs
@@ -3,11 +3,8 @@ use tauri_plugin_shell::{
ShellExt,
process::{Command, CommandChild, CommandEvent, TerminatedPayload},
};
-use tauri_plugin_store::StoreExt;
use tokio::sync::oneshot;
-use crate::constants::{SETTINGS_STORE, WSL_ENABLED_KEY};
-
const CLI_INSTALL_DIR: &str = ".opencode/bin";
const CLI_BINARY_NAME: &str = "opencode";
@@ -23,7 +20,7 @@ pub struct Config {
}
pub async fn get_config(app: &AppHandle) -> Option<Config> {
- create_command(app, "debug config", &[])
+ create_command(app, "debug config")
.output()
.await
.inspect_err(|e| tracing::warn!("Failed to read OC config: {e}"))
@@ -152,106 +149,25 @@ fn get_user_shell() -> String {
std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string())
}
-fn is_wsl_enabled(app: &tauri::AppHandle) -> bool {
- let Ok(store) = app.store(SETTINGS_STORE) else {
- return false;
- };
-
- store
- .get(WSL_ENABLED_KEY)
- .as_ref()
- .and_then(|value| value.as_bool())
- .unwrap_or(false)
-}
-
-fn shell_escape(input: &str) -> String {
- if input.is_empty() {
- return "''".to_string();
- }
-
- let mut escaped = String::from("'");
- escaped.push_str(&input.replace("'", "'\"'\"'"));
- escaped.push('\'');
- escaped
-}
-
-pub fn create_command(app: &tauri::AppHandle, args: &str, extra_env: &[(&str, String)]) -> Command {
+pub fn create_command(app: &tauri::AppHandle, args: &str) -> Command {
let state_dir = app
.path()
.resolve("", BaseDirectory::AppLocalData)
.expect("Failed to resolve app local data dir");
- let mut envs = vec![
- (
- "OPENCODE_EXPERIMENTAL_ICON_DISCOVERY".to_string(),
- "true".to_string(),
- ),
- (
- "OPENCODE_EXPERIMENTAL_FILEWATCHER".to_string(),
- "true".to_string(),
- ),
- ("OPENCODE_CLIENT".to_string(), "desktop".to_string()),
- (
- "XDG_STATE_HOME".to_string(),
- state_dir.to_string_lossy().to_string(),
- ),
- ];
- envs.extend(
- extra_env
- .iter()
- .map(|(key, value)| (key.to_string(), value.clone())),
- );
-
- if cfg!(windows) {
- if is_wsl_enabled(app) {
- tracing::info!("WSL is enabled, spawning CLI server in WSL");
- let version = app.package_info().version.to_string();
- let mut script = vec![
- "set -e".to_string(),
- "BIN=\"$HOME/.opencode/bin/opencode\"".to_string(),
- "if [ ! -x \"$BIN\" ]; then".to_string(),
- format!(
- " curl -fsSL https://opencode.ai/install | bash -s -- --version {} --no-modify-path",
- shell_escape(&version)
- ),
- "fi".to_string(),
- ];
-
- let mut env_prefix = vec![
- "OPENCODE_EXPERIMENTAL_ICON_DISCOVERY=true".to_string(),
- "OPENCODE_EXPERIMENTAL_FILEWATCHER=true".to_string(),
- "OPENCODE_CLIENT=desktop".to_string(),
- "XDG_STATE_HOME=\"$HOME/.local/state\"".to_string(),
- ];
- env_prefix.extend(
- envs.iter()
- .filter(|(key, _)| key != "OPENCODE_EXPERIMENTAL_ICON_DISCOVERY")
- .filter(|(key, _)| key != "OPENCODE_EXPERIMENTAL_FILEWATCHER")
- .filter(|(key, _)| key != "OPENCODE_CLIENT")
- .filter(|(key, _)| key != "XDG_STATE_HOME")
- .map(|(key, value)| format!("{}={}", key, shell_escape(value))),
- );
-
- script.push(format!("{} exec \"$BIN\" {}", env_prefix.join(" "), args));
-
- return app
- .shell()
- .command("wsl")
- .args(["-e", "bash", "-lc", &script.join("\n")]);
- } else {
- let mut cmd = app
- .shell()
- .sidecar("opencode-cli")
- .unwrap()
- .args(args.split_whitespace());
-
- for (key, value) in envs {
- cmd = cmd.env(key, value);
- }
-
- return cmd;
- }
- } else {
+ #[cfg(target_os = "windows")]
+ return app
+ .shell()
+ .sidecar("opencode-cli")
+ .unwrap()
+ .args(args.split_whitespace())
+ .env("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", "true")
+ .env("OPENCODE_EXPERIMENTAL_FILEWATCHER", "true")
+ .env("OPENCODE_CLIENT", "desktop")
+ .env("XDG_STATE_HOME", &state_dir);
+
+ #[cfg(not(target_os = "windows"))]
+ return {
let sidecar = get_sidecar_path(app);
let shell = get_user_shell();
@@ -261,14 +177,14 @@ pub fn create_command(app: &tauri::AppHandle, args: &str, extra_env: &[(&str, St
format!("\"{}\" {}", sidecar.display(), args)
};
- let mut cmd = app.shell().command(&shell).args(["-il", "-c", &cmd]);
-
- for (key, value) in envs {
- cmd = cmd.env(key, value);
- }
-
- cmd
- }
+ app.shell()
+ .command(&shell)
+ .env("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", "true")
+ .env("OPENCODE_EXPERIMENTAL_FILEWATCHER", "true")
+ .env("OPENCODE_CLIENT", "desktop")
+ .env("XDG_STATE_HOME", &state_dir)
+ .args(["-il", "-c", &cmd])
+ };
}
pub fn serve(
@@ -281,16 +197,12 @@ pub fn serve(
tracing::info!(port, "Spawning sidecar");
- let envs = [
- ("OPENCODE_SERVER_USERNAME", "opencode".to_string()),
- ("OPENCODE_SERVER_PASSWORD", password.to_string()),
- ];
-
let (mut rx, child) = create_command(
app,
format!("--print-logs --log-level WARN serve --hostname {hostname} --port {port}").as_str(),
- &envs,
)
+ .env("OPENCODE_SERVER_USERNAME", "opencode")
+ .env("OPENCODE_SERVER_PASSWORD", password)
.spawn()
.expect("Failed to spawn opencode");
diff --git a/packages/desktop/src-tauri/src/constants.rs b/packages/desktop/src-tauri/src/constants.rs
index 9d50d00e2..a75495b54 100644
--- a/packages/desktop/src-tauri/src/constants.rs
+++ b/packages/desktop/src-tauri/src/constants.rs
@@ -2,7 +2,6 @@ use tauri_plugin_window_state::StateFlags;
pub const SETTINGS_STORE: &str = "opencode.settings.dat";
pub const DEFAULT_SERVER_URL_KEY: &str = "defaultServerUrl";
-pub const WSL_ENABLED_KEY: &str = "wslEnabled";
pub const UPDATER_ENABLED: bool = option_env!("TAURI_SIGNING_PRIVATE_KEY").is_some();
pub fn window_state_flags() -> StateFlags {
diff --git a/packages/desktop/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs
index 2c570b7a7..6a8e5e5dc 100644
--- a/packages/desktop/src-tauri/src/lib.rs
+++ b/packages/desktop/src-tauri/src/lib.rs
@@ -52,13 +52,6 @@ enum InitStep {
Done,
}
-#[derive(serde::Deserialize, specta::Type)]
-#[serde(rename_all = "snake_case")]
-enum WslPathMode {
- Windows,
- Linux,
-}
-
struct InitState {
current: watch::Receiver<InitStep>,
}
@@ -627,50 +620,32 @@ fn check_linux_app(app_name: &str) -> bool {
return true;
}
-#[tauri::command]
-#[specta::specta]
-fn wsl_path(path: String, mode: Option<WslPathMode>) -> Result<String, String> {
- if !cfg!(windows) {
- return Ok(path);
- }
-
- let flag = match mode.unwrap_or(WslPathMode::Linux) {
- WslPathMode::Windows => "-w",
- WslPathMode::Linux => "-u",
- };
-
- let output = if path.starts_with('~') {
- let suffix = path.strip_prefix('~').unwrap_or("");
- let escaped = suffix.replace('"', "\\\"");
- let cmd = format!("wslpath {flag} \"$HOME{escaped}\"");
- Command::new("wsl")
- .args(["-e", "sh", "-lc", &cmd])
- .output()
- .map_err(|e| format!("Failed to run wslpath: {e}"))?
- } else {
- Command::new("wsl")
- .args(["-e", "wslpath", flag, &path])
- .output()
- .map_err(|e| format!("Failed to run wslpath: {e}"))?
- };
-
- if !output.status.success() {
- let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
- if stderr.is_empty() {
- return Err("wslpath failed".to_string());
- }
- return Err(stderr);
- }
-
- Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
-}
-
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
- let builder = make_specta_builder();
+ let builder = tauri_specta::Builder::<tauri::Wry>::new()
+ // Then register them (separated by a comma)
+ .commands(tauri_specta::collect_commands![
+ kill_sidecar,
+ cli::install_cli,
+ await_initialization,
+ server::get_default_server_url,
+ server::set_default_server_url,
+ get_display_backend,
+ set_display_backend,
+ markdown::parse_markdown_command,
+ check_app_exists,
+ resolve_app_path
+ ])
+ .events(tauri_specta::collect_events![LoadingWindowComplete])
+ .error_handling(tauri_specta::ErrorHandlingMode::Throw);
#[cfg(debug_assertions)] // <- Only export on non-release builds
- export_types(&builder);
+ builder
+ .export(
+ specta_typescript::Typescript::default(),
+ "../src/bindings.ts",
+ )
+ .expect("Failed to export typescript bindings");
#[cfg(all(target_os = "macos", not(debug_assertions)))]
let _ = std::process::Command::new("killall")
@@ -737,44 +712,6 @@ pub fn run() {
});
}
-fn make_specta_builder() -> tauri_specta::Builder<tauri::Wry> {
- tauri_specta::Builder::<tauri::Wry>::new()
- // Then register them (separated by a comma)
- .commands(tauri_specta::collect_commands![
- kill_sidecar,
- cli::install_cli,
- await_initialization,
- server::get_default_server_url,
- server::set_default_server_url,
- server::get_wsl_config,
- server::set_wsl_config,
- get_display_backend,
- set_display_backend,
- markdown::parse_markdown_command,
- check_app_exists,
- wsl_path,
- resolve_app_path
- ])
- .events(tauri_specta::collect_events![LoadingWindowComplete])
- .error_handling(tauri_specta::ErrorHandlingMode::Throw)
-}
-
-fn export_types(builder: &tauri_specta::Builder<tauri::Wry>) {
- builder
- .export(
- specta_typescript::Typescript::default(),
- "../src/bindings.ts",
- )
- .expect("Failed to export typescript bindings");
-}
-
-#[cfg(test)]
-#[test]
-fn test_export_types() {
- let builder = make_specta_builder();
- export_types(&builder);
-}
-
#[derive(tauri_specta::Event, serde::Deserialize, specta::Type)]
struct LoadingWindowComplete;
diff --git a/packages/desktop/src-tauri/src/server.rs b/packages/desktop/src-tauri/src/server.rs
index 81e0595af..81382f100 100644
--- a/packages/desktop/src-tauri/src/server.rs
+++ b/packages/desktop/src-tauri/src/server.rs
@@ -8,20 +8,9 @@ use tokio::task::JoinHandle;
use crate::{
cli,
- constants::{DEFAULT_SERVER_URL_KEY, SETTINGS_STORE, WSL_ENABLED_KEY},
+ constants::{DEFAULT_SERVER_URL_KEY, SETTINGS_STORE},
};
-#[derive(Clone, serde::Serialize, serde::Deserialize, specta::Type, Debug)]
-pub struct WslConfig {
- pub enabled: bool,
-}
-
-impl Default for WslConfig {
- fn default() -> Self {
- Self { enabled: false }
- }
-}
-
#[tauri::command]
#[specta::specta]
pub fn get_default_server_url(app: AppHandle) -> Result<Option<String>, String> {
@@ -59,38 +48,6 @@ pub async fn set_default_server_url(app: AppHandle, url: Option<String>) -> Resu
Ok(())
}
-#[tauri::command]
-#[specta::specta]
-pub fn get_wsl_config(app: AppHandle) -> Result<WslConfig, String> {
- let store = app
- .store(SETTINGS_STORE)
- .map_err(|e| format!("Failed to open settings store: {}", e))?;
-
- let enabled = store
- .get(WSL_ENABLED_KEY)
- .as_ref()
- .and_then(|v| v.as_bool())
- .unwrap_or(false);
-
- Ok(WslConfig { enabled })
-}
-
-#[tauri::command]
-#[specta::specta]
-pub fn set_wsl_config(app: AppHandle, config: WslConfig) -> Result<(), String> {
- let store = app
- .store(SETTINGS_STORE)
- .map_err(|e| format!("Failed to open settings store: {}", e))?;
-
- store.set(WSL_ENABLED_KEY, serde_json::Value::Bool(config.enabled));
-
- store
- .save()
- .map_err(|e| format!("Failed to save settings: {}", e))?;
-
- Ok(())
-}
-
pub async fn get_saved_server_url(app: &tauri::AppHandle) -> Option<String> {
if let Some(url) = get_default_server_url(app.clone()).ok().flatten() {
tracing::info!(%url, "Using desktop-specific custom URL");
diff --git a/packages/desktop/src-tauri/src/windows.rs b/packages/desktop/src-tauri/src/windows.rs
index 2ddcb0506..cf3e399e3 100644
--- a/packages/desktop/src-tauri/src/windows.rs
+++ b/packages/desktop/src-tauri/src/windows.rs
@@ -1,7 +1,4 @@
-use crate::{
- constants::{UPDATER_ENABLED, window_state_flags},
- server::get_wsl_config,
-};
+use crate::constants::{UPDATER_ENABLED, window_state_flags};
use std::{ops::Deref, time::Duration};
use tauri::{AppHandle, Manager, Runtime, WebviewUrl, WebviewWindow, WebviewWindowBuilder};
use tauri_plugin_window_state::AppHandleExt;
@@ -25,11 +22,6 @@ impl MainWindow {
return Ok(Self(window));
}
- let wsl_enabled = get_wsl_config(app.clone())
- .ok()
- .map(|v| v.enabled)
- .unwrap_or(false);
-
let window_builder = base_window_config(
WebviewWindowBuilder::new(app, Self::LABEL, WebviewUrl::App("/".into())),
app,
@@ -44,7 +36,6 @@ impl MainWindow {
r#"
window.__OPENCODE__ ??= {{}};
window.__OPENCODE__.updaterEnabled = {UPDATER_ENABLED};
- window.__OPENCODE__.wsl = {wsl_enabled};
"#
));
diff --git a/packages/desktop/src/bindings.ts b/packages/desktop/src/bindings.ts
index 3d588a171..c7bcaba9c 100644
--- a/packages/desktop/src/bindings.ts
+++ b/packages/desktop/src/bindings.ts
@@ -10,13 +10,10 @@ export const commands = {
awaitInitialization: (events: Channel) => __TAURI_INVOKE<ServerReadyData>("await_initialization", { events }),
getDefaultServerUrl: () => __TAURI_INVOKE<string | null>("get_default_server_url"),
setDefaultServerUrl: (url: string | null) => __TAURI_INVOKE<null>("set_default_server_url", { url }),
- getWslConfig: () => __TAURI_INVOKE<WslConfig>("get_wsl_config"),
- setWslConfig: (config: WslConfig) => __TAURI_INVOKE<null>("set_wsl_config", { config }),
getDisplayBackend: () => __TAURI_INVOKE<"wayland" | "auto" | null>("get_display_backend"),
setDisplayBackend: (backend: LinuxDisplayBackend) => __TAURI_INVOKE<null>("set_display_backend", { backend }),
parseMarkdownCommand: (markdown: string) => __TAURI_INVOKE<string>("parse_markdown_command", { markdown }),
checkAppExists: (appName: string) => __TAURI_INVOKE<boolean>("check_app_exists", { appName }),
- wslPath: (path: string, mode: "windows" | "linux" | null) => __TAURI_INVOKE<string>("wsl_path", { path, mode }),
resolveAppPath: (appName: string) => __TAURI_INVOKE<string | null>("resolve_app_path", { appName }),
};
@@ -37,12 +34,6 @@ export type ServerReadyData = {
password: string | null,
};
-export type WslConfig = {
- enabled: boolean,
- };
-
-export type WslPathMode = "windows" | "linux";
-
/* Tauri Specta runtime */
function makeEvent<T>(name: string) {
const base = {
diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx
index ca603da5f..0e2fcb7fe 100644
--- a/packages/desktop/src/index.tsx
+++ b/packages/desktop/src/index.tsx
@@ -16,6 +16,7 @@ import { open as shellOpen } from "@tauri-apps/plugin-shell"
import { type as ostype } from "@tauri-apps/plugin-os"
import { check, Update } from "@tauri-apps/plugin-updater"
import { getCurrentWindow } from "@tauri-apps/api/window"
+import { invoke } from "@tauri-apps/api/core"
import { isPermissionGranted, requestPermission } from "@tauri-apps/plugin-notification"
import { relaunch } from "@tauri-apps/plugin-process"
import { AsyncStorage } from "@solid-primitives/storage"
@@ -29,7 +30,7 @@ import { UPDATER_ENABLED } from "./updater"
import { initI18n, t } from "./i18n"
import pkg from "../package.json"
import "./styles.css"
-import { commands, InitStep, type WslConfig } from "./bindings"
+import { commands, InitStep } from "./bindings"
import { Channel } from "@tauri-apps/api/core"
import { createMenu } from "./menu"
@@ -58,374 +59,338 @@ const listenForDeepLinks = async () => {
await onOpenUrl((urls) => emitDeepLinks(urls)).catch(() => undefined)
}
-const createPlatform = (password: Accessor<string | null>): Platform => {
- const os = (() => {
+const createPlatform = (password: Accessor<string | null>): Platform => ({
+ platform: "desktop",
+ os: (() => {
const type = ostype()
if (type === "macos" || type === "windows" || type === "linux") return type
return undefined
- })()
-
- const wslHome = async () => {
- if (os !== "windows" || !window.__OPENCODE__?.wsl) return undefined
- return commands.wslPath("~", "windows").catch(() => undefined)
- }
+ })(),
+ version: pkg.version,
+
+ async openDirectoryPickerDialog(opts) {
+ const result = await open({
+ directory: true,
+ multiple: opts?.multiple ?? false,
+ title: opts?.title ?? t("desktop.dialog.chooseFolder"),
+ })
+ return result
+ },
+
+ async openFilePickerDialog(opts) {
+ const result = await open({
+ directory: false,
+ multiple: opts?.multiple ?? false,
+ title: opts?.title ?? t("desktop.dialog.chooseFile"),
+ })
+ return result
+ },
- const handleWslPicker = async <T extends string | string[]>(result: T | null): Promise<T | null> => {
- if (!result || !window.__OPENCODE__?.wsl) return result
- if (Array.isArray(result)) {
- return Promise.all(result.map((path) => commands.wslPath(path, "linux").catch(() => path))) as any
+ async saveFilePickerDialog(opts) {
+ const result = await save({
+ title: opts?.title ?? t("desktop.dialog.saveFile"),
+ defaultPath: opts?.defaultPath,
+ })
+ return result
+ },
+
+ openLink(url: string) {
+ void shellOpen(url).catch(() => undefined)
+ },
+
+ async openPath(path: string, app?: string) {
+ const os = ostype()
+ if (os === "windows" && app) {
+ const resolvedApp = await commands.resolveAppPath(app)
+ return openerOpenPath(path, resolvedApp || app)
+ }
+ return openerOpenPath(path, app)
+ },
+
+ back() {
+ window.history.back()
+ },
+
+ forward() {
+ window.history.forward()
+ },
+
+ storage: (() => {
+ type StoreLike = {
+ get(key: string): Promise<string | null | undefined>
+ set(key: string, value: string): Promise<unknown>
+ delete(key: string): Promise<unknown>
+ clear(): Promise<unknown>
+ keys(): Promise<string[]>
+ length(): Promise<number>
}
- return commands.wslPath(result, "linux").catch(() => result) as any
- }
-
- return {
- platform: "desktop",
- os,
- version: pkg.version,
-
- async openDirectoryPickerDialog(opts) {
- const defaultPath = await wslHome()
- const result = await open({
- directory: true,
- multiple: opts?.multiple ?? false,
- title: opts?.title ?? t("desktop.dialog.chooseFolder"),
- defaultPath,
- })
- return await handleWslPicker(result)
- },
-
- async openFilePickerDialog(opts) {
- const result = await open({
- directory: false,
- multiple: opts?.multiple ?? false,
- title: opts?.title ?? t("desktop.dialog.chooseFile"),
- })
- return handleWslPicker(result)
- },
-
- async saveFilePickerDialog(opts) {
- const result = await save({
- title: opts?.title ?? t("desktop.dialog.saveFile"),
- defaultPath: opts?.defaultPath,
- })
- return handleWslPicker(result)
- },
-
- openLink(url: string) {
- void shellOpen(url).catch(() => undefined)
- },
- async openPath(path: string, app?: string) {
- const os = ostype()
- if (os === "windows") {
- const resolvedApp = (app && (await commands.resolveAppPath(app))) || app
- const resolvedPath = await (async () => {
- if (window.__OPENCODE__?.wsl) {
- const converted = await commands.wslPath(path, "windows").catch(() => null)
- if (converted) return converted
- }
- return path
- })()
- return openerOpenPath(resolvedPath, resolvedApp)
- }
- return openerOpenPath(path, app)
- },
-
- back() {
- window.history.back()
- },
-
- forward() {
- window.history.forward()
- },
-
- storage: (() => {
- type StoreLike = {
- get(key: string): Promise<string | null | undefined>
- set(key: string, value: string): Promise<unknown>
- delete(key: string): Promise<unknown>
- clear(): Promise<unknown>
- keys(): Promise<string[]>
- length(): Promise<number>
- }
+ const WRITE_DEBOUNCE_MS = 250
- const WRITE_DEBOUNCE_MS = 250
+ const storeCache = new Map<string, Promise<StoreLike>>()
+ const apiCache = new Map<string, AsyncStorage & { flush: () => Promise<void> }>()
+ const memoryCache = new Map<string, StoreLike>()
- const storeCache = new Map<string, Promise<StoreLike>>()
- const apiCache = new Map<string, AsyncStorage & { flush: () => Promise<void> }>()
- const memoryCache = new Map<string, StoreLike>()
+ const flushAll = async () => {
+ const apis = Array.from(apiCache.values())
+ await Promise.all(apis.map((api) => api.flush().catch(() => undefined)))
+ }
- const flushAll = async () => {
- const apis = Array.from(apiCache.values())
- await Promise.all(apis.map((api) => api.flush().catch(() => undefined)))
+ if ("addEventListener" in globalThis) {
+ const handleVisibility = () => {
+ if (document.visibilityState !== "hidden") return
+ void flushAll()
}
- if ("addEventListener" in globalThis) {
- const handleVisibility = () => {
- if (document.visibilityState !== "hidden") return
- void flushAll()
- }
+ window.addEventListener("pagehide", () => void flushAll())
+ document.addEventListener("visibilitychange", handleVisibility)
+ }
- window.addEventListener("pagehide", () => void flushAll())
- document.addEventListener("visibilitychange", handleVisibility)
+ const createMemoryStore = () => {
+ const data = new Map<string, string>()
+ const store: StoreLike = {
+ get: async (key) => data.get(key),
+ set: async (key, value) => {
+ data.set(key, value)
+ },
+ delete: async (key) => {
+ data.delete(key)
+ },
+ clear: async () => {
+ data.clear()
+ },
+ keys: async () => Array.from(data.keys()),
+ length: async () => data.size,
}
+ return store
+ }
- const createMemoryStore = () => {
- const data = new Map<string, string>()
- const store: StoreLike = {
- get: async (key) => data.get(key),
- set: async (key, value) => {
- data.set(key, value)
- },
- delete: async (key) => {
- data.delete(key)
- },
- clear: async () => {
- data.clear()
- },
- keys: async () => Array.from(data.keys()),
- length: async () => data.size,
- }
- return store
- }
+ const getStore = (name: string) => {
+ const cached = storeCache.get(name)
+ if (cached) return cached
- const getStore = (name: string) => {
- const cached = storeCache.get(name)
+ const store = Store.load(name).catch(() => {
+ const cached = memoryCache.get(name)
if (cached) return cached
- const store = Store.load(name).catch(() => {
- const cached = memoryCache.get(name)
- if (cached) return cached
+ const memory = createMemoryStore()
+ memoryCache.set(name, memory)
+ return memory
+ })
- const memory = createMemoryStore()
- memoryCache.set(name, memory)
- return memory
- })
+ storeCache.set(name, store)
+ return store
+ }
- storeCache.set(name, store)
- return store
- }
+ const createStorage = (name: string) => {
+ const pending = new Map<string, string | null>()
+ let timer: ReturnType<typeof setTimeout> | undefined
+ let flushing: Promise<void> | undefined
- const createStorage = (name: string) => {
- const pending = new Map<string, string | null>()
- let timer: ReturnType<typeof setTimeout> | undefined
- let flushing: Promise<void> | undefined
-
- const flush = async () => {
- if (flushing) return flushing
-
- flushing = (async () => {
- const store = await getStore(name)
- while (pending.size > 0) {
- const batch = Array.from(pending.entries())
- pending.clear()
- for (const [key, value] of batch) {
- if (value === null) {
- await store.delete(key).catch(() => undefined)
- } else {
- await store.set(key, value).catch(() => undefined)
- }
+ const flush = async () => {
+ if (flushing) return flushing
+
+ flushing = (async () => {
+ const store = await getStore(name)
+ while (pending.size > 0) {
+ const batch = Array.from(pending.entries())
+ pending.clear()
+ for (const [key, value] of batch) {
+ if (value === null) {
+ await store.delete(key).catch(() => undefined)
+ } else {
+ await store.set(key, value).catch(() => undefined)
}
}
- })().finally(() => {
- flushing = undefined
- })
-
- return flushing
- }
+ }
+ })().finally(() => {
+ flushing = undefined
+ })
- const schedule = () => {
- if (timer) return
- timer = setTimeout(() => {
- timer = undefined
- void flush()
- }, WRITE_DEBOUNCE_MS)
- }
+ return flushing
+ }
- const api: AsyncStorage & { flush: () => Promise<void> } = {
- flush,
- getItem: async (key: string) => {
- const next = pending.get(key)
- if (next !== undefined) return next
-
- const store = await getStore(name)
- const value = await store.get(key).catch(() => null)
- if (value === undefined) return null
- return value
- },
- setItem: async (key: string, value: string) => {
- pending.set(key, value)
- schedule()
- },
- removeItem: async (key: string) => {
- pending.set(key, null)
- schedule()
- },
- clear: async () => {
- pending.clear()
- const store = await getStore(name)
- await store.clear().catch(() => undefined)
- },
- key: async (index: number) => {
- const store = await getStore(name)
- return (await store.keys().catch(() => []))[index]
- },
- getLength: async () => {
- const store = await getStore(name)
- return await store.length().catch(() => 0)
- },
- get length() {
- return api.getLength()
- },
- }
+ const schedule = () => {
+ if (timer) return
+ timer = setTimeout(() => {
+ timer = undefined
+ void flush()
+ }, WRITE_DEBOUNCE_MS)
+ }
- return api
+ const api: AsyncStorage & { flush: () => Promise<void> } = {
+ flush,
+ getItem: async (key: string) => {
+ const next = pending.get(key)
+ if (next !== undefined) return next
+
+ const store = await getStore(name)
+ const value = await store.get(key).catch(() => null)
+ if (value === undefined) return null
+ return value
+ },
+ setItem: async (key: string, value: string) => {
+ pending.set(key, value)
+ schedule()
+ },
+ removeItem: async (key: string) => {
+ pending.set(key, null)
+ schedule()
+ },
+ clear: async () => {
+ pending.clear()
+ const store = await getStore(name)
+ await store.clear().catch(() => undefined)
+ },
+ key: async (index: number) => {
+ const store = await getStore(name)
+ return (await store.keys().catch(() => []))[index]
+ },
+ getLength: async () => {
+ const store = await getStore(name)
+ return await store.length().catch(() => 0)
+ },
+ get length() {
+ return api.getLength()
+ },
}
- return (name = "default.dat") => {
- const cached = apiCache.get(name)
- if (cached) return cached
+ return api
+ }
- const api = createStorage(name)
- apiCache.set(name, api)
- return api
- }
- })(),
-
- checkUpdate: async () => {
- if (!UPDATER_ENABLED) return { updateAvailable: false }
- const next = await check().catch(() => null)
- if (!next) return { updateAvailable: false }
- const ok = await next
- .download()
- .then(() => true)
- .catch(() => false)
- if (!ok) return { updateAvailable: false }
- update = next
- return { updateAvailable: true, version: next.version }
- },
-
- update: async () => {
- if (!UPDATER_ENABLED || !update) return
- if (ostype() === "windows") await commands.killSidecar().catch(() => undefined)
- await update.install().catch(() => undefined)
- },
-
- restart: async () => {
- await commands.killSidecar().catch(() => undefined)
- await relaunch()
- },
-
- notify: async (title, description, href) => {
- const granted = await isPermissionGranted().catch(() => false)
- const permission = granted ? "granted" : await requestPermission().catch(() => "denied")
- if (permission !== "granted") return
-
- const win = getCurrentWindow()
- const focused = await win.isFocused().catch(() => document.hasFocus())
- if (focused) return
-
- await Promise.resolve()
- .then(() => {
- const notification = new Notification(title, {
- body: description ?? "",
- icon: "https://opencode.ai/favicon-96x96-v3.png",
- })
- notification.onclick = () => {
- const win = getCurrentWindow()
- void win.show().catch(() => undefined)
- void win.unminimize().catch(() => undefined)
- void win.setFocus().catch(() => undefined)
- if (href) {
- window.history.pushState(null, "", href)
- window.dispatchEvent(new PopStateEvent("popstate"))
- }
- notification.close()
- }
+ return (name = "default.dat") => {
+ const cached = apiCache.get(name)
+ if (cached) return cached
+
+ const api = createStorage(name)
+ apiCache.set(name, api)
+ return api
+ }
+ })(),
+
+ checkUpdate: async () => {
+ if (!UPDATER_ENABLED) return { updateAvailable: false }
+ const next = await check().catch(() => null)
+ if (!next) return { updateAvailable: false }
+ const ok = await next
+ .download()
+ .then(() => true)
+ .catch(() => false)
+ if (!ok) return { updateAvailable: false }
+ update = next
+ return { updateAvailable: true, version: next.version }
+ },
+
+ update: async () => {
+ if (!UPDATER_ENABLED || !update) return
+ if (ostype() === "windows") await commands.killSidecar().catch(() => undefined)
+ await update.install().catch(() => undefined)
+ },
+
+ restart: async () => {
+ await commands.killSidecar().catch(() => undefined)
+ await relaunch()
+ },
+
+ notify: async (title, description, href) => {
+ const granted = await isPermissionGranted().catch(() => false)
+ const permission = granted ? "granted" : await requestPermission().catch(() => "denied")
+ if (permission !== "granted") return
+
+ const win = getCurrentWindow()
+ const focused = await win.isFocused().catch(() => document.hasFocus())
+ if (focused) return
+
+ await Promise.resolve()
+ .then(() => {
+ const notification = new Notification(title, {
+ body: description ?? "",
+ icon: "https://opencode.ai/favicon-96x96-v3.png",
})
- .catch(() => undefined)
- },
+ notification.onclick = () => {
+ const win = getCurrentWindow()
+ void win.show().catch(() => undefined)
+ void win.unminimize().catch(() => undefined)
+ void win.setFocus().catch(() => undefined)
+ if (href) {
+ window.history.pushState(null, "", href)
+ window.dispatchEvent(new PopStateEvent("popstate"))
+ }
+ notification.close()
+ }
+ })
+ .catch(() => undefined)
+ },
- fetch: (input, init) => {
- const pw = password()
+ fetch: (input, init) => {
+ const pw = password()
- const addHeader = (headers: Headers, password: string) => {
- headers.append("Authorization", `Basic ${btoa(`opencode:${password}`)}`)
- }
+ const addHeader = (headers: Headers, password: string) => {
+ headers.append("Authorization", `Basic ${btoa(`opencode:${password}`)}`)
+ }
- if (input instanceof Request) {
- if (pw) addHeader(input.headers, pw)
- return tauriFetch(input)
- } else {
- const headers = new Headers(init?.headers)
- if (pw) addHeader(headers, pw)
- return tauriFetch(input, {
- ...(init as any),
- headers: headers,
- })
- }
- },
-
- getWslEnabled: async () => {
- const next = await commands.getWslConfig().catch(() => null)
- if (next) return next.enabled
- return window.__OPENCODE__!.wsl ?? false
- },
-
- setWslEnabled: async (enabled) => {
- await commands.setWslConfig({ enabled })
- },
-
- getDefaultServerUrl: async () => {
- const result = await commands.getDefaultServerUrl().catch(() => null)
- return result
- },
-
- setDefaultServerUrl: async (url: string | null) => {
- await commands.setDefaultServerUrl(url)
- },
-
- getDisplayBackend: async () => {
- const result = await commands.getDisplayBackend().catch(() => null)
- return result
- },
-
- setDisplayBackend: async (backend) => {
- await commands.setDisplayBackend(backend)
- },
-
- parseMarkdown: (markdown: string) => commands.parseMarkdownCommand(markdown),
-
- webviewZoom,
-
- checkAppExists: async (appName: string) => {
- return commands.checkAppExists(appName)
- },
-
- async readClipboardImage() {
- const image = await readImage().catch(() => null)
- if (!image) return null
- const bytes = await image.rgba().catch(() => null)
- if (!bytes || bytes.length === 0) return null
- const size = await image.size().catch(() => null)
- if (!size) return null
- const canvas = document.createElement("canvas")
- canvas.width = size.width
- canvas.height = size.height
- const ctx = canvas.getContext("2d")
- if (!ctx) return null
- const imageData = ctx.createImageData(size.width, size.height)
- imageData.data.set(bytes)
- ctx.putImageData(imageData, 0, 0)
- return new Promise<File | null>((resolve) => {
- canvas.toBlob((blob) => {
- if (!blob) return resolve(null)
- resolve(new File([blob], `pasted-image-${Date.now()}.png`, { type: "image/png" }))
- }, "image/png")
+ if (input instanceof Request) {
+ if (pw) addHeader(input.headers, pw)
+ return tauriFetch(input)
+ } else {
+ const headers = new Headers(init?.headers)
+ if (pw) addHeader(headers, pw)
+ return tauriFetch(input, {
+ ...(init as any),
+ headers: headers,
})
- },
- }
-}
+ }
+ },
+
+ getDefaultServerUrl: async () => {
+ const result = await commands.getDefaultServerUrl().catch(() => null)
+ return result
+ },
+
+ setDefaultServerUrl: async (url: string | null) => {
+ await commands.setDefaultServerUrl(url)
+ },
+
+ getDisplayBackend: async () => {
+ const result = await invoke<DisplayBackend | null>("get_display_backend").catch(() => null)
+ return result
+ },
+
+ setDisplayBackend: async (backend) => {
+ await invoke("set_display_backend", { backend }).catch(() => undefined)
+ },
+
+ parseMarkdown: (markdown: string) => commands.parseMarkdownCommand(markdown),
+
+ webviewZoom,
+
+ checkAppExists: async (appName: string) => {
+ return commands.checkAppExists(appName)
+ },
+
+ async readClipboardImage() {
+ const image = await readImage().catch(() => null)
+ if (!image) return null
+ const bytes = await image.rgba().catch(() => null)
+ if (!bytes || bytes.length === 0) return null
+ const size = await image.size().catch(() => null)
+ if (!size) return null
+ const canvas = document.createElement("canvas")
+ canvas.width = size.width
+ canvas.height = size.height
+ const ctx = canvas.getContext("2d")
+ if (!ctx) return null
+ const imageData = ctx.createImageData(size.width, size.height)
+ imageData.data.set(bytes)
+ ctx.putImageData(imageData, 0, 0)
+ return new Promise<File | null>((resolve) => {
+ canvas.toBlob((blob) => {
+ if (!blob) return resolve(null)
+ resolve(new File([blob], `pasted-image-${Date.now()}.png`, { type: "image/png" }))
+ }, "image/png")
+ })
+ },
+})
let menuTrigger = null as null | ((id: string) => void)
createMenu((id) => {
@@ -435,7 +400,6 @@ void listenForDeepLinks()
render(() => {
const [serverPassword, setServerPassword] = createSignal<string | null>(null)
-
const platform = createPlatform(() => serverPassword())
function handleClick(e: MouseEvent) {