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..9c65ace3 100644
--- a/openless-all/app/src/i18n/en.ts
+++ b/openless-all/app/src/i18n/en.ts
@@ -266,6 +266,9 @@ 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.',
+ startupAtBootError: 'Failed to toggle launch at login: {{message}}',
},
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..402961ab 100644
--- a/openless-all/app/src/i18n/zh-CN.ts
+++ b/openless-all/app/src/i18n/zh-CN.ts
@@ -264,6 +264,9 @@ 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。',
+ startupAtBootError: '开机自启切换失败:{{message}}',
},
providers: {
llmTitle: 'LLM 模型(润色)',
diff --git a/openless-all/app/src/pages/Settings.tsx b/openless-all/app/src/pages/Settings.tsx
index 2e60c839..7795c0e6 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() {
>