import { app, BrowserWindow, desktopCapturer, ipcMain, session, shell, protocol, globalShortcut } from "electron"; import { optimizer } from "@electron-toolkit/utils"; import path from "node:path" import fs from "node:fs"; import { execSync, exec as execCallback } from "child_process"; import os from "node:os"; import { promisify } from "node:util"; /** process.js 必须位于非依赖项的顶部 */ import { isDummy } from "../lib/process"; import { AppEnv, AppRuntime } from "../mapi/env"; import { MAPI } from "../mapi/main"; import { initSystemTemplates } from "../mapi/db/initSystemTemplates"; import { initBuiltinAvatars } from "../mapi/db/initBuiltinAvatars"; import { initAppConfig, initVoicePreCache } from "../mapi/config/initAppConfig"; import { ConfigMain } from "../mapi/config/main"; import { setUpdaterWindow, default as updaterIndex } from "../mapi/updater/index"; import { WindowConfig } from "../config/window"; import { AppConfig } from "../../src/config"; import Log from "../mapi/log/main"; import { ConfigMenu } from "../config/menu"; import { ConfigLang, t } from "../config/lang"; import { ConfigContextMenu } from "../config/contextMenu"; import { preloadDefault, rendererLoadPath } from "../lib/env-main"; import { Page } from "../page"; import { ConfigTray } from "../config/tray"; import { icnsLogoPath, icoLogoPath, logoPath } from "../config/icon"; import { isDev, isMac, isPackaged, shouldEnableDevTools } from "../lib/env"; import { executeHooks } from "../lib/hooks"; import { DevToolsManager } from "../lib/devtools"; import { AppsMain } from "../mapi/app/main"; import { ServerMain } from "../mapi/server/main"; import CryptoManager from "../lib/crypto"; import AntiDebugManager from "../lib/antiDebug"; import { getCommonResourcePath, getModelPath } from "../lib/resource-path"; import { ensurePythonSetup, getMacOSPythonPath } from "../lib/python-setup"; import Store from "../lib/store"; import { checkResourceBundles } from "../mapi/resourceManager"; import { fetchElectronSystemConfig } from "../mapi/systemConfig"; // 窗口状态保存路径 const windowStateFile = () => path.join(app.getPath("userData"), "window-state.json"); // 加载窗口状态 function loadWindowState() { try { if (fs.existsSync(windowStateFile())) { const data = fs.readFileSync(windowStateFile(), "utf-8"); return JSON.parse(data); } } catch (error) { Log.error("加载窗口状态失败:", error); } return null; } // 保存窗口状态 function saveWindowState(window: BrowserWindow) { try { const bounds = window.getBounds(); const state = { width: bounds.width, height: bounds.height, x: bounds.x, y: bounds.y, isMaximized: window.isMaximized(), }; fs.writeFileSync(windowStateFile(), JSON.stringify(state, null, 2)); } catch (error) { Log.error("保存窗口状态失败:", error); } } const isDummyNew = isDummy; // 临时屏蔽防破解,用于调试 // if (process.env["ELECTRON_ENV_PROD"]) { // DevToolsManager.setEnable(false); // } process.on("uncaughtException", reason => { let error: any = reason; if (error instanceof Error) { error = [error.message, error.stack].join("\n"); } Log.error("UncaughtException", error); }); process.on("unhandledRejection", reason => { Log.error("UnhandledRejection", reason); let error: any = reason; if (error instanceof Error) { error = [error.message, error.stack].join("\n"); } Log.error("UnhandledRejection", error); }); app.disableHardwareAcceleration(); // 注册自定义协议为特权协议(必须在 app.whenReady() 之前) protocol.registerSchemesAsPrivileged([ { scheme: 'font', privileges: { bypassCSP: true, supportFetchAPI: true, corsEnabled: true, stream: true } } ]); // Set application name for Windows 10+ notifications if (process.platform === "win32") app.setAppUserModelId(app.getName()); if (!app.requestSingleInstanceLock()) { app.quit(); process.exit(0); } // 🔧 便携版(绿色版)检测和配置 // 便携版应该把数据存储在应用本地目录,而不是 AppData const isPortable = (() => { // 🟢 默认启用便携模式:所有数据存储在安装目录 console.log('[Portable Detection] 便携模式已默认启用'); return true; /* // 原检测逻辑(已禁用) try { // 方法1: electron-builder 的便携版会设置这个环境变量 if (process.env.PORTABLE_EXECUTABLE_DIR || process.env.PORTABLE_EXECUTABLE_FILE) { return true; } // 方法2: 检查可执行文件名是否包含 portable if (process.execPath && process.execPath.toLowerCase().includes('portable')) { return true; } // 方法3: 检查应用根目录是否存在 portable.txt 标记文件 if (process.env.APP_ROOT) { const portableMarker = path.join(process.env.APP_ROOT, 'portable.txt'); if (fs.existsSync(portableMarker)) { return true; } } } catch (error) { console.error('[Portable Detection] 检测失败:', error); } return false; */ })(); const resolveInstallRoot = () => { if (isDev) { return process.cwd(); } return path.dirname(process.execPath); }; const resolveResourceBundleRoot = (installRoot: string) => { if (!isDev) { const packagedBundleRoot = path.join(process.resourcesPath, 'resources-bundles'); if (fs.existsSync(packagedBundleRoot)) { return packagedBundleRoot; } } return path.join(installRoot, 'resources-bundles'); }; const configurePortablePaths = () => { const installRoot = resolveInstallRoot(); const dataRoot = path.join(installRoot, 'data'); const userDataRoot = path.join(dataRoot, 'userData'); const sessionDataRoot = path.join(dataRoot, 'session'); const cacheRoot = path.join(dataRoot, 'cache'); const tempRoot = path.join(dataRoot, 'temp'); const resourceStateRoot = path.join(dataRoot, 'resource-state'); const resourceBundleRoot = resolveResourceBundleRoot(installRoot); for (const dir of [dataRoot, userDataRoot, sessionDataRoot, cacheRoot, tempRoot, resourceStateRoot, resourceBundleRoot]) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } AppEnv.installRoot = installRoot; AppEnv.dataRoot = dataRoot; AppEnv.userData = userDataRoot; AppEnv.sessionData = sessionDataRoot; AppEnv.cacheRoot = cacheRoot; AppEnv.tempRoot = tempRoot; AppEnv.resourceStateRoot = resourceStateRoot; AppEnv.resourceBundleRoot = resourceBundleRoot; app.setPath('userData', userDataRoot); app.setPath('sessionData', sessionDataRoot); try { app.setPath('temp', tempRoot); } catch (error) { console.warn('[Portable Mode] temp 路径设置失败:', error); } try { app.setPath('cache', cacheRoot); } catch (error) { console.warn('[Portable Mode] cache 路径设置失败:', error); } process.env.TMPDIR = tempRoot; process.env.TEMP = tempRoot; process.env.TMP = tempRoot; process.env.PLAYWRIGHT_BROWSERS_PATH = resourceBundleRoot; console.log('[Portable Mode] 🟢 便携版模式已启用'); console.log('[Portable Mode] 安装目录:', installRoot); console.log('[Portable Mode] 用户数据目录:', userDataRoot); console.log('[Portable Mode] 资源目录:', resourceBundleRoot); }; if (isPortable) { try { configurePortablePaths(); } catch (error) { console.error('[Portable Mode] 设置失败:', error); } } const hasSplashWindow = false; // 🔧 初始化 AppEnv - 确保所有路径都有效(支持中文路径) try { // 多重降级方案确保 appRoot 总是有效 let appRoot = process.env.APP_ROOT; if (!appRoot || typeof appRoot !== 'string') { // 🔧 修复:process.resourcesPath 是 resources 目录本身 // 如果 AppEnv.appRoot 意为应用根目录(包含 resources 的目录),则需要取父目录 appRoot = path.dirname(process.resourcesPath); console.log('[AppEnv] 使用 resourcesPath 的父目录:', appRoot); } if (!appRoot || typeof appRoot !== 'string') { appRoot = app.getAppPath(); console.log('[AppEnv] 使用 getAppPath:', appRoot); } if (!appRoot || typeof appRoot !== 'string') { // 最终降级:使用可执行文件目录 appRoot = path.dirname(process.execPath); console.log('[AppEnv] 使用 execPath dirname:', appRoot); } AppEnv.appRoot = appRoot; console.log('[AppEnv] ✅ appRoot 已设置:', AppEnv.appRoot); } catch (error) { console.error('[AppEnv] 设置 appRoot 失败:', error); AppEnv.appRoot = path.dirname(process.execPath); } try { AppEnv.appData = app.getPath("appData"); AppEnv.userData = AppEnv.userData || app.getPath("userData"); AppEnv.sessionData = AppEnv.sessionData || app.getPath("sessionData"); console.log('[AppEnv] ✅ userData 已设置:', AppEnv.userData); if (!AppEnv.installRoot) { AppEnv.installRoot = resolveInstallRoot(); } if (!AppEnv.dataRoot) { if (AppEnv.userData && typeof AppEnv.userData === 'string') { if (AppEnv.userData.endsWith('/userData') || AppEnv.userData.endsWith('\\userData')) { AppEnv.dataRoot = path.dirname(AppEnv.userData); } else if (AppEnv.userData.endsWith('/data') || AppEnv.userData.endsWith('\\data')) { AppEnv.dataRoot = AppEnv.userData; } else { AppEnv.dataRoot = path.join(AppEnv.userData, "data"); } } else { AppEnv.dataRoot = path.join(app.getPath("temp"), "zhenqianba-data"); console.warn('[AppEnv] userData 不可用,使用临时目录:', AppEnv.dataRoot); } } AppEnv.cacheRoot = AppEnv.cacheRoot || path.join(AppEnv.dataRoot, 'cache'); AppEnv.tempRoot = AppEnv.tempRoot || path.join(AppEnv.dataRoot, 'temp'); AppEnv.resourceStateRoot = AppEnv.resourceStateRoot || path.join(AppEnv.dataRoot, 'resource-state'); AppEnv.resourceBundleRoot = AppEnv.resourceBundleRoot || resolveResourceBundleRoot(AppEnv.installRoot); console.log('[AppEnv] ✅ dataRoot 已设置:', AppEnv.dataRoot); } catch (error) { console.error('[AppEnv] 设置路径失败:', error); throw error; } for (const dir of [ AppEnv.dataRoot, path.join(AppEnv.dataRoot, "logs"), path.join(AppEnv.dataRoot, "storage"), AppEnv.cacheRoot, AppEnv.tempRoot, AppEnv.resourceStateRoot, AppEnv.resourceBundleRoot, AppEnv.sessionData, ]) { if (dir && !fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } if (isPackaged) { checkResourceBundles({ installMissing: false }).then((result) => { if (result.pending.length > 0) { Log.warn('[ResourceManager] bundles pending', result.pending); } if (result.installed.length > 0) { Log.info('[ResourceManager] bundles installed', result.installed); } if (result.missingRequired.length > 0) { Log.warn('[ResourceManager] required bundles missing', result.missingRequired); } }).catch((error) => { Log.warn('[ResourceManager] initial check failed', error); }); } AppEnv.isInit = true; // 🔧 设置 U2NET_HOME 环境变量,指向打包的模型目录 // ⚠️ 只使用程序自带的 U2-Net 模型,不依赖用户目录下载 const modelsPath = getModelPath('u2net.onnx'); process.env.U2NET_HOME = path.dirname(modelsPath); Log.info("U2NET_HOME 设置完成", { U2NET_HOME: process.env.U2NET_HOME }); ConfigContextMenu.init(); Log.info("Starting"); Log.info("LaunchInfo", { isPackaged, userData: AppEnv.userData, dataRoot: AppEnv.dataRoot, }); async function createWindow() { let icon = logoPath; if (process.platform === "win32") { icon = icoLogoPath; } else if (process.platform === "darwin") { icon = icnsLogoPath; } if (hasSplashWindow) { AppRuntime.splashWindow = new BrowserWindow({ title: AppConfig.title, width: 600, height: 350, transparent: true, frame: false, alwaysOnTop: true, hasShadow: true, skipTaskbar: true, }); rendererLoadPath(AppRuntime.splashWindow, "splash.html"); } // 加载保存的窗口状态 const savedState = loadWindowState(); const windowOptions: any = { show: !hasSplashWindow, title: AppConfig.title, ...(!isPackaged ? { icon } : {}), frame: false, transparent: false, hasShadow: true, center: !savedState, // 如果有保存的状态,不居中 minWidth: WindowConfig.minWidth, minHeight: WindowConfig.minHeight, width: savedState?.width || WindowConfig.initWidth, height: savedState?.height || WindowConfig.initHeight, backgroundColor: await AppsMain.defaultDarkModeBackgroundColor(), titleBarStyle: "hidden", trafficLightPosition: { x: 10, y: 11 }, webPreferences: { preload: preloadDefault, // Warning: Enable nodeIntegration and disable contextIsolation is not secure in production nodeIntegration: true, webSecurity: false, webviewTag: true, // Consider using contextBridge.exposeInMainWorld // Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation contextIsolation: false, // sandbox: false, }, }; // 如果有保存的位置,设置窗口位置 if (savedState?.x !== undefined && savedState?.y !== undefined) { windowOptions.x = savedState.x; windowOptions.y = savedState.y; } AppRuntime.mainWindow = new BrowserWindow(windowOptions); setUpdaterWindow(AppRuntime.mainWindow); // 如果之前是最大化状态,恢复最大化 if (savedState?.isMaximized) { AppRuntime.mainWindow.maximize(); } // 监听窗口大小和位置变化,保存状态 let saveTimeout: NodeJS.Timeout | null = null; const scheduleSave = () => { if (saveTimeout) { clearTimeout(saveTimeout); } saveTimeout = setTimeout(() => { if (AppRuntime.mainWindow && !AppRuntime.mainWindow.isDestroyed()) { saveWindowState(AppRuntime.mainWindow); } }, 500); // 延迟500ms保存,避免频繁写入 }; AppRuntime.mainWindow.on("resize", scheduleSave); AppRuntime.mainWindow.on("move", scheduleSave); AppRuntime.mainWindow.on("maximize", scheduleSave); AppRuntime.mainWindow.on("unmaximize", scheduleSave); AppRuntime.mainWindow.on("closed", () => { if (saveTimeout) { clearTimeout(saveTimeout); } AppRuntime.mainWindow = null; }); AppRuntime.mainWindow.on("show", async () => { await executeHooks(AppRuntime.mainWindow, "Show"); }); AppRuntime.mainWindow.on("hide", async () => { await executeHooks(AppRuntime.mainWindow, "Hide"); }); if (isMac) { AppRuntime.mainWindow.on("close", event => { // @ts-ignore if (!app.quitForce && !isDev) { executeHooks(AppRuntime.mainWindow, "ShowQuitConfirmDialog"); event.preventDefault(); } }); } AppRuntime.mainWindow.on("enter-full-screen", () => { executeHooks(AppRuntime.mainWindow, "EnterFullScreen"); }); AppRuntime.mainWindow.on("leave-full-screen", () => { executeHooks(AppRuntime.mainWindow, "LeaveFullScreen"); }); rendererLoadPath(AppRuntime.mainWindow, "index.html"); DevToolsManager.register("Main", AppRuntime.mainWindow); AppRuntime.mainWindow.webContents.on("did-finish-load", async () => { try { const mapiCheck = await AppRuntime.mainWindow!.webContents.executeJavaScript( `JSON.stringify({ hasMapi: !!window['$mapi'], hasIpcRenderer: !!window['ipcRenderer'], hasPage: !!window['__page'], windowDollarKeys: Object.keys(window).filter(k => k.startsWith('$')) })` ); console.log('[Main:did-finish-load] preload检查:', mapiCheck); } catch(e: any) { console.error('[Main:did-finish-load] preload检查失败:', e.message); } if (hasSplashWindow) { AppRuntime.mainWindow?.show(); setTimeout(() => { try { AppRuntime.splashWindow?.close(); AppRuntime.splashWindow = null; // AppRuntime.mainWindow.webContents.openDevTools({ // mode: 'detach', // }) } catch (e) { } }, 1000); } // 🔧 延迟调用 Page.ready,直到 MAPI 初始化完成 // 等待 MAPI.init() 完成再通知前端 console.log('[Main:did-finish-load] 页面加载完成,等待 MAPI 初始化...'); // 给 MAPI 初始化一个机会完成 // 如果 MAPI 已经初始化,这会立即执行 // 否则等待最多 5 秒钟 let mapiInitCheck = 0; const mapiCheckInterval = setInterval(() => { mapiInitCheck++; if (AppRuntime.mapiInitialized || mapiInitCheck > 50) { // 5 秒超时 clearInterval(mapiCheckInterval); if (!AppRuntime.mapiInitialized) { console.warn('[Main:did-finish-load] ⚠️ MAPI 初始化超时,但继续启动页面'); } console.log('[Main:did-finish-load] 调用 Page.ready("main")'); Page.ready("main"); DevToolsManager.autoShow(AppRuntime.mainWindow); if (isPackaged) { setTimeout(async () => { try { const checkAtLaunch = ConfigMain.get("updaterCheckAtLaunch", "yes"); console.log('[Main] auto check update, setting:', checkAtLaunch); if (checkAtLaunch !== "no") { await updaterIndex.checkForUpdate(); } } catch (e: any) { console.error('[Main] auto check update failed:', e.message); } }, 8000); } } }, 100); // 🔐 启用防破解保护(生产环境且非调试模式时强制启用) if (!shouldEnableDevTools || process.env.ELECTRON_ENABLE_ANTI_DEBUG === 'true') { console.log('[Main] 启用防破解保护...'); AntiDebugManager.enableFullProtection(AppRuntime.mainWindow, { disableDevTools: true, disableShortcuts: true, disableContextMenu: true, disableMenuBar: !shouldEnableDevTools, // 调试模式保留菜单栏 interceptHotkeys: true }); } else { console.log('[Main] 调试模式:防破解保护已禁用'); console.log('[Main] 提示:生产环境中设置环境变量 ELECTRON_DEBUG_MODE=true 可启用DevTools'); } // 🆕 隐藏调试快捷键 Ctrl+Shift+Alt+D(即使在生产环境也能启用DevTools) globalShortcut.register('Ctrl+Shift+Alt+D', () => { console.log('[Main] 隐藏调试快捷键触发'); if (AppRuntime.mainWindow && !AppRuntime.mainWindow.webContents.isDevToolsOpened()) { // 使用 AntiDebugManager 的 restoreDevTools 方法,绕过防护 AntiDebugManager.restoreDevTools(AppRuntime.mainWindow); console.log('[Main] ✅ DevTools 已通过隐藏快捷键打开'); } else { AppRuntime.mainWindow?.webContents.closeDevTools(); console.log('[Main] DevTools 已关闭'); } }); }); AppRuntime.mainWindow.webContents.setWindowOpenHandler(({ url }) => { if (url.startsWith("https:")) shell.openExternal(url); return { action: "deny" }; }); } app.whenReady() .then(() => { // 注册自定义 font:// 协议 protocol.registerFileProtocol('font', (request, callback) => { try { // 从 URL 中提取文件路径 // font://path/to/font.ttf -> path/to/font.ttf const url = request.url.substring(7); // 移除 'font://' const fontPath = decodeURIComponent(url); Log.info('[FontProtocol] Loading font:', fontPath); // 验证文件存在 if (!fs.existsSync(fontPath)) { Log.error('[FontProtocol] Font file not found:', fontPath); callback({ error: -6 }); // FILE_NOT_FOUND return; } // 返回文件路径 callback({ path: fontPath }); } catch (error) { Log.error('[FontProtocol] Error loading font:', error); callback({ error: -2 }); // FAILED } }); session.defaultSession.setDisplayMediaRequestHandler((request, callback) => { desktopCapturer.getSources({ types: ["screen"] }).then(sources => { // Grant access to the first screen found. callback({ video: sources[0], audio: "loopback" }); }); }); }) .then(ConfigLang.readyAsync) .then(async () => { // 初始化加密管理器 try { console.log("[Main:init] 初始化加密管理器..."); await CryptoManager.initializeKeys(); console.log("[Main:init] ✅ 加密管理器初始化完成"); } catch (error) { console.error("[Main:init] 加密管理器初始化失败:", error); Log.error("加密管理器初始化失败:", error); } // 在创建窗口前先初始化MAPI(包括数据库) try { console.log("[Main:init] Starting MAPI initialization"); console.log("[Main:init] AppEnv.isInit =", AppEnv.isInit); console.log("[Main:init] AppEnv.dataRoot =", AppEnv.dataRoot); console.log("[Main:init] AppEnv.userData =", AppEnv.userData); await MAPI.init(); console.log("[Main:init] MAPI initialized successfully"); Log.info("MAPI initialized successfully"); // 🔧 标记 MAPI 已初始化,前端可以开始使用 API AppRuntime.mapiInitialized = true; console.log('[Main:init] 设置 AppRuntime.mapiInitialized = true'); // 初始化系统默认模板 console.log("[Main:init] Initializing system templates..."); await initSystemTemplates(); console.log("[Main:init] System templates initialized"); console.log("[Main:init] Initializing builtin avatars..."); await initBuiltinAvatars(); console.log("[Main:init] Builtin avatars initialized"); // 初始化应用配置(字幕模板等) console.log("[Main:init] Initializing app config..."); await initAppConfig(); console.log("[Main:init] App config initialized"); console.log("[Main:init] Pre-fetching system config..."); try { await fetchElectronSystemConfig(); console.log("[Main:init] System config fetched"); } catch(e: any) { console.warn("[Main:init] System config fetch failed:", e.message); } console.log("[Main:init] Initializing voice preview cache..."); await initVoicePreCache(); console.log("[Main:init] Voice preview cache initialized"); if (process.platform !== 'win32') { console.log("[Main:init] Starting macOS/Linux Python setup (background)..."); ensurePythonSetup().then((pythonPath) => { if (pythonPath) { console.log("[Main:init] ✅ Python setup complete:", pythonPath); } else { console.warn("[Main:init] Python setup returned null"); } }).catch((err) => { console.warn("[Main:init] Python setup failed (non-fatal):", err.message); }); } } catch (error) { console.error("[Main:init] MAPI initialization error:", error); Log.error("MAPI initialization failed:", error); // 🔧 即使失败也标记为已尝试初始化,允许前端继续(为了诊断) AppRuntime.mapiInitialized = true; // 不抛出错误,允许应用继续(为了诊断) } }) .then(() => { MAPI.ready(); ConfigMenu.ready(); ConfigTray.ready(); // 🔐 加密相关 IPC 处理器 ipcMain.handle("Crypto:getPublicKey", () => { try { return { success: true, publicKey: CryptoManager.getPublicKey() }; } catch (error: any) { return { success: false, error: error.message }; } }); ipcMain.handle("Crypto:encryptRSA", (event, data: string) => { try { return { success: true, encrypted: CryptoManager.encryptRSA(data) }; } catch (error: any) { return { success: false, error: error.message }; } }); ipcMain.handle("Crypto:decryptRSA", (event, encryptedData: string) => { try { return { success: true, decrypted: CryptoManager.decryptRSA(encryptedData) }; } catch (error: any) { return { success: false, error: error.message }; } }); ipcMain.handle("Crypto:encryptAES", (event, data: string, key: string) => { try { return { success: true, encrypted: CryptoManager.encryptAES(data, key) }; } catch (error: any) { return { success: false, error: error.message }; } }); ipcMain.handle("Crypto:decryptAES", (event, encryptedData: string, key: string) => { try { return { success: true, decrypted: CryptoManager.decryptAES(encryptedData, key) }; } catch (error: any) { return { success: false, error: error.message }; } }); ipcMain.handle("Crypto:sha256", (event, data: string) => { try { return { success: true, hash: CryptoManager.sha256(data) }; } catch (error: any) { return { success: false, error: error.message }; } }); ipcMain.handle("Crypto:generateRandomKey", (event, length: number = 32) => { try { return { success: true, key: CryptoManager.generateRandomKey(length) }; } catch (error: any) { return { success: false, error: error.message }; } }); ipcMain.handle("Crypto:sign", (event, data: string) => { try { return { success: true, signature: CryptoManager.sign(data) }; } catch (error: any) { return { success: false, error: error.message }; } }); ipcMain.handle("Crypto:verify", (event, data: string, signature: string) => { try { return { success: true, valid: CryptoManager.verify(data, signature) }; } catch (error: any) { return { success: false, error: error.message }; } }); // 🔧 System 相关 IPC 处理器 - 获取硬件信息用于设备指纹 const execAsync = promisify(execCallback); ipcMain.handle("System:getSystemInfo", async () => { try { console.log('[System:getSystemInfo] IPC 被调用'); // 获取磁盘序列号(Windows) let diskSerial = 'unknown'; try { if (process.platform === 'win32') { console.log('[System:getSystemInfo] 执行 wmic diskdrive 命令...'); const { stdout } = await execAsync('wmic diskdrive get SerialNumber'); console.log('[System:getSystemInfo] wmic 输出:', stdout); const lines = stdout.trim().split('\n'); if (lines.length > 1) { diskSerial = lines[1].trim(); console.log('[System:getSystemInfo] 获得的 diskSerial:', diskSerial); } } else if (process.platform === 'darwin') { const { stdout } = await execAsync('ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformSerialNumber'); const match = stdout.match(/"IOPlatformSerialNumber"\s*=\s*"([^"]+)"/); if (match) { diskSerial = match[1]; } } } catch (e) { console.warn('[System:getSystemInfo] 获取磁盘序列号失败:', e); Log.warn('[System:getSystemInfo] 获取磁盘序列号失败:', e); } const systemInfo = { hostname: os.hostname(), platform: os.platform(), arch: os.arch(), cpuModel: os.cpus()[0]?.model || 'unknown', diskSerial }; console.log('[System:getSystemInfo] 返回系统信息:', systemInfo); return systemInfo; } catch (error: any) { console.error("[System:getSystemInfo] 失败:", error); Log.error("[System:getSystemInfo] 失败:", error); return null; } }); ipcMain.handle("System:getHardwareSerial", async () => { try { console.log('[System:getHardwareSerial] IPC 被调用'); const hardwareSerial: any = { motherboard: 'unknown', bios: 'unknown' }; if (process.platform === 'win32') { // 获取主板序列号 try { console.log('[System:getHardwareSerial] 执行 wmic baseboard 命令...'); const { stdout: mbStdout } = await execAsync('wmic baseboard get SerialNumber'); console.log('[System:getHardwareSerial] baseboard 输出:', mbStdout); const mbLines = mbStdout.trim().split('\n'); if (mbLines.length > 1) { hardwareSerial.motherboard = mbLines[1].trim(); } } catch (e) { console.warn('[System:getHardwareSerial] 获取主板序列号失败:', e); Log.warn('[System:getHardwareSerial] 获取主板序列号失败:', e); } // 获取BIOS序列号 try { console.log('[System:getHardwareSerial] 执行 wmic bios 命令...'); const { stdout: biosStdout } = await execAsync('wmic bios get SerialNumber'); console.log('[System:getHardwareSerial] bios 输出:', biosStdout); const biosLines = biosStdout.trim().split('\n'); if (biosLines.length > 1) { hardwareSerial.bios = biosLines[1].trim(); } } catch (e) { console.warn('[System:getHardwareSerial] 获取BIOS序列号失败:', e); Log.warn('[System:getHardwareSerial] 获取BIOS序列号失败:', e); } } else if (process.platform === 'darwin') { try { const { stdout } = await execAsync('ioreg -rd1 -c IOPlatformExpertDevice'); const serialMatch = stdout.match(/"IOPlatformSerialNumber"\s*=\s*"([^"]+)"/); if (serialMatch) { hardwareSerial.motherboard = serialMatch[1]; hardwareSerial.bios = serialMatch[1]; } } catch (e) { console.warn('[System:getHardwareSerial] macOS 获取序列号失败:', e); } } console.log('[System:getHardwareSerial] 返回硬件序列号:', hardwareSerial); return hardwareSerial; } catch (error: any) { console.error("[System:getHardwareSerial] 失败:", error); Log.error("[System:getHardwareSerial] 失败:", error); return null; } }); // 🔧 IPC处理器:杀死模型进程 ipcMain.handle("killAllModelProcesses", async (event, data: any) => { try { let enableGpuCleanup = false; try { const raw = ConfigMain.getSync('ipAgentSettings'); if (raw) { const s = typeof raw === 'string' ? JSON.parse(raw) : raw; enableGpuCleanup = !!s.enableGpuCleanup; } } catch {} if (!enableGpuCleanup) { Log.info("[IPC] 本地模型进程优化未开启,跳过进程清理"); return { success: true, message: "未开启,跳过" }; } const isWin = process.platform === 'win32'; const defaultProcs = isWin ? ["launcher.exe", "python.exe", "ffmpeg.exe", "ffprobe.exe"] : ["python3", "ffmpeg", "ffprobe"]; const processes = data?.processes || defaultProcs; const protectedCommands = ['api_server.py', 'LstmSync']; Log.info(`[IPC] 开始杀死进程: ${processes.join(", ")}`); for (const proc of processes) { try { if (isWin) { if (proc === 'python.exe') { const output = execSync(`powershell -Command "Get-CimInstance Win32_Process -Filter \\"Name = '${proc}'\\" | Select-Object ProcessId, CommandLine | ConvertTo-Json"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }); let procs = []; try { procs = JSON.parse(output); if (!Array.isArray(procs)) procs = [procs]; } catch { procs = []; } for (const p of procs) { if (!p || !p.ProcessId) continue; const cmd = (p.CommandLine || '').toLowerCase(); if (protectedCommands.some(kw => cmd.includes(kw.toLowerCase()))) { Log.info(`[IPC] 🛡️ 跳过保护进程 PID ${p.ProcessId}: ${cmd.substring(0, 100)}`); continue; } try { execSync(`taskkill /f /pid ${p.ProcessId} /t`, { stdio: 'ignore', shell: 'cmd.exe' }); Log.info(`[IPC] ✓ 已杀死: ${proc} PID ${p.ProcessId}`); } catch {} } } else { execSync(`taskkill /f /im ${proc} /t`, { stdio: 'ignore', shell: 'cmd.exe' }); Log.info(`[IPC] ✓ 已杀死: ${proc}`); } } else { execSync(`pkill -f "${proc}" 2>/dev/null || true`, { stdio: 'ignore', shell: '/bin/bash' }); Log.info(`[IPC] ✓ 已杀死: ${proc}`); } } catch (err: any) { Log.debug(`[IPC] ${proc} 不存在或已关闭`); } } Log.info("[IPC] ✓ 进程清理完成"); return { success: true, message: "进程已杀死" }; } catch (error: any) { Log.error("[IPC] 杀死进程失败:", error.message); return { success: false, error: error.message }; } }); // 🔧 IPC处理器:手动触发GPU清理(用于测试) ipcMain.handle("testGPUCleanup", async (event) => { try { console.log("[TestGPUCleanup] ========== 手动触发GPU清理 =========="); // 检查 GPU 进程 let gpuProcs = ''; try { gpuProcs = execSync(`nvidia-smi --query-compute-apps=pid --format=csv,noheader`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim(); console.log(`[TestGPUCleanup] GPU 进程: ${gpuProcs}`); } catch (e) { console.log(`[TestGPUCleanup] 没有GPU进程或nvidia-smi失败`); } if (gpuProcs) { const pidLines = gpuProcs.split('\n'); const pids = pidLines.map(p => p.trim()).filter(p => p && !isNaN(Number(p))); console.log(`[TestGPUCleanup] 找到 ${pids.length} 个GPU进程`); const targetServers = ['indextts', 'heygem']; const targetPids: string[] = []; for (const pid of pids) { try { const cmdline = execSync( `powershell -Command "Get-CimInstance Win32_Process -Filter \\"ProcessId = ${pid}\\" | Select-Object -ExpandProperty CommandLine"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] } ).toLowerCase(); if (targetServers.some(server => cmdline.includes(server))) { console.log(`[TestGPUCleanup] 🎯 发现目标进程 PID ${pid}`); targetPids.push(pid); } } catch (e) { console.log(`[TestGPUCleanup] 检查PID ${pid} 失败`); } } if (targetPids.length > 0) { const pidList = targetPids.join(','); console.log(`[TestGPUCleanup] 🔨 清理 ${targetPids.length} 个进程: ${pidList}`); execSync( `powershell -Command "Stop-Process -Id ${pidList} -Force -ErrorAction SilentlyContinue"`, { stdio: 'ignore' } ); console.log(`[TestGPUCleanup] ✓ 已清理 ${targetPids.length} 个进程`); } } // 调用清理脚本 try { const appRoot = process.env.APP_ROOT; if (appRoot) { const cleanupScript = path.join(appRoot, 'scripts', 'cleanup_gpu.py'); if (fs.existsSync(cleanupScript)) { const pythonExe = process.platform === 'win32' ? 'python.exe' : 'python3'; const pythonPath = path.join(getCommonResourcePath('python'), pythonExe); if (fs.existsSync(pythonPath)) { const result = execSync(`"${pythonPath}" "${cleanupScript}"`, { encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'ignore'] }); console.log(`[TestGPUCleanup] ✓ GPU显存清理完成`); if (result) { console.log(`[cleanup_gpu.py] ${result}`); } } } } } catch (e) { console.warn(`[TestGPUCleanup] GPU清理脚本失败: ${e}`); } console.log("[TestGPUCleanup] ========== GPU清理完成 =========="); return { success: true, message: "GPU清理完成,请查看控制台输出" }; } catch (error: any) { console.error("[TestGPUCleanup] 错误:", error); return { success: false, error: error.message }; } }); // 添加智能体登录窗口打开处理器 ipcMain.handle("Agent.OpenLoginWindow", async (event, options) => { try { const region = options.region || '07'; await Page.open("agentLogin", { region: region, onGetQRCode: async (region: string) => { // 这里调用智能体登录二维码生成 const result = await MAPI.invoke("Agent.Login", "getQRCode", { region }); return result; }, onCheckStatus: async (region: string) => { // 这里检查登录状态 const result = await MAPI.invoke("Agent.Login", "checkStatus", { region }); return result; }, onClose: () => { console.log("智能体登录窗口已关闭"); } }); return { success: true }; } catch (error) { Log.error("打开智能体登录窗口失败:", error); return { success: false, error: error.message }; } }); app.on("browser-window-created", (_, window) => { optimizer.watchWindowShortcuts(window); }); createWindow().then(); }); // 标记应用是否正在退出 let isQuitting = false; app.on("before-quit", async (event) => { // 更新时立即退出,不执行清理(清理已在 quitAndInstall 中完成) if ((global as any).__isUpdating) { console.log("[AppShutdown] 更新模式,跳过清理,立即退出"); Log.info("[AppShutdown] 更新模式,跳过清理,立即退出"); return; } // 防止多次触发 if (!isQuitting) { event.preventDefault(); isQuitting = true; console.log("[AppShutdown] 应用关闭,开始清理资源..."); Log.info("[AppShutdown] 应用关闭,开始清理资源..."); // 设置清理超时(最多8秒,给安装程序留缓冲时间) const cleanupTimeout = setTimeout(() => { console.log("[AppShutdown] ⚠️ 清理超时,强制退出"); Log.info("[AppShutdown] 清理超时,强制退出"); process.exit(0); }, 8000); try { // 1. 停止所有运行中的服务器(最多3秒) console.log("[AppShutdown] 停止所有服务器..."); await Promise.race([ ServerMain.stopAllServers(), new Promise(resolve => setTimeout(resolve, 3000)) ]); console.log("[AppShutdown] ✓ 服务器已停止"); // 2. 杀死残留的模型进程(Windows)- 总是清理,确保更新/卸载时干净 if (process.platform === "win32") { const processesToKill = ["launcher.exe", "python.exe", "ffmpeg.exe", "ffprobe.exe"]; const protectedCommands = ['api_server.py', 'LstmSync']; console.log("[AppShutdown] 清理残留进程..."); for (const proc of processesToKill) { try { if (proc === 'python.exe') { // 使用超时保护,避免 PowerShell 查询卡住 const output = execSync(`powershell -Command "$ErrorActionPreference='SilentlyContinue'; Get-CimInstance Win32_Process -Filter \\"Name = '${proc}'\\" | Select-Object ProcessId, CommandLine | ConvertTo-Json"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'], timeout: 2000 }); let procs = []; try { procs = JSON.parse(output); if (!Array.isArray(procs)) procs = [procs]; } catch { procs = []; } for (const p of procs) { if (!p || !p.ProcessId) continue; const cmd = (p.CommandLine || '').toLowerCase(); if (protectedCommands.some(kw => cmd.includes(kw.toLowerCase()))) { console.log(`[AppShutdown] 🛡️ 跳过保护进程 PID ${p.ProcessId}`); continue; } try { execSync(`taskkill /f /pid ${p.ProcessId} /t`, { stdio: 'ignore', shell: 'cmd.exe', timeout: 1000 }); console.log(`[AppShutdown] ✓ 已杀死: ${proc} PID ${p.ProcessId}`); } catch {} } } else { execSync(`taskkill /f /im ${proc} /t`, { stdio: 'ignore', shell: 'cmd.exe', timeout: 1000 }); console.log(`[AppShutdown] ✓ 已杀死: ${proc}`); } } catch (err: any) { } } console.log("[AppShutdown] ✓ 残留进程清理完成"); } } catch (error) { console.error("[AppShutdown] 清理过程出错:", error); Log.error("[AppShutdown] 清理过程出错:", error); } finally { clearTimeout(cleanupTimeout); } console.log("[AppShutdown] ✓ 资源清理完成,退出应用"); Log.info("[AppShutdown] ✓ 资源清理完成,退出应用"); // 退出应用 app.quit(); } }); app.on("will-quit", () => { // 应用即将退出,不执行清理操作 // 操作系统会自动回收所有资源 }); app.on("window-all-closed", () => { if (process.platform !== "darwin") app.quit(); }); app.on("second-instance", () => { if (AppRuntime.mainWindow) { if (AppRuntime.mainWindow.isMinimized()) { AppRuntime.mainWindow.restore(); } AppRuntime.mainWindow.show(); AppRuntime.mainWindow.focus(); } }); app.on("activate", () => { const allWindows = BrowserWindow.getAllWindows(); if (allWindows.length) { if (!AppRuntime.mainWindow.isVisible()) { AppRuntime.mainWindow.show(); } AppRuntime.mainWindow.focus(); } else { createWindow().then(); } });