Files
tkNewPageElectron/main.js

229 lines
9.1 KiB
JavaScript
Raw Normal View History

2025-10-10 19:41:11 +08:00
// main.js
const { app, Tray, Menu, dialog, BrowserWindow, ipcMain, nativeTheme } = require('electron');
const { autoUpdater } = require('electron-updater');
const path = require('path');
const fs = require('fs');
const { spawn, spawnSync, exec } = require('child_process');
const log = require('electron-log');
Object.assign(console, log.functions);
const ALLOW_UNSIGNED = process.env.YOLO_ALLOW_UNSIGNED === '1';
let installTriggered = false; // NEW: 防止重复触发安装
// ===== 全局状态 =====
let tray = null;
let managedChild = null;
let quitting = false;
let updateWin = null;
let pythonStarted = false; // NEW: 已启动 tkpage
let updaterKicked = false; // NEW: 是否已触发检查
// ===== tkpage 路径 =====
function resolveExePath() {
const devExe = path.resolve(__dirname, 'resources', 'python', 'tkpage.exe');
const prodExe = path.join(process.resourcesPath, 'python', 'tkpage.exe');
return app.isPackaged ? prodExe : devExe;
}
// ===== 启动 tkpage只会启动一次 =====
function launchPythonIfNeeded() {
if (pythonStarted) return;
const exe = resolveExePath();
const cwd = path.dirname(exe);
console.info('[tkpage exe]', exe);
if (!fs.existsSync(exe)) {
dialog.showErrorBox('启动失败', `未找到可执行文件:\n${exe}`);
return;
}
pythonStarted = true;
managedChild = spawn(exe, [], {
cwd,
windowsHide: false,
detached: false,
stdio: 'ignore',
});
managedChild.on('exit', (code, signal) => {
console.info(`[tkpage] exit code=${code} signal=${signal}`);
if (!quitting) { quitting = true; app.quit(); }
});
managedChild.on('close', (code, signal) => {
console.info(`[tkpage] close code=${code} signal=${signal}`);
if (!quitting) { quitting = true; app.quit(); }
});
}
// ===== 结束 tkpage =====
function killPythonGUI(killSelf = true) {
if (managedChild && managedChild.pid) {
try { process.kill(managedChild.pid); } catch (e) {
console.warn('[kill] managed kill error:', e?.message || e);
} finally { managedChild = null; }
}
try { spawnSync('taskkill', ['/f', '/t', '/im', 'tkpage.exe'], { windowsHide: true }); } catch { }
if (killSelf) {
try { exec('taskkill /IM YOLO助手.exe /F'); } catch { }
}
}
// ===== 托盘 =====
function createTray() {
const iconPath = path.join(__dirname, 'assets', 'icon.ico');
tray = new Tray(iconPath);
const menu = Menu.buildFromTemplate([
{ label: '检查更新', click: () => startUpdateCheck(/*force*/true) },
{ type: 'separator' },
{ label: '显示更新窗口', click: () => { if (updateWin) { updateWin.show(); updateWin.focus(); } else { createUpdateWindow(); } } },
{ type: 'separator' },
{ label: '重启 Python GUI', click: () => { killPythonGUI(); pythonStarted = false; launchPythonIfNeeded(); } },
{ type: 'separator' },
{ label: '退出', click: () => { quitting = true; killPythonGUI(); app.quit(); } },
]);
tray.setToolTip('YOLO');
tray.setContextMenu(menu);
}
// ===== 更新窗口 =====
function createUpdateWindow() {
if (updateWin && !updateWin.isDestroyed()) { updateWin.show(); updateWin.focus(); return; }
updateWin = new BrowserWindow({
width: 640, height: 420, resizable: false, fullscreenable: false, maximizable: false,
autoHideMenuBar: true, show: true, center: true,
backgroundColor: nativeTheme.shouldUseDarkColors ? '#0b1220' : '#ffffff',
icon: path.join(__dirname, 'assets', 'icon.ico'),
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true, nodeIntegration: false, sandbox: true,
}
});
updateWin.on('close', (e) => { e.preventDefault(); updateWin.hide(); });
updateWin.loadFile(path.join(__dirname, 'index.html')).catch(err => console.error('[updateWin] load error', err));
}
// ===== Updater 事件桥接到渲染进程,并控制 tkpage 启动时机 =====
function wireUpdaterIpc() {
if (ALLOW_UNSIGNED) {
console.warn('[updater] verifyUpdateCodeSignature disabled (YOLO_ALLOW_UNSIGNED=1)');
autoUpdater.verifyUpdateCodeSignature = async () => null;
}
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = false;
const send = (ch, data) => { if (updateWin && !updateWin.isDestroyed()) updateWin.webContents.send(ch, data); };
autoUpdater.on('checking-for-update', () => {
console.info('[updater] Checking');
tray?.setToolTip('YOLO - 正在检查更新…');
send('update:status', { status: 'checking' });
});
autoUpdater.on('update-available', (info) => {
console.info('[updater] Available', info?.version);
tray?.setToolTip(`YOLO - 检测到新版本 v${info?.version},开始下载…`);
send('update:status', { status: 'available', version: info?.version, notes: info?.releaseNotes || '' });
// 此时不启动 tkpage等待下载完成
});
autoUpdater.on('update-not-available', (info) => {
console.info('[updater] None');
tray?.setToolTip('YOLO - 已是最新版本');
send('update:status', { status: 'none', currentVersion: app.getVersion(), info });
// ✅ 没有更新,立刻启动 tkpage
launchPythonIfNeeded();
// 可隐藏更新窗口(让应用留在托盘+tkpage前台
setTimeout(() => updateWin?.hide(), 1200);
});
autoUpdater.on('download-progress', (p) => {
const percent = Math.max(0, Math.min(100, p?.percent || 0));
const transferred = Math.floor((p?.transferred || 0) / 1024 / 1024);
const total = Math.floor((p?.total || 0) / 1024 / 1024);
const speed = Math.floor((p?.bytesPerSecond || 0) / 1024 / 1024);
tray?.setToolTip(`YOLO - 正在下载更新:${percent.toFixed(1)}%`);
send('update:progress', { percent, transferred, total, speed });
});
autoUpdater.on('update-downloaded', (info) => {
console.info('[updater] Downloaded', info?.version);
tray?.setToolTip('YOLO - 更新已下载,等待安装…');
// 仅通知渲染进程显示“立即安装”按钮;不自动安装
send('update:status', { status: 'downloaded', version: info?.version });
// 可选择把更新窗口确保前置
if (updateWin && !updateWin.isDestroyed()) { updateWin.show(); updateWin.focus(); }
});
autoUpdater.on('error', (err) => {
const msg = err?.message || String(err);
console.error('[updater] error', msg);
tray?.setToolTip('YOLO - 更新失败');
dialog.showErrorBox('自动更新失败', msg);
send('update:status', { status: 'error', message: msg });
// ⚠️ 出错:不阻断主流程,直接启动 tkpage
launchPythonIfNeeded();
});
// 渲染侧手动触发
ipcMain.on('updater:check', () => startUpdateCheck(/*force*/true));
ipcMain.on('updater:install-now', () => {
if (installTriggered) return;
installTriggered = true;
console.info('[updater] quitAndInstall requested by renderer');
tray?.setToolTip('YOLO - 正在安装更新…');
quitting = true;
try { killPythonGUI(false); } catch (e) {
console.warn('[updater] killPythonGUI error:', e?.message || e);
}
setTimeout(() => {
try {
autoUpdater.quitAndInstall(false, true);
} catch (e) {
installTriggered = false;
console.error('[updater] quitAndInstall error:', e?.message || e);
dialog.showErrorBox('安装失败', String(e?.message || e));
}
}, 300);
});
}
// ===== 触发更新检查(仅触发一次,托盘点“检查更新”可强制再触发) =====
function startUpdateCheck(force = false) {
if (updaterKicked && !force) return;
updaterKicked = true;
// 显示更新窗口(可选:你也可以只在有更新时显示)
if (!updateWin || updateWin.isDestroyed()) createUpdateWindow();
else { updateWin.show(); updateWin.focus(); }
autoUpdater.checkForUpdates().catch(() => { /* 错误会在 on('error') 里处理 */ });
}
// ===== 单例 & 生命周期 =====
process.on('unhandledRejection', (r) => console.error('[unhandledRejection]', r));
process.on('uncaughtException', (e) => console.error('[uncaughtException]', e));
const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
app.quit();
} else {
app.on('second-instance', () => { if (updateWin) { updateWin.show(); updateWin.focus(); } });
app.whenReady().then(() => {
if (process.platform === 'win32') {
app.setAppUserModelId('com.yolozs.newPage');
}
createTray();
createUpdateWindow();
wireUpdaterIpc();
// ✅ 启动时:只检查更新,不启动 tkpage。无更新 → 启动;有更新 → 下载并重启安装。
startUpdateCheck();
});
app.on('before-quit', () => { quitting = true; killPythonGUI(true); });
app.on('window-all-closed', () => { /* 后台常驻 */ });
}