const { app, BrowserWindow ,ipcMain} = require('electron') const path = require('path') const os = require('os') const fs = require('fs') const config = require('../configs/config.js') const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged // 修复缓存权限问题:设置用户数据目录到有权限的位置 // 必须在 app.whenReady() 之前调用 if (process.platform === 'win32') { try { // 设置缓存目录到用户临时目录,避免权限问题 const userDataPath = path.join(os.tmpdir(), 'electron-react-vite-app') // 确保目录存在 if (!fs.existsSync(userDataPath)) { fs.mkdirSync(userDataPath, { recursive: true }) } // 创建缓存子目录 const cacheDir = path.join(userDataPath, 'cache') const gpuCacheDir = path.join(userDataPath, 'gpu-cache') if (!fs.existsSync(cacheDir)) { fs.mkdirSync(cacheDir, { recursive: true }) } if (!fs.existsSync(gpuCacheDir)) { fs.mkdirSync(gpuCacheDir, { recursive: true }) } // 设置用户数据路径(必须在 app.whenReady() 之前) app.setPath('userData', userDataPath) // 设置缓存目录到有权限的位置 app.commandLine.appendSwitch('disk-cache-dir', cacheDir) app.commandLine.appendSwitch('gpu-disk-cache-dir', gpuCacheDir) console.log(`[OK] Cache directories set to: ${userDataPath}`) } catch (error) { console.warn('[WARN] Failed to set cache directories:', error.message) // 如果设置失败,尝试禁用 GPU 缓存作为备选方案 app.commandLine.appendSwitch('disable-gpu-sandbox') } } // 保存主窗口引用,用于推送消息 let mainWindowInstance = null function createWindow() { const mainWindow = new BrowserWindow({ width: config.window.width, height: config.window.height, autoHideMenuBar: config.window.autoHideMenuBar, // 从配置文件读取 webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, 'preload.js') } }) // 保存窗口引用 mainWindowInstance = mainWindow if (isDev) { const vitePort = config.vite?.port || 5173 const viteHost = config.vite?.host || 'localhost' console.log(`Loading Vite dev server at http://${viteHost}:${vitePort}`) mainWindow.loadURL(`http://${viteHost}:${vitePort}`) // 根据配置文件决定是否打开调试侧边栏 if (config.devTools.enabled) { mainWindow.webContents.openDevTools() } } else { mainWindow.loadFile(path.join(__dirname, '../dist/index.html')) } } const { spawn } = require('child_process') // 存储运行中的进程 const runningProcesses = new Map() // Execute Node.js script ipcMain.handle('run-nodejs-script', async (event, scriptName, ...parameters) => { return new Promise((resolve, reject) => { const scriptPath = path.join(__dirname, '../nodejs', `${scriptName}.js`) const processKey = `${scriptName}-${parameters.join('-')}` // 如果进程已运行,先停止它 if (runningProcesses.has(processKey)) { const oldProcess = runningProcesses.get(processKey) oldProcess.kill() runningProcesses.delete(processKey) } const nodeProcess = spawn('node', [scriptPath, ...parameters]) runningProcesses.set(processKey, nodeProcess) let stdout = '' let stderr = '' let resolved = false const finish = (result) => { if (resolved) return resolved = true resolve(result) } nodeProcess.stdout.on('data', (data) => { const dataStr = data.toString() stdout += dataStr const isLongRunning = scriptName.includes('screenshot') || scriptName.includes('adb/') try { const lines = dataStr.trim().split('\n') for (const line of lines) { if (line.trim().startsWith('{')) { const json = JSON.parse(line.trim()) if (json.success && isLongRunning) { finish({ success: true, stdout: line.trim(), stderr: stderr.trim(), exitCode: null }) return } } } } catch (e) {} }) nodeProcess.stderr.on('data', (data) => { stderr += data.toString() }) const isRunProcess = scriptName === 'run-process' const timeoutId = isRunProcess ? null : setTimeout(() => { if (!resolved) { finish({ success: false, stdout: stdout.trim(), stderr: stderr.trim(), exitCode: 1, message: 'Script is running in background' }) } }, 5000) nodeProcess.on('close', (code) => { if (timeoutId) clearTimeout(timeoutId) runningProcesses.delete(processKey) const exitCode = (code !== null && code !== undefined) ? code : 1 finish({ success: exitCode === 0, stdout: stdout.trim(), stderr: stderr.trim(), exitCode }) }) nodeProcess.on('error', (error) => { clearTimeout(timeoutId) runningProcesses.delete(processKey) if (!resolved) { resolved = true reject(error) } }) }) }) /** 停止指定的 Node.js 脚本进程 */ ipcMain.handle('kill-nodejs-script', async (event, scriptName, ...parameters) => { const processKey = `${scriptName}-${parameters.join('-')}` if (runningProcesses.has(processKey)) { runningProcesses.get(processKey).kill() runningProcesses.delete(processKey) return { killed: true } } return { killed: false } }) // Execute Python script ipcMain.handle('run-python-script', async (event, scriptName, ...parameters) => { return new Promise((resolve, reject) => { let pythonPath = 'python' if (config.pythonPath?.path) { const configPythonPath = path.join(config.pythonPath.path, 'python.exe') if (fs.existsSync(configPythonPath)) { pythonPath = configPythonPath } } const scriptPath = path.join(__dirname, '../python/scripts', `${scriptName}.py`) if (!fs.existsSync(scriptPath)) { reject({ success: false, error: `Script file not found: ${scriptPath}`, stderr: `Script file not found: ${scriptPath}` }) return } const pythonProcess = spawn(pythonPath, [scriptPath, ...parameters]) let stdout = '' let stderr = '' pythonProcess.stdout.on('data', (data) => { stdout += data.toString() }) pythonProcess.stderr.on('data', (data) => { stderr += data.toString() }) pythonProcess.on('close', (code) => { resolve({ success: code === 0, stdout: stdout.trim(), stderr: stderr.trim(), exitCode: code }) }) pythonProcess.on('error', (error) => { reject({ success: false, error: error.message, stderr: error.message }) }) }) }) // IPC 实时通信处理 // 处理前端请求(异步,返回 Promise) ipcMain.handle('ipc-request', async (event, channel, data) => { // 可以根据不同的 channel 处理不同的请求 // 例如:'run-nodejs', 'get-status' 等 if (channel === 'run-nodejs') { // 通过 IPC 调用 run-nodejs-script const { scriptName, ...parameters } = data return new Promise((resolve, reject) => { const scriptPath = path.join(__dirname, '../nodejs', `${scriptName}.js`) const processKey = `${scriptName}-${parameters.join('-')}` if (runningProcesses.has(processKey)) { const oldProcess = runningProcesses.get(processKey) oldProcess.kill() runningProcesses.delete(processKey) } const nodeProcess = spawn('node', [scriptPath, ...Object.values(parameters)]) runningProcesses.set(processKey, nodeProcess) let stdout = '' let stderr = '' let resolved = false nodeProcess.stdout.on('data', (chunk) => { stdout += chunk.toString() try { const lines = chunk.toString().trim().split('\n') for (const line of lines) { if (line.trim().startsWith('{')) { const json = JSON.parse(line.trim()) if (json.success && !resolved) { resolved = true resolve({ success: true, stdout: line.trim(), stderr: stderr.trim(), exitCode: null }) } } } } catch (e) {} }) nodeProcess.stderr.on('data', (chunk) => { stderr += chunk.toString() }) nodeProcess.on('close', (code) => { runningProcesses.delete(processKey) if (!resolved) { resolve({ success: code === 0, stdout: stdout.trim(), stderr: stderr.trim(), exitCode: code }) } }) nodeProcess.on('error', (error) => { runningProcesses.delete(processKey) if (!resolved) { reject(error) } }) setTimeout(() => { if (!resolved) { resolved = true resolve({ success: true, stdout: stdout.trim(), stderr: stderr.trim(), exitCode: null, message: 'Script is running in background' }) } }, 5000) }) } // 默认响应 return { success: true, channel, data, timestamp: Date.now() } }) // 监听前端发送的消息(不需要响应) ipcMain.on('ipc-message', (event, channel, data) => { // 处理前端发送的消息 console.log(`[IPC] Received message on channel "${channel}":`, data) }) // 推送消息到前端的辅助函数 function pushToFrontend(channel, data) { if (mainWindowInstance && !mainWindowInstance.isDestroyed()) { mainWindowInstance.webContents.send(channel, data) } } // 导出推送函数供其他模块使用 global.pushToFrontend = pushToFrontend app.whenReady().then(() => { createWindow() app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow() } }) }) app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit() } })