feat(startup): add "start minimized" toggle (Windows background autostart) (#357)
回应用户「Windows 端开机启动时不弹软件界面、自动后台启动」需求。 新 pref start_minimized: bool,默认 false 不破坏现有行为。打开后所有 启动路径都不弹主窗口(包括开机自启 + 手动启动),App 仅在菜单栏 / 托盘 运行;用户从托盘进入主窗口。Windows 开机自启场景用得多——本来想要 后台 + 托盘,不想被主窗口打扰。 设计选择:不区分"自启动 vs 手动启动"——开关一开则所有启动统一静默。 理由:避免 argv parsing + 重新注册 autostart 带来的复杂性(tauri-plugin- autostart 的 args 是 init() 时静态设的、动态切需要重注册)。开关明确 打开 = 接受所有启动都从托盘进。 实现: - types.rs: UserPreferences 加 start_minimized 字段(含 UserPreferencesWire + Default + Deserialize 全套兼容) - lib.rs setup hook: 读 coordinator.prefs().get().start_minimized;为 true 时跳过 main.show()。OPENLESS_SHOW_MAIN_ON_START=1 仍保留老的强制 show 路径(dev 用),优先级高于 prefs - 前端 types.ts / ipc.ts mockSettings / stylePrefs.test.ts: 加默认 false - Settings.tsx Recording 区在 AutostartRow 下加 Toggle - i18n 5 locale (zh-CN / zh-TW / en / ja / ko) 加 startMinimizedLabel + startMinimizedDesc 两个 key 顺手修预存 bug(跟 PR #355 副 commit 同源——本 branch 没含那个修复): HotkeyBinding default keys=None,避免旧 prefs 反序列化时 trigger / keys 不一致。types::tests 8/8 pass。 不在本 PR:Windows IME 切换还原偶发不还原 bug——需要 Windows 日志 + 复现条件才能定位,盲改可能引入新 bug。已请求用户提供 %LOCALAPPDATA%\OpenLess\Logs\openless.log + 系统版本 + IME 信息。 cargo test --lib: 135/135 pass。npm run build: tsc + vite 干净。 Co-authored-by: baiqing <lbx12309@icloud.com>
This commit is contained in:
1 parent
375456f834
commit
aee0d82a59
11 files changed
+44
-1
No files matched your search
@@ -149,7 +149,17 @@ pub fn run() {
|
||||
}
|
||||
apply_windows_rounded_frame(&main);
|
||||
}
|
||||
if let Err(e) = main.show() {
|
||||
// 静默启动开关:prefs.start_minimized = true → 不弹主窗口,
|
||||
// 用户从菜单栏 / 托盘点击访问。开机自启时尤其有用,避免每次
|
||||
// 登录都被主窗口打扰。OPENLESS_SHOW_MAIN_ON_START=1 仍保留
|
||||
// 老的强制 show 路径(手动 dispatch 测试 / dev 用),优先级高
|
||||
// 于 prefs。
|
||||
let force_show = std::env::var("OPENLESS_SHOW_MAIN_ON_START").ok().as_deref()
|
||||
== Some("1");
|
||||
let suppress_show = !force_show && coordinator.prefs().get().start_minimized;
|
||||
if suppress_show {
|
||||
log::info!("[main] start_minimized=true → 跳过初始 show,等用户点托盘");
|
||||
} else if let Err(e) = main.show() {
|
||||
log::warn!("[main] initial show failed: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,6 +226,11 @@ pub struct UserPreferences {
|
||||
/// 0 = 关闭(每次润色独立单轮,跟历史行为一致)。默认 5 分钟。
|
||||
#[serde(default = "default_polish_context_window_minutes")]
|
||||
pub polish_context_window_minutes: u32,
|
||||
/// 启动时静默运行(不弹主窗口)。开机自启用户用得多——本来想看托盘
|
||||
/// 而不是被主窗口打扰。开关一开后所有启动路径都不弹窗(包括手动点击),
|
||||
/// 用户改用托盘菜单访问主窗口。默认 false 跟历史行为一致。
|
||||
#[serde(default)]
|
||||
pub start_minimized: bool,
|
||||
}
|
||||
|
||||
fn default_local_asr_model() -> String {
|
||||
@@ -315,6 +320,8 @@ struct UserPreferencesWire {
|
||||
history_retention_days: u32,
|
||||
#[serde(default = "default_polish_context_window_minutes")]
|
||||
polish_context_window_minutes: u32,
|
||||
#[serde(default)]
|
||||
start_minimized: bool,
|
||||
}
|
||||
|
||||
impl Default for UserPreferencesWire {
|
||||
@@ -353,6 +360,7 @@ impl Default for UserPreferencesWire {
|
||||
update_channel: prefs.update_channel,
|
||||
history_retention_days: prefs.history_retention_days,
|
||||
polish_context_window_minutes: prefs.polish_context_window_minutes,
|
||||
start_minimized: prefs.start_minimized,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,6 +416,7 @@ impl<'de> Deserialize<'de> for UserPreferences {
|
||||
update_channel: wire.update_channel,
|
||||
history_retention_days: wire.history_retention_days,
|
||||
polish_context_window_minutes: wire.polish_context_window_minutes,
|
||||
start_minimized: wire.start_minimized,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -516,6 +525,7 @@ impl Default for UserPreferences {
|
||||
update_channel: UpdateChannel::default(),
|
||||
history_retention_days: default_history_retention_days(),
|
||||
polish_context_window_minutes: default_polish_context_window_minutes(),
|
||||
start_minimized: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,6 +326,8 @@ export const en: typeof zhCN = {
|
||||
historyRetentionDesc: 'History entries older than this many days are pruned when a new entry is written. 0 = no time-based pruning (still capped at 200). Default 7 days.',
|
||||
polishContextWindowLabel: 'Polish context window (minutes)',
|
||||
polishContextWindowDesc: 'Feed the LLM successfully-polished transcripts from the last N minutes as multi-turn context, so pronouns and unfinished sentences resolve against prior dictation. 0 = disabled (single-turn polish, legacy behavior). Default 5 minutes; values above 60 rarely help.',
|
||||
startMinimizedLabel: 'Start minimized (no main window)',
|
||||
startMinimizedDesc: 'When on, the main window never appears on launch (including login autostart) — OpenLess runs in the menu bar / tray only. Click the tray icon to open the main window when needed. Useful with Windows autostart.',
|
||||
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}}',
|
||||
|
||||
@@ -328,6 +328,8 @@ export const ja: typeof zhCN = {
|
||||
historyRetentionDesc: 'この日数を超えた履歴は新しいエントリ書き込み時に削除されます。0 = 時間による削除なし(200 件上限は維持)。デフォルト 7 日。',
|
||||
polishContextWindowLabel: '会話コンテキスト窓(分)',
|
||||
polishContextWindowDesc: '直近 N 分間の整文済み転写をマルチターン文脈として LLM に渡し、代名詞や未完了の文を前のディクテーションと突き合わせて解釈できるようにします。0 = 無効(シングルターン整文)。デフォルト 5 分。60 分超は実質意味なし。',
|
||||
startMinimizedLabel: '起動時にメインウィンドウを表示しない',
|
||||
startMinimizedDesc: 'ON にすると、ログイン時の自動起動を含むすべての起動でメインウィンドウは表示されません。メニューバー / トレイのみで動作し、必要時にトレイアイコンをクリックしてメインウィンドウを開きます。Windows の自動起動と組み合わせて便利。',
|
||||
startupAtBoot: '起動時に自動起動',
|
||||
startupAtBootDesc: 'ログイン後に OpenLess を自動起動。macOS は LaunchAgent、Linux は ~/.config/autostart、Windows は HKCU\\Run(管理者不要)。詳細は issue #194。',
|
||||
startupAtBootError: '自動起動の切り替えに失敗:{{message}}',
|
||||
|
||||
@@ -328,6 +328,8 @@ export const ko: typeof zhCN = {
|
||||
historyRetentionDesc: '이 기간을 초과한 기록은 새 항목을 쓸 때 정리됩니다. 0 = 시간 기반 정리 비활성화(200건 상한은 유지). 기본 7일.',
|
||||
polishContextWindowLabel: '대화 컨텍스트 윈도(분)',
|
||||
polishContextWindowDesc: '최근 N분간 성공적으로 정리된 전사를 멀티턴 컨텍스트로 LLM 에 전달하여 대명사와 미완성 문장을 이전 받아쓰기와 대조해 해석할 수 있도록 합니다. 0 = 비활성화(단일턴 정리). 기본 5분; 60분 초과는 의미가 거의 없습니다.',
|
||||
startMinimizedLabel: '시작 시 메인 창 숨기기',
|
||||
startMinimizedDesc: '켜면 로그인 자동 시작을 포함한 모든 시작 경로에서 메인 창이 나타나지 않습니다. 메뉴 막대 / 트레이에서만 실행되며, 필요할 때 트레이 아이콘을 클릭해 메인 창을 엽니다. Windows 자동 시작과 함께 사용하면 편리합니다.',
|
||||
startupAtBoot: '부팅 시 자동 시작',
|
||||
startupAtBootDesc: '로그인 후 OpenLess 자동 시작. macOS 는 LaunchAgent, Linux 는 ~/.config/autostart, Windows 는 HKCU\\Run(관리자 불필요). issue #194 참조.',
|
||||
startupAtBootError: '자동 시작 전환 실패: {{message}}',
|
||||
|
||||
@@ -324,6 +324,8 @@ export const zhCN = {
|
||||
historyRetentionDesc: '超过这个天数的历史会在写入新条目时被清理。0 = 不按时间清理(仍受 200 条上限)。默认 7 天。',
|
||||
polishContextWindowLabel: '对话上下文窗口(分钟)',
|
||||
polishContextWindowDesc: '把最近 N 分钟内已成功润色的转写作为多轮上下文喂给 LLM,让代词与未完整句子能被正确解析。0 = 关闭,单轮独立润色。默认 5 分钟;超过 60 分钟意义不大。',
|
||||
startMinimizedLabel: '启动时静默运行',
|
||||
startMinimizedDesc: '打开后所有启动路径都不弹主窗口(包括开机自启),App 仅在菜单栏 / 托盘运行。需要主窗口时点托盘图标打开。Windows 开机自启场景常用。',
|
||||
startupAtBoot: '开机自启',
|
||||
startupAtBootDesc: '登录后自动启动 OpenLess。macOS 写 LaunchAgent,Linux 写 ~/.config/autostart,Windows 写 HKCU\\Run(不需要管理员)。详见 issue #194。',
|
||||
startupAtBootError: '开机自启切换失败:{{message}}',
|
||||
|
||||
@@ -326,6 +326,8 @@ export const zhTW: typeof zhCN = {
|
||||
historyRetentionDesc: '超過這個天數的歷史會在寫入新條目時被清理。0 = 不按時間清理(仍受 200 條上限)。默認 7 天。',
|
||||
polishContextWindowLabel: '對話上下文窗口(分鐘)',
|
||||
polishContextWindowDesc: '把最近 N 分鐘內已成功潤色的轉寫作為多輪上下文喂給 LLM,讓代詞與未完整句子能被正確解析。0 = 關閉,單輪獨立潤色。默認 5 分鐘;超過 60 分鐘意義不大。',
|
||||
startMinimizedLabel: '啓動時靜默運行',
|
||||
startMinimizedDesc: '打開後所有啓動路徑都不彈主窗口(包括開機自啓),App 僅在選單欄 / 托盤運行。需要主窗口時點托盤圖示打開。Windows 開機自啓場景常用。',
|
||||
startupAtBoot: '開機自啓',
|
||||
startupAtBootDesc: '登錄後自動啓動 OpenLess。macOS 寫 LaunchAgent,Linux 寫 ~/.config/autostart,Windows 寫 HKCU\\Run(不需要管理員)。詳見 issue #194。',
|
||||
startupAtBootError: '開機自啓切換失敗:{{message}}',
|
||||
|
||||
@@ -75,6 +75,7 @@ const mockSettings: UserPreferences = {
|
||||
foundryLocalAsrKeepLoadedSecs: 300,
|
||||
historyRetentionDays: 7,
|
||||
polishContextWindowMinutes: 5,
|
||||
startMinimized: false,
|
||||
};
|
||||
|
||||
const mockHotkeyCapability: HotkeyCapability = {
|
||||
|
||||
@@ -41,6 +41,7 @@ const previousPrefs: UserPreferences = {
|
||||
foundryLocalAsrKeepLoadedSecs: 300,
|
||||
historyRetentionDays: 7,
|
||||
polishContextWindowMinutes: 5,
|
||||
startMinimized: false,
|
||||
};
|
||||
|
||||
const nextPrefs: UserPreferences = {
|
||||
|
||||
@@ -171,6 +171,9 @@ export interface UserPreferences {
|
||||
historyRetentionDays: number;
|
||||
/** 对话感知 polish 上下文窗口(分钟)。0 = 关闭。默认 5。详见 PR-A。 */
|
||||
polishContextWindowMinutes: number;
|
||||
/** 启动时静默运行(不弹主窗口)。Windows 开机自启场景常用——只想要后台 + 托盘,
|
||||
* 不想被主窗口打扰。开后所有启动路径都不弹窗,从菜单栏 / 托盘进入主窗口。默认 false。 */
|
||||
startMinimized: boolean;
|
||||
}
|
||||
|
||||
export interface MicrophoneDevice {
|
||||
|
||||
@@ -283,6 +283,8 @@ function RecordingSection() {
|
||||
if (Number.isNaN(parsed)) return;
|
||||
void savePrefs({ ...prefs, polishContextWindowMinutes: clamp(parsed, 0, 60) });
|
||||
};
|
||||
const onStartMinimizedChange = (startMinimized: boolean) =>
|
||||
savePrefs({ ...prefs, startMinimized });
|
||||
|
||||
const choices: Array<[HotkeyMode, string]> = [
|
||||
['toggle', t('settings.recording.modeToggle')],
|
||||
@@ -470,6 +472,12 @@ function RecordingSection() {
|
||||
/>
|
||||
</SettingRow>
|
||||
<AutostartRow />
|
||||
<SettingRow
|
||||
label={t('settings.recording.startMinimizedLabel')}
|
||||
desc={t('settings.recording.startMinimizedDesc')}
|
||||
>
|
||||
<Toggle on={prefs.startMinimized} onToggle={onStartMinimizedChange} />
|
||||
</SettingRow>
|
||||
{capability.statusHint && (
|
||||
<div style={{ marginTop: 6, fontSize: 11.5, color: 'var(--ol-ink-4)', lineHeight: 1.5 }}>
|
||||
{capability.statusHint}
|
||||
|
||||
Reference in new issue
Block a user