diff options
| author | Adam <[email protected]> | 2026-02-11 08:51:41 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-02-11 08:51:41 -0600 |
| commit | 2e8082dd21873928dbf6486fd004b70aff880bb5 (patch) | |
| tree | d249ef4a676ff9cfd50f80fb192405ad6669c397 /packages/desktop/src | |
| parent | a52fe28246f05683b7a52782eafa038c899808db (diff) | |
| download | opencode-2e8082dd21873928dbf6486fd004b70aff880bb5.tar.gz opencode-2e8082dd21873928dbf6486fd004b70aff880bb5.zip | |
Revert "feat(desktop): add WSL backend mode (#12914)"
This reverts commit 213a87234dd7579873787070e5f8ad98eb88bce8.
Diffstat (limited to 'packages/desktop/src')
| -rw-r--r-- | packages/desktop/src/bindings.ts | 9 | ||||
| -rw-r--r-- | packages/desktop/src/index.tsx | 638 |
2 files changed, 301 insertions, 346 deletions
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) { |
