feat(electron): add Windows startup and system tray support (#2112)
Add Windows launch-at-login with background startup support and extend the native tray integration to Windows. Add a Windows-only setting to minimize or close the main window to the system tray, persist it through desktop settings, and expose it in Settings search and all locale dictionaries. Keep tray state synchronized with live sessions on both macOS and Windows, while preserving the existing macOS behavior.
This commit is contained in:
@@ -7,11 +7,13 @@ import {
|
||||
getDesktopLanAddress,
|
||||
getDesktopKeepAwake,
|
||||
getDesktopLaunchAtLogin,
|
||||
getDesktopMinimizeToTray,
|
||||
isDesktopLocalOriginActive,
|
||||
isDesktopShell,
|
||||
restartDesktopApp,
|
||||
setDesktopKeepAwake,
|
||||
setDesktopLaunchAtLogin,
|
||||
setDesktopMinimizeToTray,
|
||||
} from '@/lib/desktop';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { runtimeFetch } from '@/lib/runtime-fetch';
|
||||
@@ -31,6 +33,9 @@ export const DesktopNetworkSettings: React.FC = () => {
|
||||
const [launchAtLoginSupported, setLaunchAtLoginSupported] = React.useState(false);
|
||||
const [launchAtLoginEnabled, setLaunchAtLoginEnabled] = React.useState(false);
|
||||
const [isSavingLaunchAtLogin, setIsSavingLaunchAtLogin] = React.useState(false);
|
||||
const [minimizeToTraySupported, setMinimizeToTraySupported] = React.useState(false);
|
||||
const [minimizeToTrayEnabled, setMinimizeToTrayEnabled] = React.useState(false);
|
||||
const [isSavingMinimizeToTray, setIsSavingMinimizeToTray] = React.useState(false);
|
||||
const [keepAwakeSupported, setKeepAwakeSupported] = React.useState(false);
|
||||
const [keepAwakeEnabled, setKeepAwakeEnabled] = React.useState(false);
|
||||
const [isSavingKeepAwake, setIsSavingKeepAwake] = React.useState(false);
|
||||
@@ -112,6 +117,27 @@ export const DesktopNetworkSettings: React.FC = () => {
|
||||
};
|
||||
}, [isLocalDesktop]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isLocalDesktop) {
|
||||
setMinimizeToTraySupported(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
const status = await getDesktopMinimizeToTray();
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
setMinimizeToTraySupported(status?.supported === true);
|
||||
setMinimizeToTrayEnabled(status?.enabled === true);
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [isLocalDesktop]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isLocalDesktop) {
|
||||
setKeepAwakeSupported(false);
|
||||
@@ -209,6 +235,33 @@ export const DesktopNetworkSettings: React.FC = () => {
|
||||
}
|
||||
}, [isSavingLaunchAtLogin, launchAtLoginEnabled, launchAtLoginSupported, t]);
|
||||
|
||||
const handleMinimizeToTrayToggle = React.useCallback(async () => {
|
||||
if (!minimizeToTraySupported || isSavingMinimizeToTray) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextValue = !minimizeToTrayEnabled;
|
||||
setMinimizeToTrayEnabled(nextValue);
|
||||
setIsSavingMinimizeToTray(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const status = await setDesktopMinimizeToTray(nextValue);
|
||||
if (!status) {
|
||||
throw new Error(t('settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed'));
|
||||
}
|
||||
if (!status.supported) {
|
||||
throw new Error(t('settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported'));
|
||||
}
|
||||
setMinimizeToTrayEnabled(status.enabled);
|
||||
} catch (cause) {
|
||||
setMinimizeToTrayEnabled(!nextValue);
|
||||
setError(cause instanceof Error ? cause.message : t('settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed'));
|
||||
} finally {
|
||||
setIsSavingMinimizeToTray(false);
|
||||
}
|
||||
}, [isSavingMinimizeToTray, minimizeToTrayEnabled, minimizeToTraySupported, t]);
|
||||
|
||||
const handleKeepAwakeToggle = React.useCallback(async () => {
|
||||
if (!keepAwakeSupported || isSavingKeepAwake) {
|
||||
return;
|
||||
@@ -311,6 +364,35 @@ export const DesktopNetworkSettings: React.FC = () => {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{minimizeToTraySupported ? (
|
||||
<div
|
||||
data-settings-item="sessions.desktop-minimize-to-tray"
|
||||
className="group flex cursor-pointer items-start gap-2 py-1.5"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={handleMinimizeToTrayToggle}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
handleMinimizeToTrayToggle();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={minimizeToTrayEnabled}
|
||||
onChange={handleMinimizeToTrayToggle}
|
||||
ariaLabel={t('settings.openchamber.desktopNetwork.field.minimizeToTrayAria')}
|
||||
disabled={isSavingMinimizeToTray}
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="typography-ui-label text-foreground">{t('settings.openchamber.desktopNetwork.field.minimizeToTray')}</div>
|
||||
<div className="typography-micro text-muted-foreground/70">
|
||||
{t('settings.openchamber.desktopNetwork.field.minimizeToTrayDescription')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{keepAwakeSupported ? (
|
||||
<div
|
||||
data-settings-item="sessions.desktop-keep-awake"
|
||||
|
||||
@@ -334,6 +334,10 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
||||
return isDesktopShell() && typeof window !== 'undefined'
|
||||
&& (window as unknown as { __OPENCHAMBER_PLATFORM__?: string }).__OPENCHAMBER_PLATFORM__ === 'darwin';
|
||||
}, []);
|
||||
const isWindows = React.useMemo(() => {
|
||||
return isDesktopShell() && typeof window !== 'undefined'
|
||||
&& (window as unknown as { __OPENCHAMBER_PLATFORM__?: string }).__OPENCHAMBER_PLATFORM__ === 'win32';
|
||||
}, []);
|
||||
|
||||
// keep platform check available for future window chrome tweaks
|
||||
|
||||
@@ -536,12 +540,12 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
||||
const settingsSearchResults = React.useMemo(() => {
|
||||
return buildSettingsSearchResults({
|
||||
query: settingsSearchQuery,
|
||||
runtimeCtx: { ...runtimeCtx, isDesktopLocalOrigin, isMac },
|
||||
runtimeCtx: { ...runtimeCtx, isDesktopLocalOrigin, isMac, isWindows },
|
||||
visiblePageSlugs,
|
||||
t,
|
||||
getPageTitle,
|
||||
});
|
||||
}, [getPageTitle, isDesktopLocalOrigin, isMac, runtimeCtx, settingsSearchQuery, t, visiblePageSlugs]);
|
||||
}, [getPageTitle, isDesktopLocalOrigin, isMac, isWindows, runtimeCtx, settingsSearchQuery, t, visiblePageSlugs]);
|
||||
|
||||
const prepareSettingsSearchTarget = React.useCallback((result: SettingsSearchResult): string => {
|
||||
if (result.id.startsWith('agents.')) {
|
||||
|
||||
@@ -27,12 +27,12 @@ import { toast } from '@/components/ui';
|
||||
import type { PermissionRequest } from '@/types/permission';
|
||||
import type { QuestionRequest } from '@/types/question';
|
||||
|
||||
// macOS menu bar bridge. The Electron main process owns the Tray UI; this hook
|
||||
// Native tray/menu bar bridge. The Electron main process owns the Tray UI; this hook
|
||||
// streams a compact snapshot of live session/approval state to it via the
|
||||
// `desktop_tray_update` IPC command, and routes tray clicks back into the app.
|
||||
//
|
||||
// Only meaningful on the macOS desktop shell — main.mjs no-ops the command on
|
||||
// other platforms, but we still gate here to avoid pointless work.
|
||||
// Only meaningful on desktop platforms with a native tray/menu bar — main.mjs
|
||||
// no-ops the command elsewhere, but we still gate here to avoid pointless work.
|
||||
|
||||
const TRAY_ACTION_EVENT = 'openchamber:tray-action';
|
||||
// Event-driven updates do the real work; this is just a slow safety net.
|
||||
@@ -99,9 +99,10 @@ type DesktopBridgeGlobal = {
|
||||
) => Promise<() => void>;
|
||||
};
|
||||
|
||||
const isMac = (): boolean => {
|
||||
const isTrayPlatform = (): boolean => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
return (window as unknown as { __OPENCHAMBER_PLATFORM__?: string }).__OPENCHAMBER_PLATFORM__ === 'darwin';
|
||||
const platform = (window as unknown as { __OPENCHAMBER_PLATFORM__?: string }).__OPENCHAMBER_PLATFORM__;
|
||||
return platform === 'darwin' || platform === 'win32';
|
||||
};
|
||||
|
||||
const permissionLabel = (request: PermissionRequest): string => {
|
||||
@@ -417,7 +418,7 @@ const buildSnapshot = (instanceName: string): TraySnapshot => {
|
||||
|
||||
export const useTraySync = (): void => {
|
||||
React.useEffect(() => {
|
||||
if (!isMac() || !canUseElectronDesktopIPC()) return;
|
||||
if (!isTrayPlatform() || !canUseElectronDesktopIPC()) return;
|
||||
|
||||
let disposed = false;
|
||||
let lastSerialized = '';
|
||||
@@ -577,7 +578,7 @@ export const useTraySync = (): void => {
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isMac() || typeof window === 'undefined') return;
|
||||
if (!isTrayPlatform() || typeof window === 'undefined') return;
|
||||
const bridge = (window as unknown as { __OPENCHAMBER_DESKTOP__?: DesktopBridgeGlobal }).__OPENCHAMBER_DESKTOP__;
|
||||
const listen = bridge?.listen;
|
||||
if (typeof listen !== 'function') return;
|
||||
|
||||
@@ -54,6 +54,7 @@ export type DesktopSettings = {
|
||||
opencodeBinary?: string;
|
||||
desktopLanAccessEnabled?: boolean;
|
||||
desktopKeepAwakeEnabled?: boolean;
|
||||
desktopMinimizeToTrayEnabled?: boolean;
|
||||
desktopUiPassword?: string;
|
||||
projects?: ProjectEntry[];
|
||||
activeProjectId?: string;
|
||||
@@ -247,6 +248,11 @@ type KeepAwakeStatus = {
|
||||
active: boolean;
|
||||
};
|
||||
|
||||
type MinimizeToTrayStatus = {
|
||||
supported: boolean;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export const getDesktopLaunchAtLogin = async (): Promise<LaunchAtLoginStatus | null> => {
|
||||
if (!canUseElectronDesktopIPC() || !isDesktopLocalOriginActive()) {
|
||||
return null;
|
||||
@@ -281,6 +287,40 @@ export const setDesktopLaunchAtLogin = async (enabled: boolean): Promise<LaunchA
|
||||
}
|
||||
};
|
||||
|
||||
export const getDesktopMinimizeToTray = async (): Promise<MinimizeToTrayStatus | null> => {
|
||||
if (!canUseElectronDesktopIPC() || !isDesktopLocalOriginActive()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await invokeDesktop<MinimizeToTrayStatus>('desktop_get_minimize_to_tray');
|
||||
if (!result || typeof result.supported !== 'boolean' || typeof result.enabled !== 'boolean') {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.warn('Failed to get minimize to tray status', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const setDesktopMinimizeToTray = async (enabled: boolean): Promise<MinimizeToTrayStatus | null> => {
|
||||
if (!canUseElectronDesktopIPC() || !isDesktopLocalOriginActive()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await invokeDesktop<MinimizeToTrayStatus>('desktop_set_minimize_to_tray', { enabled });
|
||||
if (!result || typeof result.supported !== 'boolean' || typeof result.enabled !== 'boolean') {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.warn('Failed to set minimize to tray status', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const getDesktopKeepAwake = async (): Promise<KeepAwakeStatus | null> => {
|
||||
if (!canUseElectronDesktopIPC() || !isDesktopLocalOriginActive()) {
|
||||
return null;
|
||||
|
||||
@@ -877,7 +877,10 @@ export const settingsDict = {
|
||||
'settings.openchamber.desktopNetwork.title': 'Desktop Network Access',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': 'Start OpenChamber at login',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLogin': 'Start OpenChamber when you log in',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'Starts the app in the background without opening a window. Click the Dock icon to open it.',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'Starts the app in the background without opening a window. Use the desktop status icon to open it.',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTrayAria': 'Minimize and close OpenChamber to the system tray',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTray': 'Minimize and close to the system tray',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription': 'Keeps OpenChamber running in the Windows system tray when the main window is minimized or closed.',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'Keep computer awake while OpenChamber is running',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwake': 'Keep computer awake while OpenChamber is running',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwakeDescription': 'Prevents system sleep so phones can keep reaching this app. The screen can still turn off.',
|
||||
@@ -897,6 +900,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.desktopNetwork.error.savedRestartFailed': 'Saved, but failed to restart app',
|
||||
'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': 'Launch at login is not supported on this system',
|
||||
'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': 'Failed to update launch at login setting',
|
||||
'settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported': 'System tray background mode is not supported on this system',
|
||||
'settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed': 'Failed to update system tray setting',
|
||||
'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': 'Preventing sleep is not supported on this system',
|
||||
'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': 'Failed to update keep awake setting',
|
||||
'settings.openchamber.opencodeCli.title': 'OpenCode CLI',
|
||||
|
||||
@@ -844,7 +844,10 @@ export const settingsDict = {
|
||||
"settings.openchamber.desktopNetwork.title": "Acceso de red de escritorio",
|
||||
"settings.openchamber.desktopNetwork.field.launchAtLoginAria": "Iniciar OpenChamber al iniciar sesión",
|
||||
"settings.openchamber.desktopNetwork.field.launchAtLogin": "Iniciar OpenChamber al iniciar sesión",
|
||||
"settings.openchamber.desktopNetwork.field.launchAtLoginDescription": "Inicia la app en segundo plano sin abrir una ventana. Haz clic en el icono del Dock para abrirla.",
|
||||
"settings.openchamber.desktopNetwork.field.launchAtLoginDescription": "Inicia la app en segundo plano sin abrir una ventana. Usa el icono de estado del escritorio para abrirla.",
|
||||
"settings.openchamber.desktopNetwork.field.minimizeToTrayAria": "Minimizar y cerrar OpenChamber a la bandeja del sistema",
|
||||
"settings.openchamber.desktopNetwork.field.minimizeToTray": "Minimizar y cerrar a la bandeja del sistema",
|
||||
"settings.openchamber.desktopNetwork.field.minimizeToTrayDescription": "Mantiene OpenChamber en ejecución en la bandeja del sistema de Windows cuando la ventana principal se minimiza o se cierra.",
|
||||
"settings.openchamber.desktopNetwork.field.keepAwakeAria": "Mantener el ordenador activo mientras OpenChamber está en ejecución",
|
||||
"settings.openchamber.desktopNetwork.field.keepAwake": "Mantener el ordenador activo mientras OpenChamber está en ejecución",
|
||||
"settings.openchamber.desktopNetwork.field.keepAwakeDescription": "Para que los teléfonos puedan seguir abriendo esta app. La pantalla aún puede apagarse.",
|
||||
@@ -864,6 +867,8 @@ export const settingsDict = {
|
||||
"settings.openchamber.desktopNetwork.error.savedRestartFailed": "Guardado, pero no se pudo reiniciar la aplicación",
|
||||
"settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported": "El inicio al iniciar sesión no es compatible con este sistema",
|
||||
"settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed": "No se pudo actualizar la opción de inicio al iniciar sesión",
|
||||
"settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported": "El modo en segundo plano de la bandeja del sistema no es compatible con este sistema",
|
||||
"settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed": "No se pudo actualizar la opción de bandeja del sistema",
|
||||
"settings.openchamber.desktopNetwork.error.keepAwakeUnsupported": "Evitar la suspensión no es compatible con este sistema",
|
||||
"settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed": "No se pudo actualizar la opción de mantener activo",
|
||||
"settings.openchamber.opencodeCli.title": "CLI de OpenCode",
|
||||
|
||||
@@ -772,7 +772,10 @@ export const settingsDict = {
|
||||
'settings.openchamber.desktopNetwork.title': 'Accès au réseau de bureau',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': 'Démarrez OpenChamber lors de la connexion',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLogin': 'Démarrez OpenChamber lorsque vous vous connectez',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'Démarre l\'application en arrière-plan sans ouvrir de fenêtre. Cliquez sur l\'icône Dock pour l\'ouvrir.',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'Démarre l\'application en arrière-plan sans ouvrir de fenêtre. Utilisez l\'icône d\'état du bureau pour l\'ouvrir.',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTrayAria': 'Réduire et fermer OpenChamber dans la zone de notification',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTray': 'Réduire et fermer dans la zone de notification',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription': 'Garde OpenChamber actif dans la zone de notification Windows lorsque la fenêtre principale est réduite ou fermée.',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'Garder l\'ordinateur éveillé pendant qu\'OpenChamber fonctionne',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwake': 'Garder l\'ordinateur éveillé pendant qu\'OpenChamber fonctionne',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwakeDescription': 'Pour que les téléphones puissent continuer à ouvrir cette application. L\'écran peut toujours s\'éteindre.',
|
||||
@@ -792,6 +795,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.desktopNetwork.error.savedRestartFailed': 'Enregistré, mais échec du redémarrage de l\'application',
|
||||
'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': 'Le lancement à la connexion n\'est pas pris en charge sur ce système',
|
||||
'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': 'Échec de la mise à jour du paramètre de lancement lors de la connexion',
|
||||
'settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported': 'Le mode d\'arrière-plan dans la zone de notification n\'est pas pris en charge sur ce système',
|
||||
'settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed': 'Échec de la mise à jour du paramètre de zone de notification',
|
||||
'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': 'La prévention de la veille n\'est pas prise en charge sur ce système',
|
||||
'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': 'Échec de la mise à jour du paramètre de maintien en éveil',
|
||||
'settings.openchamber.opencodeCli.title': 'OpenCode CLI',
|
||||
|
||||
@@ -877,7 +877,10 @@ export const settingsDict = {
|
||||
'settings.openchamber.desktopNetwork.title': 'Desktop ネットワークアクセス',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': 'ログイン時に OpenChamber を起動',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLogin': 'ログイン時に OpenChamber を起動',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'ウィンドウを開かずにバックグラウンドでアプリを起動します。Dock アイコンをクリックして開きます。',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'ウィンドウを開かずにバックグラウンドでアプリを起動します。デスクトップのステータスアイコンから開けます。',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTrayAria': 'OpenChamber をシステムトレイへ最小化または閉じる',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTray': 'システムトレイへ最小化または閉じる',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription': 'メインウィンドウを最小化または閉じたときも、OpenChamber を Windows のシステムトレイで実行し続けます。',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'OpenChamber の実行中はコンピューターのスリープを防止',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwake': 'OpenChamber の実行中はコンピューターのスリープを防止',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwakeDescription': 'スマートフォンがこのアプリを開き続けられるようにします。ディスプレイはオフにできます。',
|
||||
@@ -897,6 +900,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.desktopNetwork.error.savedRestartFailed': '保存しましたが、アプリの再起動に失敗しました',
|
||||
'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': 'このシステムではログイン時の起動はサポートされていません',
|
||||
'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': 'ログイン時起動設定の更新に失敗しました',
|
||||
'settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported': 'このシステムではシステムトレイのバックグラウンドモードはサポートされていません',
|
||||
'settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed': 'システムトレイ設定の更新に失敗しました',
|
||||
'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': 'このシステムではスリープ防止はサポートされていません',
|
||||
'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': 'スリープ防止設定の更新に失敗しました',
|
||||
'settings.openchamber.opencodeCli.title': 'OpenCode CLI',
|
||||
|
||||
@@ -844,7 +844,10 @@ export const settingsDict = {
|
||||
'settings.openchamber.desktopNetwork.title': 'Desktop 네트워크 접속',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': '로그인 시 OpenChamber 시작',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLogin': '로그인 시 OpenChamber 시작',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': '창을 열지 않고 백그라운드에서 앱을 시작합니다. Dock 아이콘을 클릭하면 열립니다.',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': '창을 열지 않고 백그라운드에서 앱을 시작합니다. 데스크톱 상태 아이콘으로 열 수 있습니다.',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTrayAria': 'OpenChamber를 시스템 트레이로 최소화하거나 닫기',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTray': '시스템 트레이로 최소화하고 닫기',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription': '메인 창을 최소화하거나 닫아도 OpenChamber가 Windows 시스템 트레이에서 계속 실행됩니다.',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'OpenChamber가 실행되는 동안 컴퓨터 절전 방지',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwake': 'OpenChamber가 실행되는 동안 컴퓨터 절전 방지',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwakeDescription': '휴대폰이 이 앱을 계속 열 수 있도록 합니다. 디스플레이는 꺼질 수 있습니다.',
|
||||
@@ -864,6 +867,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.desktopNetwork.error.savedRestartFailed': '저장했지만 앱을 다시 시작하지 못했습니다',
|
||||
'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': '이 시스템에서는 로그인 시 시작을 지원하지 않습니다',
|
||||
'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': '로그인 시 시작 설정을 업데이트하지 못했습니다',
|
||||
'settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported': '이 시스템에서는 시스템 트레이 백그라운드 모드를 지원하지 않습니다',
|
||||
'settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed': '시스템 트레이 설정을 업데이트하지 못했습니다',
|
||||
'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': '이 시스템에서는 절전 방지를 지원하지 않습니다',
|
||||
'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': '절전 방지 설정을 업데이트하지 못했습니다',
|
||||
'settings.openchamber.opencodeCli.title': 'OpenCode CLI',
|
||||
|
||||
@@ -692,6 +692,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.desktopNetwork.actions.saveAndRestart': 'Zapisz + Zrestartuj',
|
||||
'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': 'Uruchamianie przy logowaniu nie jest obsługiwane w tym systemie',
|
||||
'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': 'Nie udało się zaktualizować ustawienia uruchamiania przy logowaniu',
|
||||
'settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported': 'Tryb działania w tle w zasobniku systemowym nie jest obsługiwany w tym systemie',
|
||||
'settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed': 'Nie udało się zaktualizować ustawienia zasobnika systemowego',
|
||||
'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': 'Zapobieganie uśpieniu nie jest obsługiwane w tym systemie',
|
||||
'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': 'Nie udało się zaktualizować ustawienia utrzymywania aktywności',
|
||||
'settings.openchamber.desktopNetwork.error.loadFailed': 'Nie udało się załadować ustawień pulpitu',
|
||||
@@ -702,7 +704,10 @@ export const settingsDict = {
|
||||
'settings.openchamber.desktopNetwork.field.allowLanAccessDescription': 'Restartuje aplikację, aby telefony, tablety i inne komputery w Twojej sieci Wi-Fi mogły ją otworzyć.',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLogin': 'Uruchamiaj OpenChamber przy logowaniu',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': 'Uruchamiaj OpenChamber przy logowaniu',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'Uruchamia aplikację w tle bez otwierania okna. Kliknij ikonę w Docku, aby ją otworzyć.',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'Uruchamia aplikację w tle bez otwierania okna. Otworzysz ją z ikony stanu na pulpicie.',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTrayAria': 'Minimalizuj i zamykaj OpenChamber do zasobnika systemowego',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTray': 'Minimalizuj i zamykaj do zasobnika systemowego',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription': 'Utrzymuje OpenChamber w zasobniku systemowym Windows, gdy główne okno jest zminimalizowane lub zamknięte.',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'Utrzymuj komputer aktywny, gdy OpenChamber jest uruchomiony',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwake': 'Utrzymuj komputer aktywny, gdy OpenChamber jest uruchomiony',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwakeDescription': 'Aby telefony nadal mogły otwierać tę aplikację. Ekran nadal może się wyłączyć.',
|
||||
|
||||
@@ -844,7 +844,10 @@ export const settingsDict = {
|
||||
"settings.openchamber.desktopNetwork.title": "Acesso de rede do desktop",
|
||||
"settings.openchamber.desktopNetwork.field.launchAtLoginAria": "Iniciar o OpenChamber ao fazer login",
|
||||
"settings.openchamber.desktopNetwork.field.launchAtLogin": "Iniciar o OpenChamber ao fazer login",
|
||||
"settings.openchamber.desktopNetwork.field.launchAtLoginDescription": "Inicia o app em segundo plano sem abrir uma janela. Clique no ícone do Dock para abrir.",
|
||||
"settings.openchamber.desktopNetwork.field.launchAtLoginDescription": "Inicia o app em segundo plano sem abrir uma janela. Use o ícone de status da área de trabalho para abrir.",
|
||||
"settings.openchamber.desktopNetwork.field.minimizeToTrayAria": "Minimizar e fechar o OpenChamber na bandeja do sistema",
|
||||
"settings.openchamber.desktopNetwork.field.minimizeToTray": "Minimizar e fechar na bandeja do sistema",
|
||||
"settings.openchamber.desktopNetwork.field.minimizeToTrayDescription": "Mantém o OpenChamber em execução na bandeja do sistema do Windows quando a janela principal é minimizada ou fechada.",
|
||||
"settings.openchamber.desktopNetwork.field.keepAwakeAria": "Manter o computador ativo enquanto o OpenChamber estiver em execução",
|
||||
"settings.openchamber.desktopNetwork.field.keepAwake": "Manter o computador ativo enquanto o OpenChamber estiver em execução",
|
||||
"settings.openchamber.desktopNetwork.field.keepAwakeDescription": "Para que telefones continuem abrindo este app. A tela ainda pode desligar.",
|
||||
@@ -864,6 +867,8 @@ export const settingsDict = {
|
||||
"settings.openchamber.desktopNetwork.error.savedRestartFailed": "Salvo, mas não foi possível reiniciar o aplicativo",
|
||||
"settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported": "Iniciar ao fazer login não é compatível com este sistema",
|
||||
"settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed": "Não foi possível atualizar a opção de iniciar ao fazer login",
|
||||
"settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported": "O modo em segundo plano na bandeja do sistema não é compatível com este sistema",
|
||||
"settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed": "Não foi possível atualizar a opção da bandeja do sistema",
|
||||
"settings.openchamber.desktopNetwork.error.keepAwakeUnsupported": "Impedir suspensão não é compatível com este sistema",
|
||||
"settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed": "Não foi possível atualizar a opção de manter ativo",
|
||||
"settings.openchamber.opencodeCli.title": "CLI do OpenCode",
|
||||
|
||||
@@ -844,7 +844,10 @@ export const settingsDict = {
|
||||
"settings.openchamber.desktopNetwork.title": "Мережевий доступ десктопного застосунку",
|
||||
"settings.openchamber.desktopNetwork.field.launchAtLoginAria": "Запускати OpenChamber під час входу в систему",
|
||||
"settings.openchamber.desktopNetwork.field.launchAtLogin": "Запускати OpenChamber під час входу в систему",
|
||||
"settings.openchamber.desktopNetwork.field.launchAtLoginDescription": "Запускає застосунок у фоні без відкриття вікна. Щоб відкрити його, натисніть іконку в Dock.",
|
||||
"settings.openchamber.desktopNetwork.field.launchAtLoginDescription": "Запускає застосунок у фоні без відкриття вікна. Відкрийте його через піктограму стану на робочому столі.",
|
||||
"settings.openchamber.desktopNetwork.field.minimizeToTrayAria": "Згортати й закривати OpenChamber у системний трей",
|
||||
"settings.openchamber.desktopNetwork.field.minimizeToTray": "Згортати й закривати в системний трей",
|
||||
"settings.openchamber.desktopNetwork.field.minimizeToTrayDescription": "Залишає OpenChamber запущеним у системному треї Windows, коли головне вікно згорнуто або закрито.",
|
||||
"settings.openchamber.desktopNetwork.field.keepAwakeAria": "Не давати комп’ютеру засинати, поки OpenChamber запущено",
|
||||
"settings.openchamber.desktopNetwork.field.keepAwake": "Не давати комп’ютеру засинати, поки OpenChamber запущено",
|
||||
"settings.openchamber.desktopNetwork.field.keepAwakeDescription": "Щоб телефон і далі міг відкривати застосунок. Екран усе одно може вимикатися.",
|
||||
@@ -864,6 +867,8 @@ export const settingsDict = {
|
||||
"settings.openchamber.desktopNetwork.error.savedRestartFailed": "Збережено, але не вдалося перезапустити застосунок",
|
||||
"settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported": "Запуск під час входу в систему не підтримується на цій системі",
|
||||
"settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed": "Не вдалося оновити налаштування запуску під час входу в систему",
|
||||
"settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported": "Фоновий режим у системному треї не підтримується на цій системі",
|
||||
"settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed": "Не вдалося оновити налаштування системного трея",
|
||||
"settings.openchamber.desktopNetwork.error.keepAwakeUnsupported": "Запобігання сну не підтримується на цій системі",
|
||||
"settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed": "Не вдалося оновити налаштування утримання комп’ютера активним",
|
||||
"settings.openchamber.opencodeCli.title": "OpenCode CLI",
|
||||
|
||||
@@ -844,7 +844,10 @@ export const settingsDict = {
|
||||
'settings.openchamber.desktopNetwork.title': '桌面端网络访问',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': '登录时启动 OpenChamber',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLogin': '登录时启动 OpenChamber',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': '在后台启动应用且不打开窗口。点击 Dock 图标即可打开。',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': '在后台启动应用且不打开窗口。可通过桌面状态图标打开。',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTrayAria': '将 OpenChamber 最小化和关闭到系统托盘',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTray': '最小化和关闭到系统托盘',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription': '主窗口最小化或关闭时,让 OpenChamber 继续在 Windows 系统托盘中运行。',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'OpenChamber 运行时保持电脑唤醒',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwake': 'OpenChamber 运行时保持电脑唤醒',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwakeDescription': '让手机可以继续打开此应用。屏幕仍可关闭。',
|
||||
@@ -864,6 +867,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.desktopNetwork.error.savedRestartFailed': '已保存,但重启应用失败',
|
||||
'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': '此系统不支持登录时启动',
|
||||
'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': '更新登录时启动设置失败',
|
||||
'settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported': '此系统不支持系统托盘后台模式',
|
||||
'settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed': '更新系统托盘设置失败',
|
||||
'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': '此系统不支持防止睡眠',
|
||||
'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': '更新保持唤醒设置失败',
|
||||
'settings.openchamber.opencodeCli.title': 'OpenCode CLI',
|
||||
|
||||
@@ -1817,11 +1817,16 @@
|
||||
'settings.promptTemplates.page.toast.saveUnexpectedError': '儲存時發生意外錯誤',
|
||||
'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': '無法更新登入時自動啟動設定',
|
||||
'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': '此系統不支援登入時自動啟動',
|
||||
'settings.openchamber.desktopNetwork.error.minimizeToTrayUnsupported': '此系統不支援系統匣背景模式',
|
||||
'settings.openchamber.desktopNetwork.error.minimizeToTraySaveFailed': '無法更新系統匣設定',
|
||||
'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': '此系統不支援防止睡眠',
|
||||
'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': '無法更新保持喚醒設定',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLogin': '登入時自動啟動 OpenChamber',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': '登入時自動啟動 OpenChamber',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': '在背景啟動應用程式,不開啟視窗。按一下 Dock 圖示即可開啟。',
|
||||
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': '在背景啟動應用程式,不開啟視窗。可透過桌面狀態圖示開啟。',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTrayAria': '將 OpenChamber 最小化和關閉到系統匣',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTray': '最小化和關閉到系統匣',
|
||||
'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription': '主視窗最小化或關閉時,讓 OpenChamber 繼續在 Windows 系統匣中執行。',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'OpenChamber 執行時保持電腦喚醒',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwake': 'OpenChamber 執行時保持電腦喚醒',
|
||||
'settings.openchamber.desktopNetwork.field.keepAwakeDescription': '讓手機可以持續開啟此應用程式。螢幕仍可關閉。',
|
||||
|
||||
@@ -747,6 +747,12 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
||||
if (typeof candidate.desktopLanAccessEnabled === 'boolean') {
|
||||
result.desktopLanAccessEnabled = candidate.desktopLanAccessEnabled;
|
||||
}
|
||||
if (typeof candidate.desktopKeepAwakeEnabled === 'boolean') {
|
||||
result.desktopKeepAwakeEnabled = candidate.desktopKeepAwakeEnabled;
|
||||
}
|
||||
if (typeof candidate.desktopMinimizeToTrayEnabled === 'boolean') {
|
||||
result.desktopMinimizeToTrayEnabled = candidate.desktopMinimizeToTrayEnabled;
|
||||
}
|
||||
|
||||
const projects = sanitizeProjects(candidate.projects);
|
||||
if (projects) {
|
||||
|
||||
@@ -22,6 +22,8 @@ interface SettingsSearchAvailabilityContext extends SettingsRuntimeContext {
|
||||
isDesktopLocalOrigin: boolean;
|
||||
// macOS desktop shell — for controls that only render on darwin (e.g. dock badge).
|
||||
isMac: boolean;
|
||||
// Windows desktop shell — for controls that only render on win32.
|
||||
isWindows: boolean;
|
||||
}
|
||||
|
||||
const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [
|
||||
@@ -312,6 +314,14 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [
|
||||
keywords: ['desktop', 'startup', 'login'],
|
||||
isAvailable: (ctx) => ctx.isDesktopLocalOrigin,
|
||||
},
|
||||
{
|
||||
id: 'sessions.desktop-minimize-to-tray',
|
||||
page: 'sessions',
|
||||
titleKey: 'settings.openchamber.desktopNetwork.field.minimizeToTray',
|
||||
descriptionKey: 'settings.openchamber.desktopNetwork.field.minimizeToTrayDescription',
|
||||
keywords: ['desktop', 'tray', 'system tray', 'minimize', 'close', 'background', 'windows'],
|
||||
isAvailable: (ctx) => ctx.isDesktopLocalOrigin && ctx.isWindows,
|
||||
},
|
||||
{
|
||||
id: 'sessions.desktop-keep-awake',
|
||||
page: 'sessions',
|
||||
|
||||
Reference in New Issue
Block a user