mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/mihomo-party-org/clash-party.git
synced 2026-09-20 08:03:39 +08:00
feat: add lightweight mode behavior toggle
This commit is contained in:
@@ -90,6 +90,31 @@ function hasCoreProcess(): boolean {
|
||||
return Boolean(child && !child.killed && child.exitCode === null && child.signalCode === null)
|
||||
}
|
||||
|
||||
async function stopPidFileCore(): Promise<void> {
|
||||
const pidPath = path.join(dataDir(), 'core.pid')
|
||||
if (!existsSync(pidPath)) return
|
||||
|
||||
const pidString = await readFile(pidPath, 'utf-8').catch(() => '')
|
||||
const pid = parseInt(pidString.trim())
|
||||
if (!isNaN(pid)) {
|
||||
try {
|
||||
process.kill(pid, 0)
|
||||
process.kill(pid, 'SIGINT')
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
try {
|
||||
process.kill(pid, 0)
|
||||
process.kill(pid, 'SIGKILL')
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
await rm(pidPath).catch(() => {})
|
||||
}
|
||||
|
||||
// 初始化核心文件监听
|
||||
export function initCoreWatcher(): void {
|
||||
if (coreWatcher) return
|
||||
@@ -183,18 +208,8 @@ async function prepareCore(detached: boolean, skipStop = false): Promise<CoreCon
|
||||
|
||||
const { 'log-level': logLevel = 'info' as LogLevel, tun } = mihomoConfig
|
||||
|
||||
// 清理旧进程
|
||||
const pidPath = path.join(dataDir(), 'core.pid')
|
||||
if (existsSync(pidPath)) {
|
||||
const pid = parseInt(await readFile(pidPath, 'utf-8'))
|
||||
try {
|
||||
process.kill(pid, 'SIGINT')
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
await rm(pidPath)
|
||||
}
|
||||
}
|
||||
// 清理轻量模式遗留的后台核心
|
||||
await stopPidFileCore()
|
||||
|
||||
// 管理 Smart 内核覆写配置
|
||||
await manageSmartOverride()
|
||||
@@ -416,6 +431,7 @@ export async function stopCore(force = false): Promise<void> {
|
||||
managerLogger.warn('Failed to refresh axios instance:', error)
|
||||
}
|
||||
|
||||
await stopPidFileCore()
|
||||
await cleanupSocketFile()
|
||||
}
|
||||
|
||||
@@ -477,18 +493,7 @@ export async function keepCoreAlive(): Promise<void> {
|
||||
// 退出但保持核心运行
|
||||
export async function quitWithoutCore(): Promise<void> {
|
||||
managerLogger.info(`Starting lightweight mode on platform: ${process.platform}`)
|
||||
|
||||
try {
|
||||
await startCore(true)
|
||||
if (child?.pid) {
|
||||
await writeFile(path.join(dataDir(), 'core.pid'), child.pid.toString())
|
||||
managerLogger.info(`Core started in lightweight mode with PID: ${child.pid}`)
|
||||
}
|
||||
} catch (e) {
|
||||
managerLogger.error('Failed to start core in lightweight mode:', e)
|
||||
safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
|
||||
}
|
||||
|
||||
await keepCoreAlive()
|
||||
await startMonitor(true)
|
||||
managerLogger.info('Exiting main process, core will continue running in background')
|
||||
app.exit()
|
||||
|
||||
@@ -116,6 +116,10 @@ export function setupAppLifecycle(): void {
|
||||
)
|
||||
}
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
// Keep the app and tray alive when lightweight tray mode destroys the renderer window.
|
||||
})
|
||||
|
||||
app.on('before-quit', async (e) => {
|
||||
e.preventDefault()
|
||||
await cleanupBeforeExit()
|
||||
|
||||
@@ -39,6 +39,9 @@ export const defaultConfig: IAppConfig = {
|
||||
connectionDirection: 'asc',
|
||||
connectionOrderBy: 'time',
|
||||
useSubStore: DEFAULT_USE_SUB_STORE,
|
||||
autoQuitWithoutCore: false,
|
||||
autoQuitWithoutCoreDelay: 60,
|
||||
autoQuitWithoutCoreMode: 'core',
|
||||
proxyDisplayMode: 'simple',
|
||||
proxyDisplayOrder: 'default',
|
||||
autoCheckUpdate: true,
|
||||
|
||||
@@ -52,13 +52,26 @@ function ensureVisibleOnScreen(state: WindowState): WindowState {
|
||||
|
||||
export let mainWindow: BrowserWindow | null = null
|
||||
let quitTimeout: NodeJS.Timeout | null = null
|
||||
let createWindowPromise: Promise<void> | null = null
|
||||
type AutoQuitWithoutCoreMode = NonNullable<IAppConfig['autoQuitWithoutCoreMode']>
|
||||
|
||||
export async function createWindow(): Promise<void> {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) return
|
||||
if (createWindowPromise) return createWindowPromise
|
||||
|
||||
createWindowPromise = createWindowInternal().finally(() => {
|
||||
createWindowPromise = null
|
||||
})
|
||||
return createWindowPromise
|
||||
}
|
||||
|
||||
async function createWindowInternal(): Promise<void> {
|
||||
const {
|
||||
useWindowFrame = false,
|
||||
silentStart = false,
|
||||
autoQuitWithoutCore = false,
|
||||
autoQuitWithoutCoreDelay = 60
|
||||
autoQuitWithoutCoreDelay = 60,
|
||||
autoQuitWithoutCoreMode = 'core'
|
||||
} = await getAppConfig()
|
||||
|
||||
const savedState = ensureVisibleOnScreen(loadWindowState())
|
||||
@@ -97,7 +110,8 @@ export async function createWindow(): Promise<void> {
|
||||
setupWindowEvents(mainWindow, {
|
||||
silentStart,
|
||||
autoQuitWithoutCore,
|
||||
autoQuitWithoutCoreDelay
|
||||
autoQuitWithoutCoreDelay,
|
||||
autoQuitWithoutCoreMode
|
||||
})
|
||||
|
||||
if (is.dev) {
|
||||
@@ -115,14 +129,16 @@ interface WindowConfig {
|
||||
silentStart: boolean
|
||||
autoQuitWithoutCore: boolean
|
||||
autoQuitWithoutCoreDelay: number
|
||||
autoQuitWithoutCoreMode: AutoQuitWithoutCoreMode
|
||||
}
|
||||
|
||||
function setupWindowEvents(window: BrowserWindow, config: WindowConfig): void {
|
||||
const { silentStart, autoQuitWithoutCore, autoQuitWithoutCoreDelay } = config
|
||||
const { silentStart, autoQuitWithoutCore, autoQuitWithoutCoreDelay, autoQuitWithoutCoreMode } =
|
||||
config
|
||||
|
||||
window.on('ready-to-show', () => {
|
||||
if (autoQuitWithoutCore && !window.isVisible()) {
|
||||
scheduleQuitWithoutCore(autoQuitWithoutCoreDelay)
|
||||
scheduleQuitWithoutCore(autoQuitWithoutCoreDelay, autoQuitWithoutCoreMode)
|
||||
}
|
||||
|
||||
// 开发模式下始终显示窗口
|
||||
@@ -148,6 +164,7 @@ function setupWindowEvents(window: BrowserWindow, config: WindowConfig): void {
|
||||
const {
|
||||
autoQuitWithoutCore = false,
|
||||
autoQuitWithoutCoreDelay = 60,
|
||||
autoQuitWithoutCoreMode = 'core',
|
||||
useDockIcon = true
|
||||
} = await getAppConfig()
|
||||
|
||||
@@ -156,7 +173,13 @@ function setupWindowEvents(window: BrowserWindow, config: WindowConfig): void {
|
||||
}
|
||||
|
||||
if (autoQuitWithoutCore) {
|
||||
scheduleQuitWithoutCore(autoQuitWithoutCoreDelay)
|
||||
scheduleQuitWithoutCore(autoQuitWithoutCoreDelay, autoQuitWithoutCoreMode)
|
||||
}
|
||||
})
|
||||
|
||||
window.on('closed', () => {
|
||||
if (mainWindow === window) {
|
||||
mainWindow = null
|
||||
}
|
||||
})
|
||||
|
||||
@@ -176,9 +199,20 @@ function setupWindowEvents(window: BrowserWindow, config: WindowConfig): void {
|
||||
})
|
||||
}
|
||||
|
||||
function scheduleQuitWithoutCore(delaySeconds: number): void {
|
||||
function scheduleQuitWithoutCore(
|
||||
delaySeconds: number,
|
||||
mode: AutoQuitWithoutCoreMode = 'core'
|
||||
): void {
|
||||
clearQuitTimeout()
|
||||
quitTimeout = setTimeout(async () => {
|
||||
if (mode === 'tray') {
|
||||
if (mainWindow && !mainWindow.isVisible()) {
|
||||
mainWindow.destroy()
|
||||
hideDockIcon()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
await quitWithoutCore()
|
||||
}, delaySeconds * 1000)
|
||||
}
|
||||
@@ -191,7 +225,10 @@ export function clearQuitTimeout(): void {
|
||||
}
|
||||
|
||||
export function triggerMainWindow(force?: boolean): void {
|
||||
if (!mainWindow) return
|
||||
if (!mainWindow || mainWindow.isDestroyed()) {
|
||||
showMainWindow()
|
||||
return
|
||||
}
|
||||
|
||||
getAppConfig()
|
||||
.then(({ triggerMainWindowBehavior = 'toggle' }) => {
|
||||
@@ -209,11 +246,20 @@ export function triggerMainWindow(force?: boolean): void {
|
||||
}
|
||||
|
||||
export function showMainWindow(): void {
|
||||
if (mainWindow) {
|
||||
clearQuitTimeout()
|
||||
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
clearQuitTimeout()
|
||||
mainWindow.show()
|
||||
mainWindow.focusOnWebView()
|
||||
return
|
||||
}
|
||||
|
||||
void createWindow().then(() => {
|
||||
clearQuitTimeout()
|
||||
mainWindow?.show()
|
||||
mainWindow?.focusOnWebView()
|
||||
})
|
||||
}
|
||||
|
||||
export function closeMainWindow(): void {
|
||||
|
||||
@@ -69,6 +69,7 @@ const GeneralConfig: React.FC = () => {
|
||||
useWindowFrame = false,
|
||||
autoQuitWithoutCore = false,
|
||||
autoQuitWithoutCoreDelay = 60,
|
||||
autoQuitWithoutCoreMode = 'core',
|
||||
customTheme = 'default.css',
|
||||
envType = [platform === 'win32' ? 'powershell' : 'bash'],
|
||||
autoCheckUpdate,
|
||||
@@ -237,27 +238,49 @@ const GeneralConfig: React.FC = () => {
|
||||
/>
|
||||
</SettingItem>
|
||||
{autoQuitWithoutCore && (
|
||||
<SettingItem title={t('settings.autoQuitWithoutCoreDelay')} divider>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
<>
|
||||
<SettingItem title={t('settings.autoQuitWithoutCoreMode')} divider>
|
||||
<Tabs
|
||||
size="sm"
|
||||
className="w-25"
|
||||
type="number"
|
||||
value={autoQuitWithoutCoreDelay.toString()}
|
||||
onValueChange={async (v: string) => {
|
||||
const num = parseInt(v)
|
||||
await patchAppConfig({ autoQuitWithoutCoreDelay: num })
|
||||
color="primary"
|
||||
selectedKey={autoQuitWithoutCoreMode}
|
||||
onSelectionChange={async (key) => {
|
||||
const mode = key as 'core' | 'tray'
|
||||
await patchAppConfig({ autoQuitWithoutCoreMode: mode })
|
||||
if (mode === 'core' && autoQuitWithoutCoreDelay < 5) {
|
||||
await patchAppConfig({ autoQuitWithoutCoreDelay: 5 })
|
||||
}
|
||||
}}
|
||||
onBlur={async (e) => {
|
||||
let num = parseInt(e.target.value)
|
||||
if (isNaN(num)) num = 5
|
||||
if (num < 5) num = 5
|
||||
await patchAppConfig({ autoQuitWithoutCoreDelay: num })
|
||||
}}
|
||||
/>
|
||||
<span className="text-default-500">{t('common.seconds')}</span>
|
||||
</div>
|
||||
</SettingItem>
|
||||
>
|
||||
<Tab key="core" title={t('settings.autoQuitWithoutCoreModeCore')} />
|
||||
<Tab key="tray" title={t('settings.autoQuitWithoutCoreModeTray')} />
|
||||
</Tabs>
|
||||
</SettingItem>
|
||||
<SettingItem title={t('settings.autoQuitWithoutCoreDelay')} divider>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
size="sm"
|
||||
className="w-25"
|
||||
type="number"
|
||||
value={autoQuitWithoutCoreDelay.toString()}
|
||||
onValueChange={async (v: string) => {
|
||||
const num = parseInt(v)
|
||||
if (!isNaN(num)) {
|
||||
await patchAppConfig({ autoQuitWithoutCoreDelay: num })
|
||||
}
|
||||
}}
|
||||
onBlur={async (e) => {
|
||||
const minDelay = autoQuitWithoutCoreMode === 'core' ? 5 : 0
|
||||
let num = parseInt(e.target.value)
|
||||
if (isNaN(num)) num = minDelay
|
||||
if (num < minDelay) num = minDelay
|
||||
await patchAppConfig({ autoQuitWithoutCoreDelay: num })
|
||||
}}
|
||||
/>
|
||||
<span className="text-default-500">{t('common.seconds')}</span>
|
||||
</div>
|
||||
</SettingItem>
|
||||
</>
|
||||
)}
|
||||
<SettingItem
|
||||
title={t('settings.envType')}
|
||||
|
||||
@@ -81,6 +81,9 @@
|
||||
"settings.silentStart": "Silent Start",
|
||||
"settings.autoQuitWithoutCore": "Auto Enable Light Mode",
|
||||
"settings.autoQuitWithoutCoreTooltip": "Automatically enter light mode after closing window for specified time",
|
||||
"settings.autoQuitWithoutCoreMode": "Light Mode Behavior",
|
||||
"settings.autoQuitWithoutCoreModeCore": "Keep Core Only",
|
||||
"settings.autoQuitWithoutCoreModeTray": "Close Renderer Only",
|
||||
"settings.subscriptionTimeout": "Subscription Timeout",
|
||||
"settings.autoQuitWithoutCoreDelay": "Light Mode Auto Enable Delay",
|
||||
"settings.envType": "Environment Variable Type",
|
||||
|
||||
@@ -78,6 +78,9 @@
|
||||
"settings.silentStart": "اجرای بیصدا",
|
||||
"settings.autoQuitWithoutCore": "ورود خودکار به حالت سبک",
|
||||
"settings.autoQuitWithoutCoreTooltip": "ورود خودکار به حالت سبک پس از بستن پنجره برای زمان مشخص",
|
||||
"settings.autoQuitWithoutCoreMode": "رفتار حالت سبک",
|
||||
"settings.autoQuitWithoutCoreModeCore": "فقط هسته را نگه دار",
|
||||
"settings.autoQuitWithoutCoreModeTray": "فقط رندرر را ببند",
|
||||
"settings.subscriptionTimeout": "زمان انتظار اشتراک",
|
||||
"settings.autoQuitWithoutCoreDelay": "تاخیر فعالسازی خودکار حالت سبک",
|
||||
"settings.envType": "نوع متغیر محیطی",
|
||||
|
||||
@@ -80,6 +80,9 @@
|
||||
"settings.silentStart": "Тихий запуск",
|
||||
"settings.autoQuitWithoutCore": "Автоматический облегчённый режим",
|
||||
"settings.autoQuitWithoutCoreTooltip": "Автоматически переходить в облегчённый режим после закрытия окна",
|
||||
"settings.autoQuitWithoutCoreMode": "Поведение облегчённого режима",
|
||||
"settings.autoQuitWithoutCoreModeCore": "Оставить только ядро",
|
||||
"settings.autoQuitWithoutCoreModeTray": "Закрыть только рендерер",
|
||||
"settings.subscriptionTimeout": "Таймаут подписки",
|
||||
"settings.autoQuitWithoutCoreDelay": "Задержка включения облегчённого режима",
|
||||
"settings.envType": "Тип переменных окружения",
|
||||
|
||||
@@ -81,6 +81,9 @@
|
||||
"settings.silentStart": "静默启动",
|
||||
"settings.autoQuitWithoutCore": "自动进入轻量模式",
|
||||
"settings.autoQuitWithoutCoreTooltip": "关闭窗口后指定时间自动进入轻量模式",
|
||||
"settings.autoQuitWithoutCoreMode": "轻量模式行为",
|
||||
"settings.autoQuitWithoutCoreModeCore": "仅保留内核",
|
||||
"settings.autoQuitWithoutCoreModeTray": "仅关闭渲染进程",
|
||||
"settings.subscriptionTimeout": "订阅超时时间",
|
||||
"settings.autoQuitWithoutCoreDelay": "轻量模式自动启用延迟",
|
||||
"settings.envType": "环境变量类型",
|
||||
|
||||
@@ -81,6 +81,9 @@
|
||||
"settings.silentStart": "靜默啟動",
|
||||
"settings.autoQuitWithoutCore": "自動進入輕量模式",
|
||||
"settings.autoQuitWithoutCoreTooltip": "關閉窗口後指定時間自動進入輕量模式",
|
||||
"settings.autoQuitWithoutCoreMode": "輕量模式行為",
|
||||
"settings.autoQuitWithoutCoreModeCore": "僅保留內核",
|
||||
"settings.autoQuitWithoutCoreModeTray": "僅關閉渲染進程",
|
||||
"settings.subscriptionTimeout": "訂閱逾時時間",
|
||||
"settings.autoQuitWithoutCoreDelay": "輕量模式自動啟用延遲",
|
||||
"settings.envType": "環境變數類型",
|
||||
|
||||
1
src/shared/types.d.ts
vendored
1
src/shared/types.d.ts
vendored
@@ -318,6 +318,7 @@ interface IAppConfig {
|
||||
subStoreBackendUploadCron?: string
|
||||
autoQuitWithoutCore?: boolean
|
||||
autoQuitWithoutCoreDelay?: number
|
||||
autoQuitWithoutCoreMode?: 'core' | 'tray'
|
||||
useCustomSubStore?: boolean
|
||||
useProxyInSubStore?: boolean
|
||||
mihomoCpuPriority?: Priority
|
||||
|
||||
Reference in New Issue
Block a user