From e5608db21e2d950ff7325f0a1fa1fc488a7801cb Mon Sep 17 00:00:00 2001 From: baiqing Date: Mon, 4 May 2026 07:07:55 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(autostart):=20macOS=20/=20Linux=20/=20?= =?UTF-8?q?Windows=20=E4=B8=89=E5=B9=B3=E5=8F=B0=E5=BC=80=E6=9C=BA?= =?UTF-8?q?=E8=87=AA=E5=90=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 接入官方 tauri-plugin-autostart: - macOS:~/Library/LaunchAgents/com.openless.app.plist (LaunchAgent) - Linux:~/.config/autostart/openless.desktop (XDG) - Windows:HKCU\Software\Microsoft\Windows\CurrentVersion\Run (per-user, 不需要 admin) 设置页 → 录制 区追加「开机自启」Toggle;状态以 OS 为唯一真相, 直接调插件 isEnabled / enable / disable,不进 prefs,避免缓存 与系统设置不一致。 Closes #194 --- openless-all/app/package-lock.json | 14 +++- openless-all/app/package.json | 1 + openless-all/app/src-tauri/Cargo.lock | 80 +++++++++++++++++-- openless-all/app/src-tauri/Cargo.toml | 1 + .../app/src-tauri/capabilities/default.json | 3 +- openless-all/app/src-tauri/src/lib.rs | 7 ++ openless-all/app/src/i18n/en.ts | 2 + openless-all/app/src/i18n/zh-CN.ts | 2 + openless-all/app/src/pages/Settings.tsx | 48 +++++++++++ 9 files changed, 148 insertions(+), 10 deletions(-) diff --git a/openless-all/app/package-lock.json b/openless-all/app/package-lock.json index 15112f79..a2085ffa 100644 --- a/openless-all/app/package-lock.json +++ b/openless-all/app/package-lock.json @@ -1,14 +1,15 @@ { "name": "openless-app", - "version": "1.2.8", + "version": "1.2.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openless-app", - "version": "1.2.8", + "version": "1.2.13", "dependencies": { "@tauri-apps/api": "^2.1.1", + "@tauri-apps/plugin-autostart": "^2.5.1", "@tauri-apps/plugin-shell": "^2.0.1", "@tauri-apps/plugin-updater": "^2.10.1", "i18next": "^26.0.8", @@ -1342,6 +1343,15 @@ "node": ">= 10" } }, + "node_modules/@tauri-apps/plugin-autostart": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-autostart/-/plugin-autostart-2.5.1.tgz", + "integrity": "sha512-zS/xx7yzveCcotkA+8TqkI2lysmG2wvQXv2HGAVExITmnFfHAdj1arGsbbfs3o6EktRHf6l34pJxc3YGG2mg7w==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, "node_modules/@tauri-apps/plugin-shell": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.5.tgz", diff --git a/openless-all/app/package.json b/openless-all/app/package.json index 8bd82c33..0a243db6 100644 --- a/openless-all/app/package.json +++ b/openless-all/app/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@tauri-apps/api": "^2.1.1", + "@tauri-apps/plugin-autostart": "^2.5.1", "@tauri-apps/plugin-shell": "^2.0.1", "@tauri-apps/plugin-updater": "^2.10.1", "i18next": "^26.0.8", diff --git a/openless-all/app/src-tauri/Cargo.lock b/openless-all/app/src-tauri/Cargo.lock index a40216c3..cf0dc120 100644 --- a/openless-all/app/src-tauri/Cargo.lock +++ b/openless-all/app/src-tauri/Cargo.lock @@ -308,6 +308,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "auto-launch" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f012b8cc0c850f34117ec8252a44418f2e34a2cf501de89e29b241ae5f79471" +dependencies = [ + "dirs 4.0.0", + "thiserror 1.0.69", + "winreg 0.10.1", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -1090,13 +1101,33 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] + [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", ] [[package]] @@ -1107,7 +1138,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.5.2", "windows-sys 0.61.2", ] @@ -3278,6 +3309,7 @@ dependencies = [ "simplelog", "tauri", "tauri-build", + "tauri-plugin-autostart", "tauri-plugin-shell", "tauri-plugin-single-instance", "tauri-plugin-updater", @@ -4042,6 +4074,17 @@ dependencies = [ "bitflags 2.11.1", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -5026,7 +5069,7 @@ dependencies = [ "anyhow", "bytes", "cookie", - "dirs", + "dirs 6.0.0", "dunce", "embed_plist", "getrandom 0.3.4", @@ -5076,7 +5119,7 @@ checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d" dependencies = [ "anyhow", "cargo_toml", - "dirs", + "dirs 6.0.0", "glob", "heck 0.5.0", "json-patch", @@ -5148,6 +5191,20 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-autostart" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459383cebc193cdd03d1ba4acc40f2c408a7abce419d64bdcd2d745bc2886f70" +dependencies = [ + "auto-launch", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", +] + [[package]] name = "tauri-plugin-shell" version = "2.3.5" @@ -5191,7 +5248,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af" dependencies = [ "base64 0.22.1", - "dirs", + "dirs 6.0.0", "flate2", "futures-util", "http", @@ -5742,7 +5799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" dependencies = [ "crossbeam-channel", - "dirs", + "dirs 6.0.0", "libappindicator", "muda", "objc2 0.6.4", @@ -6927,6 +6984,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "winreg" version = "0.52.0" @@ -7057,7 +7123,7 @@ dependencies = [ "block2 0.6.2", "cookie", "crossbeam-channel", - "dirs", + "dirs 6.0.0", "dom_query", "dpi", "dunce", diff --git a/openless-all/app/src-tauri/Cargo.toml b/openless-all/app/src-tauri/Cargo.toml index 9df49b38..ef66d79c 100644 --- a/openless-all/app/src-tauri/Cargo.toml +++ b/openless-all/app/src-tauri/Cargo.toml @@ -18,6 +18,7 @@ tauri = { version = "2", features = ["macos-private-api", "tray-icon"] } tauri-plugin-shell = "2" tauri-plugin-updater = "2" tauri-plugin-single-instance = "2" +tauri-plugin-autostart = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["full"] } diff --git a/openless-all/app/src-tauri/capabilities/default.json b/openless-all/app/src-tauri/capabilities/default.json index a17c96af..b04dca4a 100644 --- a/openless-all/app/src-tauri/capabilities/default.json +++ b/openless-all/app/src-tauri/capabilities/default.json @@ -16,6 +16,7 @@ "core:webview:default", "core:event:default", "shell:allow-open", - "updater:default" + "updater:default", + "autostart:default" ] } diff --git a/openless-all/app/src-tauri/src/lib.rs b/openless-all/app/src-tauri/src/lib.rs index 539f224d..8323a686 100644 --- a/openless-all/app/src-tauri/src/lib.rs +++ b/openless-all/app/src-tauri/src/lib.rs @@ -56,6 +56,13 @@ pub fn run() { })) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_updater::Builder::new().build()) + // 跨平台开机自启:mac 写 LaunchAgent plist,linux 写 ~/.config/autostart/*.desktop, + // windows 写 HKCU\Software\Microsoft\Windows\CurrentVersion\Run。前端 toggle 直接 + // 调插件 isEnabled / enable / disable,不维持本地 prefs,让 OS 当唯一真相。issue #194。 + .plugin(tauri_plugin_autostart::init( + tauri_plugin_autostart::MacosLauncher::LaunchAgent, + None, + )) .manage(coordinator.clone()) .setup(move |app| { // Capsule 启动时定位到屏幕底部居中并隐藏;coordinator 按需显示。 diff --git a/openless-all/app/src/i18n/en.ts b/openless-all/app/src/i18n/en.ts index 157a63e5..8b408236 100644 --- a/openless-all/app/src/i18n/en.ts +++ b/openless-all/app/src/i18n/en.ts @@ -266,6 +266,8 @@ export const en: typeof zhCN = { capsuleDesc: 'Show a translucent capsule at the bottom of the screen while recording / transcribing.', restoreClipboardLabel: 'Restore clipboard after insert', restoreClipboardDesc: 'Windows / Linux only: restore your original clipboard after a successful paste (default on). Turn off to keep the dictation text in the clipboard so you can manually Ctrl+V if the simulated paste did not actually land. See issue #111.', + startupAtBoot: 'Launch at login', + startupAtBootDesc: 'Start OpenLess automatically when you sign in. macOS uses a LaunchAgent, Linux writes ~/.config/autostart, Windows writes HKCU\\Run (no admin required). See issue #194.', }, providers: { llmTitle: 'LLM (polishing)', diff --git a/openless-all/app/src/i18n/zh-CN.ts b/openless-all/app/src/i18n/zh-CN.ts index c98358bb..825d56d8 100644 --- a/openless-all/app/src/i18n/zh-CN.ts +++ b/openless-all/app/src/i18n/zh-CN.ts @@ -264,6 +264,8 @@ export const zhCN = { capsuleDesc: '录音 / 转写时在屏幕底部显示半透明胶囊。', restoreClipboardLabel: '插入后恢复剪贴板', restoreClipboardDesc: '仅 Windows / Linux:粘贴成功后恢复你原来的剪贴板内容(默认开)。关掉就把听写文本留在剪贴板,模拟粘贴没真正落地时可以手动 Ctrl+V 找回。详见 issue #111。', + startupAtBoot: '开机自启', + startupAtBootDesc: '登录后自动启动 OpenLess。macOS 写 LaunchAgent,Linux 写 ~/.config/autostart,Windows 写 HKCU\\Run(不需要管理员)。详见 issue #194。', }, providers: { llmTitle: 'LLM 模型(润色)', diff --git a/openless-all/app/src/pages/Settings.tsx b/openless-all/app/src/pages/Settings.tsx index 2e60c839..de370263 100644 --- a/openless-all/app/src/pages/Settings.tsx +++ b/openless-all/app/src/pages/Settings.tsx @@ -4,6 +4,7 @@ import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; +import { disable as disableAutostart, enable as enableAutostart, isEnabled as isAutostartEnabled } from '@tauri-apps/plugin-autostart'; import { Icon } from '../components/Icon'; import { isDialogStatus, UpdateDialog, useAutoUpdate } from '../components/AutoUpdate'; import { APP_VERSION_LABEL } from '../lib/appVersion'; @@ -231,6 +232,7 @@ function RecordingSection() { > + {capability.statusHint && (
{capability.statusHint} @@ -240,6 +242,52 @@ function RecordingSection() { ); } +// 不存进 prefs:autostart 状态由 OS 持有(mac LaunchAgent plist / linux .desktop / +// windows HKCU\Run),prefs 缓存反而会与 OS 真相不一致。issue #194。 +function AutostartRow() { + const { t } = useTranslation(); + const [enabled, setEnabled] = useState(false); + const [loaded, setLoaded] = useState(false); + + useEffect(() => { + let cancelled = false; + isAutostartEnabled() + .then(v => { + if (!cancelled) { + setEnabled(v); + setLoaded(true); + } + }) + .catch(err => { + console.error('[autostart] isEnabled failed', err); + if (!cancelled) setLoaded(true); + }); + return () => { + cancelled = true; + }; + }, []); + + const onToggle = async (next: boolean) => { + setEnabled(next); + try { + if (next) await enableAutostart(); + else await disableAutostart(); + } catch (err) { + console.error('[autostart] toggle failed', err); + setEnabled(!next); + } + }; + + return ( + + {loaded ? : null} + + ); +} + function Toggle({ on, onToggle }: { on: boolean; onToggle?: (next: boolean) => void }) { return (