diff --git a/src/main/core/manager.ts b/src/main/core/manager.ts index b50feef..6bd2c2d 100644 --- a/src/main/core/manager.ts +++ b/src/main/core/manager.ts @@ -4,7 +4,7 @@ import { promisify } from 'util' import path from 'path' import os from 'os' import { createWriteStream, existsSync } from 'fs' -import chokidar from 'chokidar' +import chokidar, { FSWatcher } from 'chokidar' import { app, ipcMain } from 'electron' import { mainWindow } from '../window' import { @@ -69,15 +69,38 @@ export { export { getDefaultDevice } from './dns' const execFilePromise = promisify(execFile) +const ctlParam = process.platform === 'win32' ? '-ext-ctl-pipe' : '-ext-ctl-unix' -chokidar.watch(path.join(mihomoCoreDir(), 'meta-update'), {}).on('unlinkDir', async () => { - try { - await stopCore(true) - await startCore() - } catch (e) { - safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`) +// 核心进程状态 +let child: ChildProcess +let retry = 10 +let isRestarting = false + +// 文件监听器 +let coreWatcher: FSWatcher | null = null + +// 初始化核心文件监听 +export function initCoreWatcher(): void { + if (coreWatcher) return + + coreWatcher = chokidar.watch(path.join(mihomoCoreDir(), 'meta-update'), {}) + coreWatcher.on('unlinkDir', async () => { + try { + await stopCore(true) + await startCore() + } catch (e) { + safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`) + } + }) +} + +// 清理核心文件监听 +export function cleanupCoreWatcher(): void { + if (coreWatcher) { + coreWatcher.close() + coreWatcher = null } -}) +} // 动态生成 IPC 路径 export const getMihomoIpcPath = (): string => { @@ -96,14 +119,20 @@ export const getMihomoIpcPath = (): string => { return `/tmp/mihomo-party-${uid}-${processId}.sock` } -const ctlParam = process.platform === 'win32' ? '-ext-ctl-pipe' : '-ext-ctl-unix' +// 核心配置接口 +interface CoreConfig { + corePath: string + workDir: string + ipcPath: string + logLevel: LogLevel + tunEnabled: boolean + autoSetDNS: boolean + cpuPriority: string + detached: boolean +} -let child: ChildProcess -let retry = 10 -let isRestarting = false - -export async function startCore(detached = false): Promise[]> { - // 合并配置读取,避免多次 await +// 准备核心配置 +async function prepareCore(detached: boolean): Promise { const [appConfig, mihomoConfig] = await Promise.all([ getAppConfig(), getControledMihomoConfig() @@ -116,7 +145,7 @@ export async function startCore(detached = false): Promise[]> { mihomoCpuPriority = 'PRIORITY_NORMAL' } = appConfig - const { 'log-level': logLevel, tun } = mihomoConfig + const { 'log-level': logLevel = 'info' as LogLevel, tun } = mihomoConfig // 清理旧进程 const pidPath = path.join(dataDir(), 'core.pid') @@ -131,8 +160,6 @@ export async function startCore(detached = false): Promise[]> { } } - const corePath = mihomoCorePath(core) - // 管理 Smart 内核覆写配置 await manageSmartOverride() @@ -142,6 +169,7 @@ export async function startCore(detached = false): Promise[]> { await stopCore() await cleanupSocketFile() + // 设置 DNS if (tun?.enable && autoSetDNS) { try { await setPublicDNS() @@ -151,39 +179,57 @@ export async function startCore(detached = false): Promise[]> { } // 获取动态 IPC 路径 - const dynamicIpcPath = getMihomoIpcPath() - managerLogger.info(`Using IPC path: ${dynamicIpcPath}`) + const ipcPath = getMihomoIpcPath() + managerLogger.info(`Using IPC path: ${ipcPath}`) if (process.platform === 'win32') { - await validateWindowsPipeAccess(dynamicIpcPath) + await validateWindowsPipeAccess(ipcPath) } - // 内核日志输出 + return { + corePath: mihomoCorePath(core), + workDir: diffWorkDir ? mihomoProfileWorkDir(current) : mihomoWorkDir(), + ipcPath, + logLevel, + tunEnabled: tun?.enable ?? false, + autoSetDNS, + cpuPriority: mihomoCpuPriority, + detached + } +} + +// 启动核心进程 +function spawnCoreProcess(config: CoreConfig): ChildProcess { + const { corePath, workDir, ipcPath, cpuPriority, detached } = config + const stdout = createWriteStream(coreLogPath(), { flags: 'a' }) const stderr = createWriteStream(coreLogPath(), { flags: 'a' }) - child = spawn( - corePath, - ['-d', diffWorkDir ? mihomoProfileWorkDir(current) : mihomoWorkDir(), ctlParam, dynamicIpcPath], - { - detached, - stdio: detached ? 'ignore' : undefined - } - ) + const proc = spawn(corePath, ['-d', workDir, ctlParam, ipcPath], { + detached, + stdio: detached ? 'ignore' : undefined + }) - if (process.platform === 'win32' && child.pid) { - os.setPriority(child.pid, os.constants.priority[mihomoCpuPriority]) + if (process.platform === 'win32' && proc.pid) { + os.setPriority(proc.pid, os.constants.priority[cpuPriority as keyof typeof os.constants.priority]) } - if (detached) { - managerLogger.info( - `Core process detached successfully on ${process.platform}, PID: ${child.pid}` - ) - child.unref() - return [new Promise(() => {})] + if (!detached) { + proc.stdout?.pipe(stdout) + proc.stderr?.pipe(stderr) } - child.on('close', async (code, signal) => { + return proc +} + +// 设置核心进程事件监听 +function setupCoreListeners( + proc: ChildProcess, + logLevel: LogLevel, + resolve: (value: Promise[]) => void, + reject: (reason: unknown) => void +): void { + proc.on('close', async (code, signal) => { managerLogger.info(`Core closed, code: ${code}, signal: ${signal}`) if (isRestarting) { @@ -200,77 +246,96 @@ export async function startCore(detached = false): Promise[]> { } }) - child.stdout?.pipe(stdout) - child.stderr?.pipe(stderr) + proc.stdout?.on('data', async (data) => { + const str = data.toString() - return new Promise((resolve, reject) => { - child.stdout?.on('data', async (data) => { - const str = data.toString() + // TUN 权限错误 + if (str.includes('configure tun interface: operation not permitted')) { + patchControledMihomoConfig({ tun: { enable: false } }) + mainWindow?.webContents.send('controledMihomoConfigUpdated') + ipcMain.emit('updateTrayMenu') + reject(i18next.t('tun.error.tunPermissionDenied')) + return + } - if (str.includes('configure tun interface: operation not permitted')) { - patchControledMihomoConfig({ tun: { enable: false } }) - mainWindow?.webContents.send('controledMihomoConfigUpdated') - ipcMain.emit('updateTrayMenu') - reject(i18next.t('tun.error.tunPermissionDenied')) - } + // 控制器监听错误 + const isControllerError = + (process.platform !== 'win32' && str.includes('External controller unix listen error')) || + (process.platform === 'win32' && str.includes('External controller pipe listen error')) - const isControllerError = - (process.platform !== 'win32' && str.includes('External controller unix listen error')) || - (process.platform === 'win32' && str.includes('External controller pipe listen error')) + if (isControllerError) { + managerLogger.error('External controller listen error detected:', str) - if (isControllerError) { - managerLogger.error('External controller listen error detected:', str) - - if (process.platform === 'win32') { - managerLogger.info('Attempting Windows pipe cleanup and retry...') - try { - await cleanupWindowsNamedPipes() - await new Promise((r) => setTimeout(r, 2000)) - } catch (cleanupError) { - managerLogger.error('Pipe cleanup failed:', cleanupError) - } + if (process.platform === 'win32') { + managerLogger.info('Attempting Windows pipe cleanup and retry...') + try { + await cleanupWindowsNamedPipes() + await new Promise((r) => setTimeout(r, 2000)) + } catch (cleanupError) { + managerLogger.error('Pipe cleanup failed:', cleanupError) } - - reject(i18next.t('mihomo.error.externalControllerListenError')) } - const isApiReady = - (process.platform !== 'win32' && str.includes('RESTful API unix listening at')) || - (process.platform === 'win32' && str.includes('RESTful API pipe listening at')) + reject(i18next.t('mihomo.error.externalControllerListenError')) + return + } - if (isApiReady) { - resolve([ - new Promise((innerResolve) => { - child.stdout?.on('data', async (innerData) => { - if ( - innerData.toString().toLowerCase().includes('start initial compatible provider default') - ) { - try { - mainWindow?.webContents.send('groupsUpdated') - mainWindow?.webContents.send('rulesUpdated') - await uploadRuntimeConfig() - } catch { - // ignore - } - await patchMihomoConfig({ 'log-level': logLevel }) - innerResolve() + // API 就绪 + const isApiReady = + (process.platform !== 'win32' && str.includes('RESTful API unix listening at')) || + (process.platform === 'win32' && str.includes('RESTful API pipe listening at')) + + if (isApiReady) { + resolve([ + new Promise((innerResolve) => { + proc.stdout?.on('data', async (innerData) => { + if ( + innerData.toString().toLowerCase().includes('start initial compatible provider default') + ) { + try { + mainWindow?.webContents.send('groupsUpdated') + mainWindow?.webContents.send('rulesUpdated') + await uploadRuntimeConfig() + } catch { + // ignore } - }) + await patchMihomoConfig({ 'log-level': logLevel }) + innerResolve() + } }) - ]) + }) + ]) - await waitForCoreReady() - await getAxios(true) - await startMihomoTraffic() - await startMihomoConnections() - await startMihomoLogs() - await startMihomoMemory() - retry = 10 - } - }) + await waitForCoreReady() + await getAxios(true) + await startMihomoTraffic() + await startMihomoConnections() + await startMihomoLogs() + await startMihomoMemory() + retry = 10 + } }) } +// 启动核心 +export async function startCore(detached = false): Promise[]> { + const config = await prepareCore(detached) + child = spawnCoreProcess(config) + + if (detached) { + managerLogger.info( + `Core process detached successfully on ${process.platform}, PID: ${child.pid}` + ) + child.unref() + return [new Promise(() => {})] + } + + return new Promise((resolve, reject) => { + setupCoreListeners(child, config.logLevel, resolve, reject) + }) +} + +// 停止核心 export async function stopCore(force = false): Promise { try { if (!force) { @@ -299,6 +364,7 @@ export async function stopCore(force = false): Promise { await cleanupSocketFile() } +// 重启核心 export async function restartCore(): Promise { if (isRestarting) { managerLogger.info('Core restart already in progress, skipping duplicate request') @@ -316,6 +382,7 @@ export async function restartCore(): Promise { } } +// 保持核心运行 export async function keepCoreAlive(): Promise { try { await startCore(true) @@ -327,6 +394,7 @@ export async function keepCoreAlive(): Promise { } } +// 退出但保持核心运行 export async function quitWithoutCore(): Promise { managerLogger.info(`Starting lightweight mode on platform: ${process.platform}`) @@ -346,6 +414,7 @@ export async function quitWithoutCore(): Promise { app.exit() } +// 检查配置文件 async function checkProfile( current: string | undefined, core: string = 'mihomo', diff --git a/src/main/index.ts b/src/main/index.ts index 0431373..3edafd4 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -10,7 +10,8 @@ import { checkHighPrivilegeCore, restartAsAdmin, initAdminStatus, - checkAdminPrivileges + checkAdminPrivileges, + initCoreWatcher } from './core/manager' import { createTray } from './resolve/tray' import { init, initBasic, safeShowErrorBox } from './utils/init' @@ -149,6 +150,7 @@ app.whenReady().then(async () => { } try { + initCoreWatcher() const [startPromise] = await startCore() startPromise.then(async () => { await initProfileUpdater() diff --git a/src/main/lifecycle.ts b/src/main/lifecycle.ts index 14a978d..5c8bd32 100644 --- a/src/main/lifecycle.ts +++ b/src/main/lifecycle.ts @@ -3,7 +3,7 @@ import { promisify } from 'util' import { stat } from 'fs/promises' import { existsSync } from 'fs' import { app, powerMonitor } from 'electron' -import { stopCore } from './core/manager' +import { stopCore, cleanupCoreWatcher } from './core/manager' import { triggerSysProxy } from './sys/sysproxy' import { exePath } from './utils/dirs' @@ -56,12 +56,14 @@ export function setupPlatformSpecifics(): void { export function setupAppLifecycle(): void { app.on('before-quit', async (e) => { e.preventDefault() + cleanupCoreWatcher() await triggerSysProxy(false) await stopCore() app.exit() }) powerMonitor.on('shutdown', async () => { + cleanupCoreWatcher() triggerSysProxy(false) await stopCore() app.exit() diff --git a/src/renderer/src/utils/ipc.ts b/src/renderer/src/utils/ipc.ts index 4d65b0d..fc03204 100644 --- a/src/renderer/src/utils/ipc.ts +++ b/src/renderer/src/utils/ipc.ts @@ -12,189 +12,309 @@ async function invoke(channel: string, ...args: unknown[]): Promise { return checkIpcError(response) } -// Mihomo API -export const mihomoVersion = (): Promise => invoke('mihomoVersion') -export const mihomoCloseConnection = (id: string): Promise => - invoke('mihomoCloseConnection', id) -export const mihomoCloseAllConnections = (): Promise => invoke('mihomoCloseAllConnections') -export const mihomoRules = (): Promise => invoke('mihomoRules') -export const mihomoProxies = (): Promise => invoke('mihomoProxies') -export const mihomoGroups = (): Promise => invoke('mihomoGroups') -export const mihomoProxyProviders = (): Promise => - invoke('mihomoProxyProviders') -export const mihomoUpdateProxyProviders = (name: string): Promise => - invoke('mihomoUpdateProxyProviders', name) -export const mihomoRuleProviders = (): Promise => - invoke('mihomoRuleProviders') -export const mihomoUpdateRuleProviders = (name: string): Promise => - invoke('mihomoUpdateRuleProviders', name) -export const mihomoChangeProxy = (group: string, proxy: string): Promise => - invoke('mihomoChangeProxy', group, proxy) -export const mihomoUnfixedProxy = (group: string): Promise => - invoke('mihomoUnfixedProxy', group) -export const mihomoUpgradeGeo = (): Promise => invoke('mihomoUpgradeGeo') -export const mihomoUpgrade = (): Promise => invoke('mihomoUpgrade') -export const mihomoUpgradeUI = (): Promise => invoke('mihomoUpgradeUI') -export const mihomoUpgradeConfig = (): Promise => invoke('mihomoUpgradeConfig') -export const mihomoProxyDelay = (proxy: string, url?: string): Promise => - invoke('mihomoProxyDelay', proxy, url) -export const mihomoGroupDelay = (group: string, url?: string): Promise => - invoke('mihomoGroupDelay', group, url) -export const patchMihomoConfig = (patch: Partial): Promise => - invoke('patchMihomoConfig', patch) -export const mihomoSmartGroupWeights = (groupName: string): Promise> => - invoke('mihomoSmartGroupWeights', groupName) -export const mihomoSmartFlushCache = (configName?: string): Promise => - invoke('mihomoSmartFlushCache', configName) -export const getSmartOverrideContent = (): Promise => - invoke('getSmartOverrideContent') +// IPC API 类型定义 +interface IpcApi { + // Mihomo API + mihomoVersion: () => Promise + mihomoCloseConnection: (id: string) => Promise + mihomoCloseAllConnections: () => Promise + mihomoRules: () => Promise + mihomoProxies: () => Promise + mihomoGroups: () => Promise + mihomoProxyProviders: () => Promise + mihomoUpdateProxyProviders: (name: string) => Promise + mihomoRuleProviders: () => Promise + mihomoUpdateRuleProviders: (name: string) => Promise + mihomoChangeProxy: (group: string, proxy: string) => Promise + mihomoUnfixedProxy: (group: string) => Promise + mihomoUpgradeGeo: () => Promise + mihomoUpgrade: () => Promise + mihomoUpgradeUI: () => Promise + mihomoUpgradeConfig: () => Promise + mihomoProxyDelay: (proxy: string, url?: string) => Promise + mihomoGroupDelay: (group: string, url?: string) => Promise + patchMihomoConfig: (patch: Partial) => Promise + mihomoSmartGroupWeights: (groupName: string) => Promise> + mihomoSmartFlushCache: (configName?: string) => Promise + getSmartOverrideContent: () => Promise + // AutoRun + checkAutoRun: () => Promise + enableAutoRun: () => Promise + disableAutoRun: () => Promise + // Config + getAppConfig: (force?: boolean) => Promise + patchAppConfig: (patch: Partial) => Promise + getControledMihomoConfig: (force?: boolean) => Promise> + patchControledMihomoConfig: (patch: Partial) => Promise + resetAppConfig: () => Promise + // Profile + getProfileConfig: (force?: boolean) => Promise + setProfileConfig: (config: IProfileConfig) => Promise + getCurrentProfileItem: () => Promise + getProfileItem: (id: string | undefined) => Promise + getProfileStr: (id: string) => Promise + setProfileStr: (id: string, str: string) => Promise + addProfileItem: (item: Partial) => Promise + removeProfileItem: (id: string) => Promise + updateProfileItem: (item: IProfileItem) => Promise + changeCurrentProfile: (id: string) => Promise + addProfileUpdater: (item: IProfileItem) => Promise + removeProfileUpdater: (id: string) => Promise + // Override + getOverrideConfig: (force?: boolean) => Promise + setOverrideConfig: (config: IOverrideConfig) => Promise + getOverrideItem: (id: string) => Promise + addOverrideItem: (item: Partial) => Promise + removeOverrideItem: (id: string) => Promise + updateOverrideItem: (item: IOverrideItem) => Promise + getOverride: (id: string, ext: 'js' | 'yaml' | 'log') => Promise + setOverride: (id: string, ext: 'js' | 'yaml', str: string) => Promise + // File + getFileStr: (path: string) => Promise + setFileStr: (path: string, str: string) => Promise + convertMrsRuleset: (path: string, behavior: string) => Promise + getRuntimeConfig: () => Promise + getRuntimeConfigStr: () => Promise + getRuleStr: (id: string) => Promise + setRuleStr: (id: string, str: string) => Promise + getFilePath: (ext: string[]) => Promise + readTextFile: (filePath: string) => Promise + openFile: (type: 'profile' | 'override', id: string, ext?: 'yaml' | 'js') => Promise + // Core + restartCore: () => Promise + startMonitor: () => Promise + quitWithoutCore: () => Promise + // System + triggerSysProxy: (enable: boolean) => Promise + checkTunPermissions: () => Promise + grantTunPermissions: () => Promise + manualGrantCorePermition: () => Promise + checkAdminPrivileges: () => Promise + restartAsAdmin: () => Promise + checkMihomoCorePermissions: () => Promise + checkHighPrivilegeCore: () => Promise + showTunPermissionDialog: () => Promise + showErrorDialog: (title: string, message: string) => Promise + openUWPTool: () => Promise + setupFirewall: () => Promise + getInterfaces: () => Promise> + setNativeTheme: (theme: 'system' | 'light' | 'dark') => Promise + copyEnv: (type: 'bash' | 'cmd' | 'powershell') => Promise + // Update + checkUpdate: () => Promise + downloadAndInstallUpdate: (version: string) => Promise + getVersion: () => Promise + platform: () => Promise + fetchMihomoTags: ( + forceRefresh?: boolean + ) => Promise<{ name: string; zipball_url: string; tarball_url: string }[]> + installSpecificMihomoCore: (version: string) => Promise + clearMihomoVersionCache: () => Promise + // Backup + webdavBackup: () => Promise + webdavRestore: (filename: string) => Promise + listWebdavBackups: () => Promise + webdavDelete: (filename: string) => Promise + reinitWebdavBackupScheduler: () => Promise + exportLocalBackup: () => Promise + importLocalBackup: () => Promise + // SubStore + startSubStoreFrontendServer: () => Promise + stopSubStoreFrontendServer: () => Promise + startSubStoreBackendServer: () => Promise + stopSubStoreBackendServer: () => Promise + downloadSubStore: () => Promise + subStorePort: () => Promise + subStoreFrontendPort: () => Promise + subStoreSubs: () => Promise + subStoreCollections: () => Promise + // Theme + resolveThemes: () => Promise<{ key: string; label: string; content: string }[]> + fetchThemes: () => Promise + importThemes: (files: string[]) => Promise + readTheme: (theme: string) => Promise + writeTheme: (theme: string, css: string) => Promise + // Tray + showTrayIcon: () => Promise + closeTrayIcon: () => Promise + updateTrayIcon: () => Promise + // Window + showMainWindow: () => Promise + closeMainWindow: () => Promise + triggerMainWindow: () => Promise + showFloatingWindow: () => Promise + closeFloatingWindow: () => Promise + showContextMenu: () => Promise + setAlwaysOnTop: (alwaysOnTop: boolean) => Promise + isAlwaysOnTop: () => Promise + openDevTools: () => Promise + createHeapSnapshot: () => Promise + // Shortcut + registerShortcut: (oldShortcut: string, newShortcut: string, action: string) => Promise + // Misc + getGistUrl: () => Promise + getImageDataURL: (url: string) => Promise + relaunchApp: () => Promise + quitApp: () => Promise +} -// AutoRun -export const checkAutoRun = (): Promise => invoke('checkAutoRun') -export const enableAutoRun = (): Promise => invoke('enableAutoRun') -export const disableAutoRun = (): Promise => invoke('disableAutoRun') +// 使用 Proxy 自动生成 IPC 调用 +const ipc = new Proxy({} as IpcApi, { + get: + (_: IpcApi, channel: K) => + (...args: Parameters) => + invoke(channel, ...args) +}) -// Config -export const getAppConfig = (force = false): Promise => invoke('getAppConfig', force) -export const patchAppConfig = (patch: Partial): Promise => - invoke('patchAppConfig', patch) -export const getControledMihomoConfig = (force = false): Promise> => - invoke('getControledMihomoConfig', force) -export const patchControledMihomoConfig = (patch: Partial): Promise => - invoke('patchControledMihomoConfig', patch) -export const resetAppConfig = (): Promise => invoke('resetAppConfig') +// 导出所有 IPC 方法 +export const { + // Mihomo API + mihomoVersion, + mihomoCloseConnection, + mihomoCloseAllConnections, + mihomoRules, + mihomoProxies, + mihomoGroups, + mihomoProxyProviders, + mihomoUpdateProxyProviders, + mihomoRuleProviders, + mihomoUpdateRuleProviders, + mihomoChangeProxy, + mihomoUnfixedProxy, + mihomoUpgradeGeo, + mihomoUpgrade, + mihomoUpgradeUI, + mihomoUpgradeConfig, + mihomoProxyDelay, + mihomoGroupDelay, + patchMihomoConfig, + mihomoSmartGroupWeights, + mihomoSmartFlushCache, + getSmartOverrideContent, + // AutoRun + checkAutoRun, + enableAutoRun, + disableAutoRun, + // Config + getAppConfig, + patchAppConfig, + getControledMihomoConfig, + patchControledMihomoConfig, + resetAppConfig, + // Profile + getProfileConfig, + setProfileConfig, + getCurrentProfileItem, + getProfileItem, + getProfileStr, + setProfileStr, + addProfileItem, + removeProfileItem, + updateProfileItem, + changeCurrentProfile, + addProfileUpdater, + removeProfileUpdater, + // Override + getOverrideConfig, + setOverrideConfig, + getOverrideItem, + addOverrideItem, + removeOverrideItem, + updateOverrideItem, + getOverride, + setOverride, + // File + getFileStr, + setFileStr, + convertMrsRuleset, + getRuntimeConfig, + getRuntimeConfigStr, + getRuleStr, + setRuleStr, + getFilePath, + readTextFile, + openFile, + // Core + restartCore, + startMonitor, + quitWithoutCore, + // System + triggerSysProxy, + checkTunPermissions, + grantTunPermissions, + manualGrantCorePermition, + checkAdminPrivileges, + restartAsAdmin, + checkMihomoCorePermissions, + checkHighPrivilegeCore, + showTunPermissionDialog, + showErrorDialog, + openUWPTool, + setupFirewall, + getInterfaces, + setNativeTheme, + copyEnv, + // Update + checkUpdate, + downloadAndInstallUpdate, + getVersion, + fetchMihomoTags, + installSpecificMihomoCore, + clearMihomoVersionCache, + // Backup + webdavBackup, + webdavRestore, + listWebdavBackups, + webdavDelete, + reinitWebdavBackupScheduler, + exportLocalBackup, + importLocalBackup, + // SubStore + startSubStoreFrontendServer, + stopSubStoreFrontendServer, + startSubStoreBackendServer, + stopSubStoreBackendServer, + downloadSubStore, + subStorePort, + subStoreFrontendPort, + subStoreSubs, + subStoreCollections, + // Theme + resolveThemes, + fetchThemes, + importThemes, + readTheme, + writeTheme, + // Tray + showTrayIcon, + closeTrayIcon, + updateTrayIcon, + // Window + showMainWindow, + closeMainWindow, + triggerMainWindow, + showFloatingWindow, + closeFloatingWindow, + showContextMenu, + setAlwaysOnTop, + isAlwaysOnTop, + openDevTools, + createHeapSnapshot, + // Shortcut + registerShortcut, + // Misc + getGistUrl, + getImageDataURL, + relaunchApp, + quitApp +} = ipc -// Profile -export const getProfileConfig = (force = false): Promise => - invoke('getProfileConfig', force) -export const setProfileConfig = (config: IProfileConfig): Promise => - invoke('setProfileConfig', config) -export const getCurrentProfileItem = (): Promise => invoke('getCurrentProfileItem') -export const getProfileItem = (id: string | undefined): Promise => - invoke('getProfileItem', id) -export const getProfileStr = (id: string): Promise => invoke('getProfileStr', id) -export const setProfileStr = (id: string, str: string): Promise => - invoke('setProfileStr', id, str) -export const addProfileItem = (item: Partial): Promise => - invoke('addProfileItem', item) -export const removeProfileItem = (id: string): Promise => invoke('removeProfileItem', id) -export const updateProfileItem = (item: IProfileItem): Promise => - invoke('updateProfileItem', item) -export const changeCurrentProfile = (id: string): Promise => - invoke('changeCurrentProfile', id) -export const addProfileUpdater = (item: IProfileItem): Promise => - invoke('addProfileUpdater', item) -export const removeProfileUpdater = (id: string): Promise => - invoke('removeProfileUpdater', id) +// platform 需要重命名导出 +export const getPlatform = ipc.platform -// Override -export const getOverrideConfig = (force = false): Promise => - invoke('getOverrideConfig', force) -export const setOverrideConfig = (config: IOverrideConfig): Promise => - invoke('setOverrideConfig', config) -export const getOverrideItem = (id: string): Promise => - invoke('getOverrideItem', id) -export const addOverrideItem = (item: Partial): Promise => - invoke('addOverrideItem', item) -export const removeOverrideItem = (id: string): Promise => invoke('removeOverrideItem', id) -export const updateOverrideItem = (item: IOverrideItem): Promise => - invoke('updateOverrideItem', item) -export const getOverride = (id: string, ext: 'js' | 'yaml' | 'log'): Promise => - invoke('getOverride', id, ext) -export const setOverride = (id: string, ext: 'js' | 'yaml', str: string): Promise => - invoke('setOverride', id, ext, str) - -// File -export const getFileStr = (path: string): Promise => invoke('getFileStr', path) -export const setFileStr = (path: string, str: string): Promise => - invoke('setFileStr', path, str) -export const convertMrsRuleset = (path: string, behavior: string): Promise => - invoke('convertMrsRuleset', path, behavior) -export const getRuntimeConfig = (): Promise => invoke('getRuntimeConfig') -export const getRuntimeConfigStr = (): Promise => invoke('getRuntimeConfigStr') -export const getRuleStr = (id: string): Promise => invoke('getRuleStr', id) -export const setRuleStr = (id: string, str: string): Promise => invoke('setRuleStr', id, str) -export const getFilePath = (ext: string[]): Promise => - invoke('getFilePath', ext) -export const readTextFile = (filePath: string): Promise => invoke('readTextFile', filePath) -export const openFile = ( - type: 'profile' | 'override', - id: string, - ext?: 'yaml' | 'js' -): Promise => invoke('openFile', type, id, ext) - -// Core -export const restartCore = (): Promise => invoke('restartCore') -export const startMonitor = (): Promise => invoke('startMonitor') -export const quitWithoutCore = (): Promise => invoke('quitWithoutCore') - -// System -export const triggerSysProxy = (enable: boolean): Promise => invoke('triggerSysProxy', enable) -export const checkTunPermissions = (): Promise => invoke('checkTunPermissions') -export const grantTunPermissions = (): Promise => invoke('grantTunPermissions') -export const manualGrantCorePermition = (): Promise => invoke('manualGrantCorePermition') -export const checkAdminPrivileges = (): Promise => invoke('checkAdminPrivileges') -export const restartAsAdmin = (): Promise => invoke('restartAsAdmin') -export const checkMihomoCorePermissions = (): Promise => - invoke('checkMihomoCorePermissions') -export const checkHighPrivilegeCore = (): Promise => invoke('checkHighPrivilegeCore') -export const showTunPermissionDialog = (): Promise => invoke('showTunPermissionDialog') -export const showErrorDialog = (title: string, message: string): Promise => - invoke('showErrorDialog', title, message) -export const openUWPTool = (): Promise => invoke('openUWPTool') -export const setupFirewall = (): Promise => invoke('setupFirewall') -export const getInterfaces = (): Promise> => - invoke('getInterfaces') -export const setNativeTheme = (theme: 'system' | 'light' | 'dark'): Promise => - invoke('setNativeTheme', theme) -export const copyEnv = (type: 'bash' | 'cmd' | 'powershell'): Promise => - invoke('copyEnv', type) - -// Update -export const checkUpdate = (): Promise => invoke('checkUpdate') -export const downloadAndInstallUpdate = (version: string): Promise => - invoke('downloadAndInstallUpdate', version) -export const getVersion = (): Promise => invoke('getVersion') -export const getPlatform = (): Promise => invoke('platform') -export const fetchMihomoTags = ( - forceRefresh = false -): Promise<{ name: string; zipball_url: string; tarball_url: string }[]> => - invoke('fetchMihomoTags', forceRefresh) -export const installSpecificMihomoCore = (version: string): Promise => - invoke('installSpecificMihomoCore', version) -export const clearMihomoVersionCache = (): Promise => invoke('clearMihomoVersionCache') - -// Backup -export const webdavBackup = (): Promise => invoke('webdavBackup') -export const webdavRestore = (filename: string): Promise => invoke('webdavRestore', filename) -export const listWebdavBackups = (): Promise => invoke('listWebdavBackups') -export const webdavDelete = (filename: string): Promise => invoke('webdavDelete', filename) -export const reinitWebdavBackupScheduler = (): Promise => - invoke('reinitWebdavBackupScheduler') -export const exportLocalBackup = (): Promise => invoke('exportLocalBackup') -export const importLocalBackup = (): Promise => invoke('importLocalBackup') - -// SubStore -export const startSubStoreFrontendServer = (): Promise => - invoke('startSubStoreFrontendServer') -export const stopSubStoreFrontendServer = (): Promise => invoke('stopSubStoreFrontendServer') -export const startSubStoreBackendServer = (): Promise => invoke('startSubStoreBackendServer') -export const stopSubStoreBackendServer = (): Promise => invoke('stopSubStoreBackendServer') -export const downloadSubStore = (): Promise => invoke('downloadSubStore') -export const subStorePort = (): Promise => invoke('subStorePort') -export const subStoreFrontendPort = (): Promise => invoke('subStoreFrontendPort') -export const subStoreSubs = (): Promise => invoke('subStoreSubs') -export const subStoreCollections = (): Promise => invoke('subStoreCollections') - -// Theme -export const resolveThemes = (): Promise<{ key: string; label: string; content: string }[]> => - invoke('resolveThemes') -export const fetchThemes = (): Promise => invoke('fetchThemes') -export const importThemes = (files: string[]): Promise => invoke('importThemes', files) -export const readTheme = (theme: string): Promise => invoke('readTheme', theme) -export const writeTheme = (theme: string, css: string): Promise => - invoke('writeTheme', theme, css) +// 需要特殊处理的函数 +// applyTheme: 防抖处理,避免频繁调用 let applyThemeRunning = false let pendingTheme: string | null = null @@ -216,21 +336,7 @@ export async function applyTheme(theme: string): Promise { } } -// Tray -export const showTrayIcon = (): Promise => invoke('showTrayIcon') -export const closeTrayIcon = (): Promise => invoke('closeTrayIcon') -export const updateTrayIcon = (): Promise => invoke('updateTrayIcon') -export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: boolean): void { - window.electron.ipcRenderer.invoke('updateTrayIconImmediate', sysProxyEnabled, tunEnabled) -} - -// Window -export const showMainWindow = (): Promise => invoke('showMainWindow') -export const closeMainWindow = (): Promise => invoke('closeMainWindow') -export const triggerMainWindow = (): Promise => invoke('triggerMainWindow') -export const showFloatingWindow = (): Promise => invoke('showFloatingWindow') -export const closeFloatingWindow = (): Promise => invoke('closeFloatingWindow') -export const showContextMenu = (): Promise => invoke('showContextMenu') +// setTitleBarOverlay: 需要静默处理不支持的平台 export async function setTitleBarOverlay(overlay: TitleBarOverlayOptions): Promise { try { await invoke('setTitleBarOverlay', overlay) @@ -238,21 +344,8 @@ export async function setTitleBarOverlay(overlay: TitleBarOverlayOptions): Promi // Not supported on this platform } } -export const setAlwaysOnTop = (alwaysOnTop: boolean): Promise => - invoke('setAlwaysOnTop', alwaysOnTop) -export const isAlwaysOnTop = (): Promise => invoke('isAlwaysOnTop') -export const openDevTools = (): Promise => invoke('openDevTools') -export const createHeapSnapshot = (): Promise => invoke('createHeapSnapshot') -// Shortcut -export const registerShortcut = ( - oldShortcut: string, - newShortcut: string, - action: string -): Promise => invoke('registerShortcut', oldShortcut, newShortcut, action) - -// Misc -export const getGistUrl = (): Promise => invoke('getGistUrl') -export const getImageDataURL = (url: string): Promise => invoke('getImageDataURL', url) -export const relaunchApp = (): Promise => invoke('relaunchApp') -export const quitApp = (): Promise => invoke('quitApp') +// updateTrayIconImmediate: 同步调用,不等待结果 +export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: boolean): void { + window.electron.ipcRenderer.invoke('updateTrayIconImmediate', sysProxyEnabled, tunEnabled) +}