diff --git a/openless-all/app/src-tauri/src/lib.rs b/openless-all/app/src-tauri/src/lib.rs index a7797680..3ca4aa5a 100644 --- a/openless-all/app/src-tauri/src/lib.rs +++ b/openless-all/app/src-tauri/src/lib.rs @@ -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}"); } } diff --git a/openless-all/app/src-tauri/src/types.rs b/openless-all/app/src-tauri/src/types.rs index d82d93e0..b2578117 100644 --- a/openless-all/app/src-tauri/src/types.rs +++ b/openless-all/app/src-tauri/src/types.rs @@ -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, } } } diff --git a/openless-all/app/src/i18n/en.ts b/openless-all/app/src/i18n/en.ts index b71c0a0c..17feb523 100644 --- a/openless-all/app/src/i18n/en.ts +++ b/openless-all/app/src/i18n/en.ts @@ -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}}', diff --git a/openless-all/app/src/i18n/ja.ts b/openless-all/app/src/i18n/ja.ts index 4e67fcc1..b5b3ec28 100644 --- a/openless-all/app/src/i18n/ja.ts +++ b/openless-all/app/src/i18n/ja.ts @@ -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}}', diff --git a/openless-all/app/src/i18n/ko.ts b/openless-all/app/src/i18n/ko.ts index 71d1554f..9518e2af 100644 --- a/openless-all/app/src/i18n/ko.ts +++ b/openless-all/app/src/i18n/ko.ts @@ -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}}', diff --git a/openless-all/app/src/i18n/zh-CN.ts b/openless-all/app/src/i18n/zh-CN.ts index a9d830a5..265449a2 100644 --- a/openless-all/app/src/i18n/zh-CN.ts +++ b/openless-all/app/src/i18n/zh-CN.ts @@ -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}}', diff --git a/openless-all/app/src/i18n/zh-TW.ts b/openless-all/app/src/i18n/zh-TW.ts index 5d905384..21dc2d80 100644 --- a/openless-all/app/src/i18n/zh-TW.ts +++ b/openless-all/app/src/i18n/zh-TW.ts @@ -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}}', diff --git a/openless-all/app/src/lib/ipc.ts b/openless-all/app/src/lib/ipc.ts index 3ad36ffa..96adf2e7 100644 --- a/openless-all/app/src/lib/ipc.ts +++ b/openless-all/app/src/lib/ipc.ts @@ -75,6 +75,7 @@ const mockSettings: UserPreferences = { foundryLocalAsrKeepLoadedSecs: 300, historyRetentionDays: 7, polishContextWindowMinutes: 5, + startMinimized: false, }; const mockHotkeyCapability: HotkeyCapability = { diff --git a/openless-all/app/src/lib/stylePrefs.test.ts b/openless-all/app/src/lib/stylePrefs.test.ts index e93e7ac7..cfcdcf67 100644 --- a/openless-all/app/src/lib/stylePrefs.test.ts +++ b/openless-all/app/src/lib/stylePrefs.test.ts @@ -41,6 +41,7 @@ const previousPrefs: UserPreferences = { foundryLocalAsrKeepLoadedSecs: 300, historyRetentionDays: 7, polishContextWindowMinutes: 5, + startMinimized: false, }; const nextPrefs: UserPreferences = { diff --git a/openless-all/app/src/lib/types.ts b/openless-all/app/src/lib/types.ts index e4dc057b..b946ff0e 100644 --- a/openless-all/app/src/lib/types.ts +++ b/openless-all/app/src/lib/types.ts @@ -171,6 +171,9 @@ export interface UserPreferences { historyRetentionDays: number; /** 对话感知 polish 上下文窗口(分钟)。0 = 关闭。默认 5。详见 PR-A。 */ polishContextWindowMinutes: number; + /** 启动时静默运行(不弹主窗口)。Windows 开机自启场景常用——只想要后台 + 托盘, + * 不想被主窗口打扰。开后所有启动路径都不弹窗,从菜单栏 / 托盘进入主窗口。默认 false。 */ + startMinimized: boolean; } export interface MicrophoneDevice { diff --git a/openless-all/app/src/pages/Settings.tsx b/openless-all/app/src/pages/Settings.tsx index 64ec266c..95f68e60 100644 --- a/openless-all/app/src/pages/Settings.tsx +++ b/openless-all/app/src/pages/Settings.tsx @@ -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() { /> + + + {capability.statusHint && (
{capability.statusHint}