feat: system settings tab — launch at login and prevent-sleep toggles

AppSettings.system persisted through the existing settings IPC; the main
process applies real side effects at startup and on every save via
app.setLoginItemSettings and powerSaveBlocker.
This commit is contained in:
954801926@qq.com committed 2026-09-06 11:43:40 +08:00
1 parent db0eae5efd
commit 18506c926a
6 files changed
+116 -8

No files matched your search

+4
View File
@@ -2,6 +2,7 @@ import { app, BrowserWindow, shell } from 'electron'
import { join } from 'path'
import { registerIpc } from './ipc'
import { killAllPtys } from './pty'
import { applyStartupSystemSettings, loadSettings } from './settingsStore'
function createWindow(): void {
const win = new BrowserWindow({
@@ -42,6 +43,9 @@ function createWindow(): void {
app.whenReady().then(() => {
registerIpc()
// OS-level effects (login item, sleep blocker) must apply even if the
// settings dialog is never opened this run.
applyStartupSystemSettings(loadSettings())
createWindow()
app.on('activate', () => {
+54 -4
View File
@@ -1,4 +1,4 @@
import { app, ipcMain } from 'electron'
import { app, ipcMain, powerSaveBlocker } from 'electron'
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'fs'
import { join } from 'path'
import { Ipc } from '../shared/ipc'
@@ -7,6 +7,7 @@ import {
DEFAULT_SETTINGS,
type AppSettings,
type HighlightRule,
type SystemSettings,
type TerminalSettings
} from '../shared/settings'
import type { TerminalTheme } from '../shared/theme'
@@ -14,6 +15,8 @@ import { broadcast } from './broadcast'
const settingsPath = (): string => join(app.getPath('userData'), 'settings.json')
const DEFAULT_SYSTEM: SystemSettings = { launchAtLogin: false, preventSleep: false }
const TERMINAL_KEYS = new Set(Object.keys(DEFAULT_SETTINGS.terminal) as (keyof TerminalSettings)[])
/** Light structural check for a persisted highlight rule. */
@@ -66,9 +69,53 @@ function deepMerge(raw: unknown): { settings: AppSettings; errors: string[] } {
}
}
let system: unknown = DEFAULT_SYSTEM
if (raw !== null && typeof raw === 'object') {
const sys = (raw as { system?: unknown }).system
if (sys !== null && typeof sys === 'object') {
const candidate = sys as Record<string, unknown>
system = {
launchAtLogin: candidate.launchAtLogin === true,
preventSleep: candidate.preventSleep === true
}
}
}
const themes: TerminalTheme[] = Array.isArray(customThemes) ? (customThemes as TerminalTheme[]) : []
return { settings: { terminal, customThemes: themes, highlightRules: sanitizeRules(highlightRules) }, errors }
return {
settings: {
terminal,
customThemes: themes,
highlightRules: sanitizeRules(highlightRules),
system: system as SystemSettings
},
errors
}
}
// ---- real system side effects ------------------------------------------------
let sleepBlockerId: number | undefined
/** Apply OS-level effects of the system settings (login item, sleep blocker). */
function applySystemSettings(system: SystemSettings): void {
try {
app.setLoginItemSettings({ openAtLogin: system.launchAtLogin })
} catch (err) {
console.error('[settings] setLoginItemSettings failed', err)
}
if (system.preventSleep && sleepBlockerId === undefined) {
sleepBlockerId = powerSaveBlocker.start('prevent-app-suspension')
} else if (!system.preventSleep && sleepBlockerId !== undefined) {
powerSaveBlocker.stop(sleepBlockerId)
sleepBlockerId = undefined
}
}
/** Apply system side effects for the settings loaded at startup. */
export function applyStartupSystemSettings(settings: AppSettings): void {
applySystemSettings(settings.system)
}
export function loadSettings(): AppSettings {
@@ -80,7 +127,8 @@ export function loadSettings(): AppSettings {
return {
terminal: { ...DEFAULT_SETTINGS.terminal },
customThemes: [...DEFAULT_SETTINGS.customThemes],
highlightRules: DEFAULT_HIGHLIGHT_RULES.map((rule) => ({ ...rule }))
highlightRules: DEFAULT_HIGHLIGHT_RULES.map((rule) => ({ ...rule })),
system: { ...DEFAULT_SYSTEM }
}
}
}
@@ -89,8 +137,10 @@ export function saveSettings(next: AppSettings): AppSettings {
const merged = deepMerge({
terminal: next.terminal,
customThemes: next.customThemes,
highlightRules: next.highlightRules
highlightRules: next.highlightRules,
system: next.system ?? DEFAULT_SYSTEM
}).settings
applySystemSettings(merged.system)
const pretty = JSON.stringify(merged, null, 2)
mkdirSync(app.getPath('userData'), { recursive: true })
+3 -2
View File
@@ -3,7 +3,7 @@ import { Button, Modal, Tabs } from 'antd'
import type { TabsProps } from 'antd'
import { DEFAULT_SETTINGS } from '@shared/settings'
import { useSettingsStore } from './store'
import { CursorSettingsTab, FontSettingsTab, RenderSettingsTab } from './SettingsTabs'
import { CursorSettingsTab, FontSettingsTab, RenderSettingsTab, SystemSettingsTab } from './SettingsTabs'
import { ThemeSettingsTab } from './ThemeSettingsTab'
import { HighlightTab } from './HighlightTab'
import { ThemeEditor } from '../theme/editor/ThemeEditor'
@@ -34,7 +34,8 @@ export function SettingsDialog({ open, onClose }: SettingsDialogProps): React.JS
onCreateTheme={() => setEditorState({ open: true, themeId: undefined })}
/>
)
}
},
{ key: 'system', label: '系统', children: <SystemSettingsTab /> }
]
const closeEditor = (): void => setEditorState((prev) => (prev.open ? { ...prev, open: false } : prev))
@@ -282,6 +282,35 @@ export function RenderSettingsTab(): React.JSX.Element {
)
}
export function SystemSettingsTab(): React.JSX.Element {
const settings = useSettingsStore((s) => s.settings)
const updateSystem = useSettingsStore((s) => s.updateSystem)
return (
<div className="settings-pane">
<SettingRow
label="开机时自启动"
desc="开机时自动启动软件(开发模式下注册的是开发版程序)"
control={
<Switch
checked={settings.system.launchAtLogin}
onChange={(checked) => void updateSystem({ launchAtLogin: checked })}
/>
}
/>
<SettingRow
label="阻止系统休眠"
desc="开启后 OpenTerminal 将阻止系统自动休眠;关闭则允许系统按系统设置休眠"
control={
<Switch
checked={settings.system.preventSleep}
onChange={(checked) => void updateSystem({ preventSleep: checked })}
/>
}
/>
</div>
)
}
/**
* Live preview of the current font settings, rendered as an `ls -la`-style
* terminal screen with ANSI-colored tokens on the active theme background.
+16 -1
View File
@@ -1,4 +1,4 @@
import type { AppSettings, TerminalSettings } from '@shared/settings'
import type { AppSettings, SystemSettings, TerminalSettings } from '@shared/settings'
import { DEFAULT_SETTINGS } from '@shared/settings'
import type { TerminalTheme } from '@shared/theme'
import { getThemeById } from '@shared/theme'
@@ -15,6 +15,8 @@ export interface SettingsState {
setCustomThemes: (themes: AppSettings['customThemes']) => Promise<void>
/** replace highlightRules array and persist */
setHighlightRules: (rules: AppSettings['highlightRules']) => Promise<void>
/** shallow-merge into settings.system and persist */
updateSystem: (partial: Partial<SystemSettings>) => Promise<void>
}
/** unsubscriber for the cross-window SETTINGS_CHANGED listener; held module-level so hydrate() is idempotent */
@@ -86,6 +88,19 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
console.error('[settings] setHighlightRules failed, rolling back', err)
set({ settings: prev })
}
},
updateSystem: async (partial) => {
const prev = get().settings
const next: AppSettings = { ...prev, system: { ...prev.system, ...partial } }
// optimistic update; the SETTINGS_CHANGED echo will re-set the same value (idempotent)
set({ settings: next })
try {
await window.api.saveSettings(next)
} catch (err) {
console.error('[settings] updateSystem failed, rolling back', err)
set({ settings: prev })
}
}
}))
+10 -1
View File
@@ -36,12 +36,20 @@ export interface TerminalSettings {
pasteRiskConfirm: boolean
}
export interface SystemSettings {
/** register the app to launch at OS login */
launchAtLogin: boolean
/** hold a powerSaveBlocker so the OS never auto-suspends while running */
preventSleep: boolean
}
export interface AppSettings {
terminal: TerminalSettings
/** user-created themes; builtin themes are immutable and live in shared/theme.ts */
customThemes: TerminalTheme[]
/** terminal keyword highlight rules, applied at write time in priority order */
highlightRules: HighlightRule[]
system: SystemSettings
}
const RULE = (
@@ -86,5 +94,6 @@ export const DEFAULT_SETTINGS: AppSettings = {
pasteRiskConfirm: true
},
customThemes: [],
highlightRules: DEFAULT_HIGHLIGHT_RULES
highlightRules: DEFAULT_HIGHLIGHT_RULES,
system: { launchAtLogin: false, preventSleep: false }
}