更新压缩机制,提高前缀缓存命中率,优化UI渲染机制,提高帧数
This commit is contained in:
1 parent
6131c2563e
commit
0ce72f74f1
175 files changed
+7621
-2611
No files matched your search
+80
-17
@@ -60,6 +60,25 @@ let mainWindow = null;
|
||||
let pythonProcess = null;
|
||||
let isQuitting = false;
|
||||
let isLaunching = false; // 防止 setup 窗口关闭时触发 app.quit
|
||||
let _closeConfirmed = false; // 后台任务确认后放行关闭
|
||||
|
||||
/** 查询后端运行中的后台终端任务数(退出确认用;后端不可用时视为 0) */
|
||||
function checkRunningTasks() {
|
||||
return new Promise((resolve) => {
|
||||
const req = http.get(`${BASE_URL}/api/terminal/running-count`, { timeout: 2000 }, (res) => {
|
||||
let body = '';
|
||||
res.on('data', (d) => (body += d));
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const n = Number(JSON.parse(body).running) || 0;
|
||||
resolve(n);
|
||||
} catch { resolve(0); }
|
||||
});
|
||||
});
|
||||
req.on('error', () => resolve(0));
|
||||
req.on('timeout', () => { req.destroy(); resolve(0); });
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 端口清理 ====================
|
||||
function killPortProcess(port) {
|
||||
@@ -343,6 +362,28 @@ async function createMainWindow(themeMode) {
|
||||
|
||||
mainWindow.on('closed', () => { mainWindow = null; });
|
||||
|
||||
// 关闭拦截:若仍有后台终端任务在运行,先弹确认("终止并关闭 / 取消")
|
||||
mainWindow.on('close', (e) => {
|
||||
if (isQuitting || _closeConfirmed) return;
|
||||
e.preventDefault();
|
||||
checkRunningTasks().then(async (n) => {
|
||||
if (n > 0) {
|
||||
const { response } = await dialog.showMessageBox(mainWindow, {
|
||||
type: 'warning',
|
||||
buttons: ['终止并关闭', '取消'],
|
||||
defaultId: 1,
|
||||
cancelId: 1,
|
||||
title: '后台终端仍在运行',
|
||||
message: `仍有 ${n} 个后台终端任务在运行`,
|
||||
detail: '终止这些任务并关闭应用?(若不终止,进程将与应用一并被清理)',
|
||||
});
|
||||
if (response !== 0) return;
|
||||
}
|
||||
_closeConfirmed = true;
|
||||
mainWindow.close();
|
||||
});
|
||||
});
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
require('electron').shell.openExternal(url).catch(() => {});
|
||||
return { action: 'deny' };
|
||||
@@ -971,11 +1012,29 @@ app.on('before-quit', () => {
|
||||
stopFastAPI();
|
||||
});
|
||||
|
||||
// ==================== 内置 PowerShell 终端 ====================
|
||||
// 每个标签对应一个 powershell 子进程;面板折叠不 kill,仅标签 x / 右上角 x / 退出时清理。
|
||||
// ==================== 内置 PowerShell 终端(ConPTY 真终端) ====================
|
||||
// 每个标签对应一个经 node-pty (Windows ConPTY) 启动的真实 PowerShell 会话,
|
||||
// PSReadLine / Tab 补全 / 历史 / 全屏应用由真实控制台提供,前端 xterm.js 渲染。
|
||||
// 面板折叠不 kill(仅隐藏);标签 x 单个 kill;右上角 x 确认后 kill 全部。
|
||||
const _terminals = new Map(); // id -> { proc, sender }
|
||||
let _terminalSeq = 0;
|
||||
|
||||
// 优先 PowerShell 7(truecolor / PSReadLine 更强),回退 Windows PowerShell 5.1
|
||||
let _shellPathCache = '';
|
||||
function resolveShellPath() {
|
||||
if (_shellPathCache) return _shellPathCache;
|
||||
try {
|
||||
execSync('where pwsh.exe', { stdio: 'ignore', shell: true });
|
||||
_shellPathCache = 'pwsh.exe';
|
||||
} catch (e) {
|
||||
_shellPathCache = path.join(
|
||||
process.env.SystemRoot || 'C:\\Windows',
|
||||
'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'
|
||||
);
|
||||
}
|
||||
return _shellPathCache;
|
||||
}
|
||||
|
||||
function forceKill(proc) {
|
||||
try { proc.kill(); } catch (e) { /* ignore */ }
|
||||
// Windows 兜底:taskkill 结束整个进程树(处理 Ctrl 信号不响应/子进程残留)
|
||||
@@ -991,30 +1050,28 @@ function killAllTerminals() {
|
||||
_terminals.clear();
|
||||
}
|
||||
|
||||
ipcMain.handle('terminal:spawn', (event, { cwd } = {}) => {
|
||||
ipcMain.handle('terminal:spawn', (event, { cwd, cols, rows } = {}) => {
|
||||
const id = ++_terminalSeq;
|
||||
let proc;
|
||||
try {
|
||||
proc = spawn('powershell.exe', ['-NoLogo'], {
|
||||
// ConPTY 需真实控制台;懒加载,模块缺失时不影响应用其余功能
|
||||
const pty = require('@homebridge/node-pty-prebuilt-multiarch');
|
||||
proc = pty.spawn(resolveShellPath(), ['-NoLogo'], {
|
||||
cwd: (cwd && fs.existsSync(cwd)) ? cwd : undefined,
|
||||
windowsHide: true,
|
||||
cols: (cols | 0) || 80,
|
||||
rows: (rows | 0) || 24,
|
||||
env: { ...process.env, TERM: 'xterm-256color' },
|
||||
});
|
||||
} catch (e) {
|
||||
return { id, ok: false, error: String(e && e.message || e) };
|
||||
return { id, ok: false, error: String((e && e.message) || e) };
|
||||
}
|
||||
_terminals.set(id, { proc, sender: event.sender });
|
||||
proc.stdout.on('data', (chunk) => {
|
||||
if (!event.sender.isDestroyed()) event.sender.send('terminal:data', { id, chunk: chunk.toString('utf8') });
|
||||
proc.onData((chunk) => {
|
||||
if (!event.sender.isDestroyed()) event.sender.send('terminal:data', { id, chunk });
|
||||
});
|
||||
proc.stderr.on('data', (chunk) => {
|
||||
if (!event.sender.isDestroyed()) event.sender.send('terminal:data', { id, chunk: chunk.toString('utf8') });
|
||||
});
|
||||
proc.on('exit', (code) => {
|
||||
proc.onExit(({ exitCode }) => {
|
||||
_terminals.delete(id);
|
||||
if (!event.sender.isDestroyed()) event.sender.send('terminal:exit', { id, code });
|
||||
});
|
||||
proc.on('error', (err) => {
|
||||
if (!event.sender.isDestroyed()) event.sender.send('terminal:exit', { id, code: -1, error: String(err.message || err) });
|
||||
if (!event.sender.isDestroyed()) event.sender.send('terminal:exit', { id, code: exitCode });
|
||||
});
|
||||
return { id, ok: true };
|
||||
});
|
||||
@@ -1022,7 +1079,13 @@ ipcMain.handle('terminal:spawn', (event, { cwd } = {}) => {
|
||||
ipcMain.on('terminal:input', (_event, { id, data } = {}) => {
|
||||
const t = _terminals.get(id);
|
||||
if (!t || !data) return;
|
||||
try { t.proc.stdin.write(data); } catch (e) { /* ignore */ }
|
||||
try { t.proc.write(data); } catch (e) { /* ignore */ }
|
||||
});
|
||||
|
||||
ipcMain.on('terminal:resize', (_event, { id, cols, rows } = {}) => {
|
||||
const t = _terminals.get(id);
|
||||
if (!t) return;
|
||||
try { t.proc.resize((cols | 0) || 80, (rows | 0) || 24); } catch (e) { /* ignore */ }
|
||||
});
|
||||
|
||||
ipcMain.on('terminal:kill', (_event, { id } = {}) => {
|
||||
|
||||
+2
-1
@@ -29,8 +29,9 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
openInExplorer: (filePath) => ipcRenderer.invoke('shell:openInExplorer', filePath),
|
||||
|
||||
// ===== 内置 PowerShell 终端 =====
|
||||
spawnTerminal: (cwd) => ipcRenderer.invoke('terminal:spawn', { cwd }),
|
||||
spawnTerminal: (cwd, cols, rows) => ipcRenderer.invoke('terminal:spawn', { cwd, cols, rows }),
|
||||
sendTerminalInput: (id, data) => ipcRenderer.send('terminal:input', { id, data }),
|
||||
resizeTerminal: (id, cols, rows) => ipcRenderer.send('terminal:resize', { id, cols, rows }),
|
||||
killTerminal: (id) => ipcRenderer.send('terminal:kill', { id }),
|
||||
killAllTerminals: () => ipcRenderer.send('terminal:killAll'),
|
||||
onTerminalData: (cb) => {
|
||||
|
||||
Reference in new issue
Block a user