diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 5bbe86e209..e49b725a19 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[] } + __OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string; deepLinks?: string[]; wsl?: boolean } } } diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 5835f2afad..72135c342e 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -367,10 +367,10 @@ export const SettingsGeneral: Component = () => { - + {(_) => { - const [valueResource, actions] = createResource(() => platform.getWslConfig?.()) - const value = () => (valueResource.state === "pending" ? undefined : valueResource.latest) + const [enabledResource, actions] = createResource(() => platform.getWslEnabled?.()) + const enabled = () => (enabledResource.state === "pending" ? undefined : enabledResource.latest) return (
@@ -383,11 +383,9 @@ export const SettingsGeneral: Component = () => { >
- platform.setWslConfig?.({ enabled: checked })?.finally(() => actions.refetch()) - } + checked={enabled() ?? false} + disabled={enabledResource.state === "pending"} + onChange={(checked) => platform.setWslEnabled?.(checked)?.finally(() => actions.refetch())} />
diff --git a/packages/app/src/context/platform.tsx b/packages/app/src/context/platform.tsx index f753f637c8..63066f3e87 100644 --- a/packages/app/src/context/platform.tsx +++ b/packages/app/src/context/platform.tsx @@ -61,10 +61,10 @@ export type Platform = { setDefaultServerUrl?(url: string | null): Promise | void /** Get the configured WSL integration (desktop only) */ - getWslConfig?(): Promise<{ enabled: boolean } | null> | { enabled: boolean } | null + getWslEnabled?(): Promise /** Set the configured WSL integration (desktop only) */ - setWslConfig?(config: { enabled: boolean }): Promise | void + setWslEnabled?(config: boolean): Promise | void /** Get the preferred display backend (desktop only) */ getDisplayBackend?(): Promise | DisplayBackend | null diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx index bcdd895258..d5b9573338 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -46,8 +46,7 @@ export default function Home() { } } - const wslEnabled = platform.wslEnabled?.() === true - if (platform.openDirectoryPickerDialog && server.isLocal() && !wslEnabled) { + if (platform.openDirectoryPickerDialog && server.isLocal() && !(await platform.getWslEnabled?.())) { const result = await platform.openDirectoryPickerDialog?.({ title: language.t("command.project.open"), multiple: true, diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 96d05989ba..0351c4c0c5 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1182,8 +1182,7 @@ export default function Layout(props: ParentProps) { } } - const wslEnabled = platform.wslEnabled?.() === true - if (platform.openDirectoryPickerDialog && server.isLocal() && !wslEnabled) { + if (platform.openDirectoryPickerDialog && server.isLocal() && !(await platform.getWslEnabled?.())) { const result = await platform.openDirectoryPickerDialog?.({ title: language.t("command.project.open"), multiple: true, diff --git a/packages/desktop/src-tauri/src/cli.rs b/packages/desktop/src-tauri/src/cli.rs index 9f029cc1b1..121197dea5 100644 --- a/packages/desktop/src-tauri/src/cli.rs +++ b/packages/desktop/src-tauri/src/cli.rs @@ -204,57 +204,55 @@ pub fn create_command(app: &tauri::AppHandle, args: &str, extra_env: &[(&str, St .map(|(key, value)| (key.to_string(), value.clone())), ); - #[cfg(target_os = "windows")] - if is_wsl_enabled(app) { - 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(), - ]; + if cfg!(windows) { + if is_wsl_enabled(app) { + 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))), - ); + 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)); + script.push(format!("{} exec \"$BIN\" {}", env_prefix.join(" "), args)); - return app - .shell() - .command("wsl") - .args(["-e", "bash", "-lc", &script.join("\n")]); - } + 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()); - let mut cmd = app - .shell() - .sidecar("opencode-cli") - .unwrap() - .args(args.split_whitespace()); + for (key, value) in envs { + cmd = cmd.env(key, value); + } - for (key, value) in envs { - cmd = cmd.env(key, value); - } - - return cmd; - - #[cfg(not(target_os = "windows"))] - return { + return cmd; + } + } else { let sidecar = get_sidecar_path(app); let shell = get_user_shell(); @@ -271,7 +269,7 @@ pub fn create_command(app: &tauri::AppHandle, args: &str, extra_env: &[(&str, St } cmd - }; + } } pub fn serve(app: &AppHandle, hostname: &str, port: u32, password: &str) -> CommandChild { diff --git a/packages/desktop/src-tauri/src/windows.rs b/packages/desktop/src-tauri/src/windows.rs index cf3e399e34..2ddcb0506d 100644 --- a/packages/desktop/src-tauri/src/windows.rs +++ b/packages/desktop/src-tauri/src/windows.rs @@ -1,4 +1,7 @@ -use crate::constants::{UPDATER_ENABLED, window_state_flags}; +use crate::{ + constants::{UPDATER_ENABLED, window_state_flags}, + server::get_wsl_config, +}; use std::{ops::Deref, time::Duration}; use tauri::{AppHandle, Manager, Runtime, WebviewUrl, WebviewWindow, WebviewWindowBuilder}; use tauri_plugin_window_state::AppHandleExt; @@ -22,6 +25,11 @@ 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, @@ -36,6 +44,7 @@ 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 2fe0d2d2ab..4c1e5b2d64 100644 --- a/packages/desktop/src/bindings.ts +++ b/packages/desktop/src/bindings.ts @@ -12,8 +12,8 @@ export const commands = { setDefaultServerUrl: (url: string | null) => __TAURI_INVOKE("set_default_server_url", { url }), getWslConfig: () => __TAURI_INVOKE("get_wsl_config"), setWslConfig: (config: WslConfig) => __TAURI_INVOKE("set_wsl_config", { config }), - getDisplayBackend: () => __TAURI_INVOKE("get_display_backend"), - setDisplayBackend: (backend: DisplayBackend) => __TAURI_INVOKE("set_display_backend", { backend }), + getDisplayBackend: () => __TAURI_INVOKE<"wayland" | "auto" | null>("get_display_backend"), + setDisplayBackend: (backend: LinuxDisplayBackend) => __TAURI_INVOKE("set_display_backend", { backend }), parseMarkdownCommand: (markdown: string) => __TAURI_INVOKE("parse_markdown_command", { markdown }), checkAppExists: (appName: string) => __TAURI_INVOKE("check_app_exists", { appName }), wslPath: (path: string, mode: "windows" | "linux" | null) => __TAURI_INVOKE("wsl_path", { path, mode }), @@ -25,14 +25,10 @@ export const events = { }; /* Types */ -export type WslConfig = { - enabled: boolean, - }; - -export type DisplayBackend = "wayland" | "auto"; - export type InitStep = { phase: "server_waiting" } | { phase: "sqlite_waiting" } | { phase: "done" }; +export type LinuxDisplayBackend = "wayland" | "auto"; + export type LoadingWindowComplete = null; export type ServerReadyData = { @@ -40,6 +36,10 @@ export type ServerReadyData = { password: string | null, }; +export type WslConfig = { + enabled: boolean, + }; + export type WslPathMode = "windows" | "linux"; /* Tauri Specta runtime */ @@ -58,3 +58,4 @@ function makeEvent(name: string) { return Object.assign(fn, base); } + diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx index 4990731b06..3b636cf966 100644 --- a/packages/desktop/src/index.tsx +++ b/packages/desktop/src/index.tsx @@ -58,13 +58,10 @@ const listenForDeepLinks = async () => { await onOpenUrl((urls) => emitDeepLinks(urls)).catch(() => undefined) } -const defaultWsl: WslConfig = { enabled: false } - const createPlatform = ( password: Accessor, - wsl: Accessor, + wsl: Accessor, setWsl: (next: WslConfig) => void, - fetchWsl: () => Promise, ): Platform => { const os = (() => { const type = ostype() @@ -78,7 +75,6 @@ const createPlatform = ( version: pkg.version, async openDirectoryPickerDialog(opts) { - if (wsl().enabled) return null const result = await open({ directory: true, multiple: opts?.multiple ?? false, @@ -109,7 +105,7 @@ const createPlatform = ( }, async openPath(path: string, app?: string) { - if (wsl().enabled && os === "windows") { + if (wsl() && os === "windows") { const converted = await commands.wslPath(path, "windows").catch(() => null) return openerOpenPath(converted && converted.length > 0 ? converted : path, app) } @@ -331,18 +327,37 @@ const createPlatform = ( .catch(() => undefined) }, - getWslConfig: async () => { - const next = await fetchWsl() - if (next) return next + fetch: (input, init) => { + const pw = 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 wsl() }, - setWslConfig: async (config: WslConfig) => { - setWsl(config) - await commands.setWslConfig(config).catch(() => undefined) + setWslEnabled: async (enabled) => { + await commands.setWslConfig({ enabled }) }, - wslEnabled: () => wsl().enabled, + wslEnabled: () => wsl(), getDefaultServerUrl: async () => { const result = await commands.getDefaultServerUrl().catch(() => null) @@ -359,7 +374,7 @@ const createPlatform = ( }, setDisplayBackend: async (backend) => { - await commands.setDisplayBackend(backend).catch(() => undefined) + await commands.setDisplayBackend(backend) }, parseMarkdown: (markdown: string) => commands.parseMarkdownCommand(markdown), @@ -403,16 +418,9 @@ void listenForDeepLinks() render(() => { const [serverPassword, setServerPassword] = createSignal(null) - const [wsl, setWsl] = createSignal(defaultWsl) + const [wsl, setWsl] = createSignal(window.__OPENCODE__?.wsl ?? false) - const fetchWsl = async () => { - const next = await commands.getWslConfig().catch(() => null) - if (!next) return null - setWsl(next) - return next - } - - const platform = createPlatform(() => serverPassword(), wsl, setWsl, fetchWsl) + const platform = createPlatform(() => serverPassword(), wsl, setWsl) function handleClick(e: MouseEvent) { const link = (e.target as HTMLElement).closest("a.external-link") as HTMLAnchorElement | null @@ -424,7 +432,6 @@ render(() => { onMount(() => { document.addEventListener("click", handleClick) - void fetchWsl() onCleanup(() => { document.removeEventListener("click", handleClick) })