From 6a74e8e739c3d2db2db737b1ecb0117eb88a4129 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Mon, 18 May 2026 16:07:07 +0800
Subject: [PATCH] feat: show background activity status
---
VoiceSnapGo/frontend/src/components/settings/PermissionsPage.svelte | 25 +++++++++++-
TODO.md | 9 ++++
VoiceSnapGo/internal/permissions/permissions.go | 1
VoiceSnapGo/internal/permissions/permissions_other.go | 1
VoiceSnapGo/app.go | 2
VoiceSnapGo/build/darwin/Info.plist | 2
VoiceSnapGo/frontend/src/lib/i18n/zh.json | 7 +++
VoiceSnapGo/frontend/src/lib/i18n/en.json | 7 +++
VoiceSnapGo/internal/permissions/permissions_darwin.go | 23 +++++++++++
9 files changed, 71 insertions(+), 6 deletions(-)
diff --git a/TODO.md b/TODO.md
index a411d06..2621835 100644
--- a/TODO.md
+++ b/TODO.md
@@ -17,6 +17,15 @@
## Done
+- [2026-05-18] 将 macOS 后台活动/登录项纳入权限检查面板。
+ - 说明: 系统设置里的 “VoiceSnap Local” 是本地稳定签名证书的开发者名,下面挂着 VoiceSnap 的登录项/后台活动。
+ - 它不是右 Alt、录音、自动上屏的必需隐私权限,但会影响登录后是否自动在后台运行。
+ - 面板新增“后台活动 / 登录项”,标为“可选”,不影响核心权限总绿灯。
+ - build: `20260518.1604`
+ - 输出: `VoiceSnapGo/build/local/arm64/VoiceSnap.app`
+ - 输出: `VoiceSnapGo/build/local/arm64/VoiceSnap-2.1.1-build20260518.1604-arm64-local.dmg`
+ - 兼容输出: `VoiceSnapGo/build/local/arm64/VoiceSnap-2.1.1-arm64-local.dmg` 已替换为同一份新包。
+ - 已安装到 `/Applications/VoiceSnap.app`;启动日志验证 build `20260518.1604`。
- [2026-05-18] 修复右 Alt 事件进入但被 macOS 物理状态查询误判为未按下的问题。
- 日志显示 R-Alt `flagsChanged` 事件正常进入,`flags=0x80140`,但 `physicalDown=false` 导致 `pollDown=false`。
- 修复: 按事件 keyCode 区分左右 Alt,并用该事件的 modifier flags 判定按下/释放。
diff --git a/VoiceSnapGo/app.go b/VoiceSnapGo/app.go
index fa35b92..e33d529 100755
--- a/VoiceSnapGo/app.go
+++ b/VoiceSnapGo/app.go
@@ -27,7 +27,7 @@
const (
appVersion = "2.1.1"
- appBuild = "20260518.1554"
+ appBuild = "20260518.1604"
appDisplayVersion = appVersion + " (build " + appBuild + ")"
appName = "VoiceSnap"
diff --git a/VoiceSnapGo/build/darwin/Info.plist b/VoiceSnapGo/build/darwin/Info.plist
index d543e73..e55ed86 100755
--- a/VoiceSnapGo/build/darwin/Info.plist
+++ b/VoiceSnapGo/build/darwin/Info.plist
@@ -17,7 +17,7 @@
<key>CFBundleShortVersionString</key>
<string>2.1.1</string>
<key>CFBundleVersion</key>
- <string>20260518.1554</string>
+ <string>20260518.1604</string>
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
<key>NSMicrophoneUsageDescription</key>
diff --git a/VoiceSnapGo/frontend/src/components/settings/PermissionsPage.svelte b/VoiceSnapGo/frontend/src/components/settings/PermissionsPage.svelte
index 821bcf9..57f3850 100644
--- a/VoiceSnapGo/frontend/src/components/settings/PermissionsPage.svelte
+++ b/VoiceSnapGo/frontend/src/components/settings/PermissionsPage.svelte
@@ -10,7 +10,7 @@
required: boolean
}
- const permissionOrder = ['microphone', 'accessibility', 'inputMonitoring']
+ const permissionOrder = ['microphone', 'accessibility', 'inputMonitoring', 'backgroundActivity']
let statuses = $state<PermissionStatus[]>([])
let loading = $state(true)
@@ -56,13 +56,15 @@
}
function stateText(item: PermissionStatus) {
+ if (!item.required) return item.granted ? t('permissions.enabled') : t('permissions.disabled')
if (item.granted) return t('permissions.granted')
if (loading) return t('permissions.checking')
return t('permissions.missing')
}
function allGranted() {
- return statuses.length > 0 && statuses.every(item => item.granted)
+ const required = statuses.filter(item => item.required)
+ return required.length > 0 && required.every(item => item.granted)
}
</script>
@@ -95,7 +97,12 @@
</div>
<div class="permission-info">
<div class="permission-heading">
- <span class="permission-title">{titleFor(item.id)}</span>
+ <span class="permission-title">
+ {titleFor(item.id)}
+ {#if !item.required}
+ <span class="optional-badge">{t('permissions.optional')}</span>
+ {/if}
+ </span>
<span class="permission-state" class:granted={item.granted}>{stateText(item)}</span>
</div>
<p class="permission-desc">{descFor(item.id)}</p>
@@ -250,10 +257,22 @@
}
.permission-title {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
font-size: var(--font-size-lg);
font-weight: 650;
}
+ .optional-badge {
+ border-radius: 999px;
+ background: var(--color-bg-secondary);
+ color: var(--color-secondary-label);
+ font-size: var(--font-size-xs);
+ font-weight: 600;
+ padding: 2px 7px;
+ }
+
.permission-state {
color: var(--color-red);
font-size: var(--font-size-sm);
diff --git a/VoiceSnapGo/frontend/src/lib/i18n/en.json b/VoiceSnapGo/frontend/src/lib/i18n/en.json
index 96aa055..4527cd3 100755
--- a/VoiceSnapGo/frontend/src/lib/i18n/en.json
+++ b/VoiceSnapGo/frontend/src/lib/i18n/en.json
@@ -54,6 +54,9 @@
"grant": "Grant",
"granted": "Granted",
"missing": "Missing",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "optional": "Optional",
"checking": "Checking",
"allGranted": "All permissions are ready",
"needsAttention": "Some permissions need attention",
@@ -69,6 +72,10 @@
"inputMonitoring": {
"title": "Input Monitoring",
"desc": "Listens for the global right Alt hotkey while other apps are in front."
+ },
+ "backgroundActivity": {
+ "title": "Background Activity / Login Item",
+ "desc": "Starts VoiceSnap after login and allows it to run as a menu bar app in the background; it does not affect the currently running voice input."
}
},
"hotkeys": {
diff --git a/VoiceSnapGo/frontend/src/lib/i18n/zh.json b/VoiceSnapGo/frontend/src/lib/i18n/zh.json
index c15a30a..8a52d67 100755
--- a/VoiceSnapGo/frontend/src/lib/i18n/zh.json
+++ b/VoiceSnapGo/frontend/src/lib/i18n/zh.json
@@ -54,6 +54,9 @@
"grant": "去授权",
"granted": "已授权",
"missing": "未授权",
+ "enabled": "已启用",
+ "disabled": "未启用",
+ "optional": "可选",
"checking": "检查中",
"allGranted": "所有权限已就绪",
"needsAttention": "有权限需要处理",
@@ -69,6 +72,10 @@
"inputMonitoring": {
"title": "输入监控",
"desc": "用于在 VoiceSnap 窗口不可见、其他 App 正在前台时监听右 Alt 全局热键。"
+ },
+ "backgroundActivity": {
+ "title": "后台活动 / 登录项",
+ "desc": "用于登录系统后自动启动 VoiceSnap,并允许它作为菜单栏应用在后台运行;不影响当前已经打开的语音输入。"
}
},
"hotkeys": {
diff --git a/VoiceSnapGo/internal/permissions/permissions.go b/VoiceSnapGo/internal/permissions/permissions.go
index e9cf138..dfee258 100644
--- a/VoiceSnapGo/internal/permissions/permissions.go
+++ b/VoiceSnapGo/internal/permissions/permissions.go
@@ -12,4 +12,5 @@
IDMicrophone = "microphone"
IDAccessibility = "accessibility"
IDInputMonitoring = "inputMonitoring"
+ IDBackground = "backgroundActivity"
)
diff --git a/VoiceSnapGo/internal/permissions/permissions_darwin.go b/VoiceSnapGo/internal/permissions/permissions_darwin.go
index 6879a75..8a66fc2 100644
--- a/VoiceSnapGo/internal/permissions/permissions_darwin.go
+++ b/VoiceSnapGo/internal/permissions/permissions_darwin.go
@@ -42,7 +42,10 @@
*/
import "C"
-import "os/exec"
+import (
+ "os/exec"
+ "voicesnap/internal/startup"
+)
// Statuses returns the current macOS privacy permission state.
func Statuses() []Status {
@@ -65,6 +68,12 @@
State: grantedState(C.inputMonitoringGranted() != 0),
Required: true,
},
+ {
+ ID: IDBackground,
+ Granted: startup.IsEnabled(),
+ State: enabledState(startup.IsEnabled()),
+ Required: false,
+ },
}
}
@@ -80,6 +89,9 @@
openSettings(id)
case IDInputMonitoring:
C.requestInputMonitoring()
+ openSettings(id)
+ case IDBackground:
+ _ = startup.SetEnabled(true)
openSettings(id)
}
}
@@ -110,6 +122,13 @@
return "denied"
}
+func enabledState(enabled bool) string {
+ if enabled {
+ return "enabled"
+ }
+ return "disabled"
+}
+
func openSettings(id string) {
url := "x-apple.systempreferences:com.apple.preference.security"
switch id {
@@ -119,6 +138,8 @@
url = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
case IDInputMonitoring:
url = "x-apple.systempreferences:com.apple.preference.security?Privacy_ListenEvent"
+ case IDBackground:
+ url = "x-apple.systempreferences:com.apple.LoginItems-Settings.extension"
}
_ = exec.Command("open", url).Start()
}
diff --git a/VoiceSnapGo/internal/permissions/permissions_other.go b/VoiceSnapGo/internal/permissions/permissions_other.go
index ccb9e70..db31980 100644
--- a/VoiceSnapGo/internal/permissions/permissions_other.go
+++ b/VoiceSnapGo/internal/permissions/permissions_other.go
@@ -7,6 +7,7 @@
{ID: IDMicrophone, Granted: true, State: "granted", Required: true},
{ID: IDAccessibility, Granted: true, State: "granted", Required: false},
{ID: IDInputMonitoring, Granted: true, State: "granted", Required: false},
+ {ID: IDBackground, Granted: true, State: "enabled", Required: false},
}
}
--
Gitblit v1.9.3