feat(desktop): add keep-awake setting

This commit is contained in:
Bohdan Triapitsyn
2026-07-01 19:07:20 +03:00
parent 4b1e05160f
commit bc4a7d358a
16 changed files with 236 additions and 2 deletions
+44 -1
View File
@@ -1,4 +1,4 @@
import { app, BrowserWindow, dialog, ipcMain, Menu, nativeTheme, net as electronNet, Notification, powerMonitor, protocol, screen, session, shell, webContents } from 'electron';
import { app, BrowserWindow, dialog, ipcMain, Menu, nativeTheme, net as electronNet, Notification, powerMonitor, powerSaveBlocker, protocol, screen, session, shell, webContents } from 'electron';
import contextMenu from 'electron-context-menu';
import log from 'electron-log/main.js';
import dgram from 'node:dgram';
@@ -193,6 +193,32 @@ const state = {
sshLogs: new Map(),
trayController: null,
lastFocusedWindowId: null,
keepAwakeBlockerId: null,
};
const setDesktopKeepAwakeActive = (enabled) => {
const currentId = state.keepAwakeBlockerId;
const isActive = Number.isInteger(currentId) && powerSaveBlocker.isStarted(currentId);
if (enabled) {
if (!isActive) {
state.keepAwakeBlockerId = powerSaveBlocker.start('prevent-app-suspension');
}
return Number.isInteger(state.keepAwakeBlockerId) && powerSaveBlocker.isStarted(state.keepAwakeBlockerId);
}
if (isActive) {
powerSaveBlocker.stop(currentId);
}
state.keepAwakeBlockerId = null;
return false;
};
const readDesktopKeepAwakeStatus = () => {
const enabled = readSettingsRoot().desktopKeepAwakeEnabled === true;
const currentId = state.keepAwakeBlockerId;
const active = Number.isInteger(currentId) && powerSaveBlocker.isStarted(currentId);
return { supported: true, enabled, active };
};
const quitRisk = {
@@ -228,6 +254,7 @@ const quitConfirmationMessage = () => {
const shutdownBackgroundServices = () => {
if (state.backgroundShutdownComplete) return;
state.backgroundShutdownComplete = true;
setDesktopKeepAwakeActive(false);
if (state.installingUpdate) return;
killSidecar();
setImmediate(() => {
@@ -271,6 +298,8 @@ const prepareForQuit = ({ installingUpdate = false } = {}) => {
}
}
setDesktopKeepAwakeActive(false);
if (installingUpdate) {
state.backgroundShutdownComplete = true;
return;
@@ -1141,6 +1170,7 @@ const spawnLocalServer = async () => {
// so phones/tablets on the same Wi-Fi can reach the app. UI shows a clear
// warning and persists the flag via /api/config/settings.
const lanAccessEnabled = settings.desktopLanAccessEnabled === true;
setDesktopKeepAwakeActive(settings.desktopKeepAwakeEnabled === true);
const desktopUiPassword = typeof settings.desktopUiPassword === 'string' ? settings.desktopUiPassword.trim() : '';
const lanAccessBlockedByMissingPassword = lanAccessEnabled && !desktopUiPassword;
const effectiveLanAccessEnabled = lanAccessEnabled && !lanAccessBlockedByMissingPassword;
@@ -3166,6 +3196,19 @@ const handleInvoke = async (browserWindow, command, args = {}) => {
return { supported: true, enabled: settings.openAtLogin === true };
}
case 'desktop_get_keep_awake': {
return readDesktopKeepAwakeStatus();
}
case 'desktop_set_keep_awake': {
const enabled = args.enabled === true;
await mutateSettingsRoot((root) => {
root.desktopKeepAwakeEnabled = enabled;
});
const active = setDesktopKeepAwakeActive(enabled);
return { supported: true, enabled, active };
}
case 'desktop_browser_capture_page': {
const wcId = Number.isFinite(args.webContentsId) ? Math.trunc(args.webContentsId) : null;
if (wcId === null || wcId < 0) throw new Error('webContentsId is required');
@@ -5,10 +5,12 @@ import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import {
getDesktopLanAddress,
getDesktopKeepAwake,
getDesktopLaunchAtLogin,
isDesktopLocalOriginActive,
isDesktopShell,
restartDesktopApp,
setDesktopKeepAwake,
setDesktopLaunchAtLogin,
} from '@/lib/desktop';
import { useI18n } from '@/lib/i18n';
@@ -29,6 +31,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 [keepAwakeSupported, setKeepAwakeSupported] = React.useState(false);
const [keepAwakeEnabled, setKeepAwakeEnabled] = React.useState(false);
const [isSavingKeepAwake, setIsSavingKeepAwake] = React.useState(false);
const [error, setError] = React.useState<string | null>(null);
const [lanAddress, setLanAddress] = React.useState<string | null>(null);
@@ -107,6 +112,27 @@ export const DesktopNetworkSettings: React.FC = () => {
};
}, [isLocalDesktop]);
React.useEffect(() => {
if (!isLocalDesktop) {
setKeepAwakeSupported(false);
return;
}
let cancelled = false;
void (async () => {
const status = await getDesktopKeepAwake();
if (cancelled) {
return;
}
setKeepAwakeSupported(status?.supported === true);
setKeepAwakeEnabled(status?.enabled === true);
})();
return () => {
cancelled = true;
};
}, [isLocalDesktop]);
React.useEffect(() => {
if (!isLocalDesktop || !draftValue) {
setLanAddress(null);
@@ -183,6 +209,30 @@ export const DesktopNetworkSettings: React.FC = () => {
}
}, [isSavingLaunchAtLogin, launchAtLoginEnabled, launchAtLoginSupported, t]);
const handleKeepAwakeToggle = React.useCallback(async () => {
if (!keepAwakeSupported || isSavingKeepAwake) {
return;
}
const nextValue = !keepAwakeEnabled;
setKeepAwakeEnabled(nextValue);
setIsSavingKeepAwake(true);
setError(null);
try {
const status = await setDesktopKeepAwake(nextValue);
if (!status?.supported) {
throw new Error(t('settings.openchamber.desktopNetwork.error.keepAwakeUnsupported'));
}
setKeepAwakeEnabled(status.enabled);
} catch (cause) {
setKeepAwakeEnabled(!nextValue);
setError(cause instanceof Error ? cause.message : t('settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed'));
} finally {
setIsSavingKeepAwake(false);
}
}, [isSavingKeepAwake, keepAwakeEnabled, keepAwakeSupported, t]);
const handleSaveAndRestart = React.useCallback(async () => {
if (!isDirty) {
return;
@@ -261,6 +311,35 @@ export const DesktopNetworkSettings: React.FC = () => {
</div>
) : null}
{keepAwakeSupported ? (
<div
data-settings-item="sessions.desktop-keep-awake"
className="group flex cursor-pointer items-start gap-2 py-1.5"
role="button"
tabIndex={0}
onClick={handleKeepAwakeToggle}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleKeepAwakeToggle();
}
}}
>
<Checkbox
checked={keepAwakeEnabled}
onChange={handleKeepAwakeToggle}
ariaLabel={t('settings.openchamber.desktopNetwork.field.keepAwakeAria')}
disabled={isSavingKeepAwake}
/>
<div className="min-w-0 flex-1">
<div className="typography-ui-label text-foreground">{t('settings.openchamber.desktopNetwork.field.keepAwake')}</div>
<div className="typography-micro text-muted-foreground/70">
{t('settings.openchamber.desktopNetwork.field.keepAwakeDescription')}
</div>
</div>
</div>
) : null}
<div data-settings-item="sessions.desktop-ui-password" className="space-y-1 py-1.5">
<label className="typography-ui-label text-foreground" htmlFor="desktop-ui-password">
{t('settings.openchamber.desktopPassword.field.password')}
+41 -1
View File
@@ -51,6 +51,7 @@ export type DesktopSettings = {
// Optional absolute path to `opencode` binary.
opencodeBinary?: string;
desktopLanAccessEnabled?: boolean;
desktopKeepAwakeEnabled?: boolean;
desktopUiPassword?: string;
projects?: ProjectEntry[];
activeProjectId?: string;
@@ -235,6 +236,12 @@ type LaunchAtLoginStatus = {
enabled: boolean;
};
type KeepAwakeStatus = {
supported: boolean;
enabled: boolean;
active: boolean;
};
export const getDesktopLaunchAtLogin = async (): Promise<LaunchAtLoginStatus | null> => {
if (!canUseElectronDesktopIPC() || !isDesktopLocalOriginActive()) {
return null;
@@ -269,6 +276,40 @@ export const setDesktopLaunchAtLogin = async (enabled: boolean): Promise<LaunchA
}
};
export const getDesktopKeepAwake = async (): Promise<KeepAwakeStatus | null> => {
if (!canUseElectronDesktopIPC() || !isDesktopLocalOriginActive()) {
return null;
}
try {
const result = await invokeDesktop<KeepAwakeStatus>('desktop_get_keep_awake');
if (!result || typeof result.supported !== 'boolean' || typeof result.enabled !== 'boolean' || typeof result.active !== 'boolean') {
return null;
}
return result;
} catch (error) {
console.warn('Failed to get keep awake status', error);
return null;
}
};
export const setDesktopKeepAwake = async (enabled: boolean): Promise<KeepAwakeStatus | null> => {
if (!canUseElectronDesktopIPC() || !isDesktopLocalOriginActive()) {
return null;
}
try {
const result = await invokeDesktop<KeepAwakeStatus>('desktop_set_keep_awake', { enabled });
if (!result || typeof result.supported !== 'boolean' || typeof result.enabled !== 'boolean' || typeof result.active !== 'boolean') {
return null;
}
return result;
} catch (error) {
console.warn('Failed to set keep awake status', error);
return null;
}
};
const normalizeOrigin = (raw: string): string | null => {
const trimmed = raw.trim();
if (!trimmed) return null;
@@ -819,4 +860,3 @@ export const fetchDesktopInstalledApps = async (
return { apps: [], success: false, hasCache: false, isCacheStale: false };
}
};
@@ -823,6 +823,9 @@ export const settingsDict = {
'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.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.',
'settings.openchamber.desktopNetwork.field.allowLanAccessAria': 'Allow LAN access to desktop sidecar',
'settings.openchamber.desktopNetwork.field.allowLanAccess': 'Let other devices on your local network open this app',
'settings.openchamber.desktopNetwork.field.allowLanAccessDescription': 'Restarts the app so phones, tablets, and other computers on your Wi-Fi can open it.',
@@ -839,6 +842,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.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',
'settings.openchamber.opencodeCli.tooltipPrefix': 'Optional absolute path to the',
'settings.openchamber.opencodeCli.tooltipSuffix': 'binary.',
@@ -790,6 +790,9 @@ export const settingsDict = {
"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.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.",
"settings.openchamber.desktopNetwork.field.allowLanAccessAria": "Permitir acceso LAN al lado de escritorio",
"settings.openchamber.desktopNetwork.field.allowLanAccess": "Permitir que otros dispositivos en tu red local abran esta aplicación",
"settings.openchamber.desktopNetwork.field.allowLanAccessDescription": "Reinicia la aplicación para que los teléfonos, tablets y otros ordenadores en tu Wi-Fi puedan abrirla.",
@@ -806,6 +809,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.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",
"settings.openchamber.opencodeCli.tooltipPrefix": "Ruta absoluta opcional al",
"settings.openchamber.opencodeCli.tooltipSuffix": "ejecutable.",
@@ -773,6 +773,9 @@ export const settingsDict = {
'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.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.',
'settings.openchamber.desktopNetwork.field.allowLanAccessAria': 'Autoriser l\'accès LAN au side-car du bureau',
'settings.openchamber.desktopNetwork.field.allowLanAccess': 'Autorisez les autres appareils de votre réseau local à ouvrir cette application',
'settings.openchamber.desktopNetwork.field.allowLanAccessDescription': 'Redémarre l\'application afin que les téléphones, tablettes et autres ordinateurs connectés à votre réseau Wi-Fi puissent l\'ouvrir.',
@@ -789,6 +792,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.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',
'settings.openchamber.opencodeCli.tooltipPrefix': 'Chemin absolu facultatif vers le',
'settings.openchamber.opencodeCli.tooltipSuffix': 'binaire.',
@@ -823,6 +823,9 @@ export const settingsDict = {
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': 'ログイン時に OpenChamber を起動',
'settings.openchamber.desktopNetwork.field.launchAtLogin': 'ログイン時に OpenChamber を起動',
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': 'ウィンドウを開かずにバックグラウンドでアプリを起動します。Dock アイコンをクリックして開きます。',
'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'OpenChamber の実行中はコンピューターのスリープを防止',
'settings.openchamber.desktopNetwork.field.keepAwake': 'OpenChamber の実行中はコンピューターのスリープを防止',
'settings.openchamber.desktopNetwork.field.keepAwakeDescription': 'スマートフォンがこのアプリを開き続けられるようにします。ディスプレイはオフにできます。',
'settings.openchamber.desktopNetwork.field.allowLanAccessAria': 'Desktop サイドカーへの LAN アクセスを許可',
'settings.openchamber.desktopNetwork.field.allowLanAccess': 'ローカルネットワーク上の他のデバイスがこのアプリを開けるようにする',
'settings.openchamber.desktopNetwork.field.allowLanAccessDescription': 'アプリを再起動して、Wi-Fi 上の他のデバイスから開けるようにします。',
@@ -839,6 +842,8 @@ export const settingsDict = {
'settings.openchamber.desktopNetwork.error.savedRestartFailed': '保存しましたが、アプリの再起動に失敗しました',
'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': 'このシステムではログイン時の起動はサポートされていません',
'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': 'ログイン時起動設定の更新に失敗しました',
'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': 'このシステムではスリープ防止はサポートされていません',
'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': 'スリープ防止設定の更新に失敗しました',
'settings.openchamber.opencodeCli.title': 'OpenCode CLI',
'settings.openchamber.opencodeCli.tooltipPrefix': '以下への絶対パス(任意):',
'settings.openchamber.opencodeCli.tooltipSuffix': 'バイナリ。',
@@ -790,6 +790,9 @@ export const settingsDict = {
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': '로그인 시 OpenChamber 시작',
'settings.openchamber.desktopNetwork.field.launchAtLogin': '로그인 시 OpenChamber 시작',
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': '창을 열지 않고 백그라운드에서 앱을 시작합니다. Dock 아이콘을 클릭하면 열립니다.',
'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'OpenChamber가 실행되는 동안 컴퓨터 절전 방지',
'settings.openchamber.desktopNetwork.field.keepAwake': 'OpenChamber가 실행되는 동안 컴퓨터 절전 방지',
'settings.openchamber.desktopNetwork.field.keepAwakeDescription': '휴대폰이 이 앱을 계속 열 수 있도록 합니다. 디스플레이는 꺼질 수 있습니다.',
'settings.openchamber.desktopNetwork.field.allowLanAccessAria': 'desktop sidecar에 LAN 접속 허용',
'settings.openchamber.desktopNetwork.field.allowLanAccess': '로컬 네트워크의 다른 기기에서 이 앱 열기 허용',
'settings.openchamber.desktopNetwork.field.allowLanAccessDescription': '휴대폰, 태블릿, Wi-Fi의 다른 컴퓨터에서 열 수 있도록 앱을 다시 시작합니다.',
@@ -806,6 +809,8 @@ export const settingsDict = {
'settings.openchamber.desktopNetwork.error.savedRestartFailed': '저장했지만 앱을 다시 시작하지 못했습니다',
'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': '이 시스템에서는 로그인 시 시작을 지원하지 않습니다',
'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': '로그인 시 시작 설정을 업데이트하지 못했습니다',
'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': '이 시스템에서는 절전 방지를 지원하지 않습니다',
'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': '절전 방지 설정을 업데이트하지 못했습니다',
'settings.openchamber.opencodeCli.title': 'OpenCode CLI',
'settings.openchamber.opencodeCli.tooltipPrefix': '선택적 절대 경로:',
'settings.openchamber.opencodeCli.tooltipSuffix': 'binary.',
@@ -687,6 +687,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.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',
'settings.openchamber.desktopNetwork.error.saveFailed': 'Nie udało się zapisać ustawień pulpitu',
'settings.openchamber.desktopNetwork.error.savedRestartFailed': 'Zapisano, ale restart aplikacji nie powiódł się',
@@ -696,6 +698,9 @@ export const settingsDict = {
'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.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ć.',
'settings.openchamber.desktopNetwork.field.warning': 'Ostrzeżenie: po włączeniu aplikacja jest dostępna dla każdego w tej samej sieci lokalnej.',
'settings.openchamber.desktopNetwork.field.passwordRequiredWarning': 'Dostęp LAN wymaga hasła UI pulpitu. Dopóki go nie ustawisz, aplikacja pulpitu uruchamia się tylko lokalnie.',
'settings.openchamber.desktopPassword.field.password': 'Hasło UI pulpitu',
@@ -790,6 +790,9 @@ export const settingsDict = {
"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.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.",
"settings.openchamber.desktopNetwork.field.allowLanAccessAria": "Permitir acesso LAN ao lado de desktop",
"settings.openchamber.desktopNetwork.field.allowLanAccess": "Permitir que outros dispositivos na sua rede local abram este aplicativo",
"settings.openchamber.desktopNetwork.field.allowLanAccessDescription": "Reinicia o aplicativo para que os telefones, tablets e outros computadores em seu Wi-Fi possam abri-lo.",
@@ -806,6 +809,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.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",
"settings.openchamber.opencodeCli.tooltipPrefix": "Caminho absoluto opcional para o",
"settings.openchamber.opencodeCli.tooltipSuffix": "executável.",
@@ -790,6 +790,9 @@ export const settingsDict = {
"settings.openchamber.desktopNetwork.field.launchAtLoginAria": "Запускати OpenChamber під час входу в систему",
"settings.openchamber.desktopNetwork.field.launchAtLogin": "Запускати OpenChamber під час входу в систему",
"settings.openchamber.desktopNetwork.field.launchAtLoginDescription": "Запускає застосунок у фоні без відкриття вікна. Щоб відкрити його, натисніть іконку в Dock.",
"settings.openchamber.desktopNetwork.field.keepAwakeAria": "Не давати комп’ютеру засинати, поки OpenChamber запущено",
"settings.openchamber.desktopNetwork.field.keepAwake": "Не давати комп’ютеру засинати, поки OpenChamber запущено",
"settings.openchamber.desktopNetwork.field.keepAwakeDescription": "Щоб телефон і далі міг відкривати застосунок. Екран усе одно може вимикатися.",
"settings.openchamber.desktopNetwork.field.allowLanAccessAria": "Дозволити LAN-доступ до десктопного застосунку",
"settings.openchamber.desktopNetwork.field.allowLanAccess": "Дозволити іншим пристроям у локальній мережі відкривати цей застосунок",
"settings.openchamber.desktopNetwork.field.allowLanAccessDescription": "Перезапускає застосунок, щоб телефони, планшети та інші комп’ютери в мережі Wi-Fi могли його відкрити.",
@@ -806,6 +809,8 @@ export const settingsDict = {
"settings.openchamber.desktopNetwork.error.savedRestartFailed": "Збережено, але не вдалося перезапустити застосунок",
"settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported": "Запуск під час входу в систему не підтримується на цій системі",
"settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed": "Не вдалося оновити налаштування запуску під час входу в систему",
"settings.openchamber.desktopNetwork.error.keepAwakeUnsupported": "Запобігання сну не підтримується на цій системі",
"settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed": "Не вдалося оновити налаштування утримання комп’ютера активним",
"settings.openchamber.opencodeCli.title": "OpenCode CLI",
"settings.openchamber.opencodeCli.tooltipPrefix": "Додатковий абсолютний шлях до",
"settings.openchamber.opencodeCli.tooltipSuffix": "бінарного файлу.",
@@ -790,6 +790,9 @@ export const settingsDict = {
'settings.openchamber.desktopNetwork.field.launchAtLoginAria': '登录时启动 OpenChamber',
'settings.openchamber.desktopNetwork.field.launchAtLogin': '登录时启动 OpenChamber',
'settings.openchamber.desktopNetwork.field.launchAtLoginDescription': '在后台启动应用且不打开窗口。点击 Dock 图标即可打开。',
'settings.openchamber.desktopNetwork.field.keepAwakeAria': 'OpenChamber 运行时保持电脑唤醒',
'settings.openchamber.desktopNetwork.field.keepAwake': 'OpenChamber 运行时保持电脑唤醒',
'settings.openchamber.desktopNetwork.field.keepAwakeDescription': '让手机可以继续打开此应用。屏幕仍可关闭。',
'settings.openchamber.desktopNetwork.field.allowLanAccessAria': '允许桌面 sidecar 局域网访问',
'settings.openchamber.desktopNetwork.field.allowLanAccess': '允许你本地网络中的其他设备打开此应用',
'settings.openchamber.desktopNetwork.field.allowLanAccessDescription': '会重启应用,以便手机、平板和同一 Wi‑Fi 下的其他电脑访问。',
@@ -806,6 +809,8 @@ export const settingsDict = {
'settings.openchamber.desktopNetwork.error.savedRestartFailed': '已保存,但重启应用失败',
'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': '此系统不支持登录时启动',
'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': '更新登录时启动设置失败',
'settings.openchamber.desktopNetwork.error.keepAwakeUnsupported': '此系统不支持防止睡眠',
'settings.openchamber.desktopNetwork.error.keepAwakeSaveFailed': '更新保持唤醒设置失败',
'settings.openchamber.opencodeCli.title': 'OpenCode CLI',
'settings.openchamber.opencodeCli.tooltipPrefix': '可选的',
'settings.openchamber.opencodeCli.tooltipSuffix': '二进制绝对路径。',
@@ -1729,9 +1729,14 @@
'settings.promptTemplates.page.toast.saveUnexpectedError': '儲存時發生意外錯誤',
'settings.openchamber.desktopNetwork.error.launchAtLoginSaveFailed': '無法更新登入時自動啟動設定',
'settings.openchamber.desktopNetwork.error.launchAtLoginUnsupported': '此系統不支援登入時自動啟動',
'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.keepAwakeAria': 'OpenChamber 執行時保持電腦喚醒',
'settings.openchamber.desktopNetwork.field.keepAwake': 'OpenChamber 執行時保持電腦喚醒',
'settings.openchamber.desktopNetwork.field.keepAwakeDescription': '讓手機可以持續開啟此應用程式。螢幕仍可關閉。',
'settings.openchamber.desktopPassword.field.password': '桌面 UI 密碼',
'settings.openchamber.desktopPassword.field.passwordDescription': 'OpenChamber 會在重新啟動後要求輸入密碼,之後會在登入工作階段過期時再次要求:12 小時後,或選擇「信任此裝置」後 7 天。留空可停用登入。',
'settings.openchamber.desktopPassword.field.passwordPlaceholder': '不需要密碼',
+8
View File
@@ -287,6 +287,14 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [
keywords: ['desktop', 'startup', 'login'],
isAvailable: (ctx) => ctx.isDesktopLocalOrigin,
},
{
id: 'sessions.desktop-keep-awake',
page: 'sessions',
titleKey: 'settings.openchamber.desktopNetwork.field.keepAwake',
descriptionKey: 'settings.openchamber.desktopNetwork.field.keepAwakeDescription',
keywords: ['desktop', 'sleep', 'awake', 'server', 'mobile', 'phone'],
isAvailable: (ctx) => ctx.isDesktopLocalOrigin,
},
{
id: 'sessions.desktop-ui-password',
page: 'sessions',
@@ -178,6 +178,9 @@ export const createSettingsHelpers = (dependencies) => {
if (typeof candidate.desktopLanAccessEnabled === 'boolean') {
result.desktopLanAccessEnabled = candidate.desktopLanAccessEnabled;
}
if (typeof candidate.desktopKeepAwakeEnabled === 'boolean') {
result.desktopKeepAwakeEnabled = candidate.desktopKeepAwakeEnabled;
}
if (typeof candidate.desktopUiPassword === 'string') {
result.desktopUiPassword = candidate.desktopUiPassword.trim();
}
@@ -89,6 +89,17 @@ describe('settings helpers', () => {
});
});
it('accepts desktopKeepAwakeEnabled as a persisted shared setting', () => {
const helpers = createTestHelpers();
expect(helpers.sanitizeSettingsUpdate({ desktopKeepAwakeEnabled: true })).toEqual({
desktopKeepAwakeEnabled: true,
});
expect(helpers.sanitizeSettingsUpdate({ desktopKeepAwakeEnabled: false })).toEqual({
desktopKeepAwakeEnabled: false,
});
});
it('accepts desktopUiPassword as a persisted shared setting', () => {
const helpers = createTestHelpers();