Improve terminal renderer recovery
This commit is contained in:
@@ -158,6 +158,7 @@ export class TerminalEmulatorRuntime {
|
||||
convertEol: false,
|
||||
cursorBlink: true,
|
||||
cursorStyle: "bar",
|
||||
customGlyphs: true,
|
||||
fontFamily: DEFAULT_TERMINAL_FONT_FAMILY,
|
||||
fontSize: 13,
|
||||
lineHeight: 1.0,
|
||||
@@ -173,6 +174,7 @@ export class TerminalEmulatorRuntime {
|
||||
const fitAddon = new FitAddon();
|
||||
const unicode11Addon = new Unicode11Addon();
|
||||
let webglAddon: WebglAddon | null = null;
|
||||
let imageAddon: ImageAddon | null = null;
|
||||
terminal.loadAddon(fitAddon);
|
||||
terminal.loadAddon(unicode11Addon);
|
||||
terminal.loadAddon(
|
||||
@@ -195,34 +197,46 @@ export class TerminalEmulatorRuntime {
|
||||
// Ignore if unicode API isn't available in this build/runtime.
|
||||
}
|
||||
|
||||
// Prefer GPU rendering when available. This tends to reduce visible seams in block characters
|
||||
// and improves scroll performance, but must gracefully fall back on platforms without WebGL.
|
||||
const disposeImageAddon = (): void => {
|
||||
try {
|
||||
imageAddon?.dispose();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
imageAddon = null;
|
||||
};
|
||||
const disposeWebglRenderer = (): void => {
|
||||
if (!webglAddon) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
webglAddon.dispose();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
webglAddon = null;
|
||||
disposeImageAddon();
|
||||
// WebGL and DOM renderers can have different cell dimensions.
|
||||
this.fitAndEmitResize?.(true);
|
||||
};
|
||||
|
||||
let webglAddonRaf: number | null = requestAnimationFrame(() => {
|
||||
webglAddonRaf = null;
|
||||
try {
|
||||
disposeWebglRenderer();
|
||||
webglAddon = new WebglAddon();
|
||||
webglAddon.onContextLoss(() => {
|
||||
try {
|
||||
webglAddon?.dispose();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
webglAddon = null;
|
||||
// Force a refresh after context loss to avoid visual corruption.
|
||||
try {
|
||||
terminal.refresh(0, terminal.rows - 1);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
disposeWebglRenderer();
|
||||
});
|
||||
terminal.loadAddon(webglAddon);
|
||||
imageAddon = new ImageAddon();
|
||||
terminal.loadAddon(imageAddon);
|
||||
this.fitAndEmitResize?.(true);
|
||||
} catch {
|
||||
webglAddon = null;
|
||||
disposeWebglRenderer();
|
||||
}
|
||||
});
|
||||
|
||||
terminal.loadAddon(new ImageAddon());
|
||||
|
||||
// Suppress terminal query responses — the server-side headless xterm handles these.
|
||||
// Without this, xterm.js generates DA/CPR responses via onData that feed back
|
||||
// to the PTY as visible text.
|
||||
@@ -451,12 +465,8 @@ export class TerminalEmulatorRuntime {
|
||||
cancelAnimationFrame(webglAddonRaf);
|
||||
webglAddonRaf = null;
|
||||
}
|
||||
try {
|
||||
webglAddon?.dispose();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
webglAddon = null;
|
||||
disposeWebglRenderer();
|
||||
disposeImageAddon();
|
||||
},
|
||||
disposeTerminal: () => {
|
||||
terminal.dispose();
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
getMainWindowChromeOptions,
|
||||
getWindowBackgroundColor,
|
||||
resolveSystemWindowTheme,
|
||||
setupDarwinPaintRefresh,
|
||||
setupWindowResizeEvents,
|
||||
setupDefaultContextMenu,
|
||||
setupDragDropPrevention,
|
||||
@@ -189,6 +190,7 @@ async function createMainWindow(): Promise<void> {
|
||||
app.dock?.setBadge(devWorktreeName);
|
||||
}
|
||||
|
||||
setupDarwinPaintRefresh(mainWindow);
|
||||
setupWindowResizeEvents(mainWindow);
|
||||
setupDefaultContextMenu(mainWindow);
|
||||
setupDragDropPrevention(mainWindow);
|
||||
|
||||
@@ -219,6 +219,59 @@ export function setupWindowResizeEvents(win: BrowserWindow): void {
|
||||
});
|
||||
}
|
||||
|
||||
function refreshChromiumSurface(win: BrowserWindow): void {
|
||||
if (win.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
win.webContents.invalidate();
|
||||
if (win.isMaximized() || win.isFullScreen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [width, height] = win.getSize();
|
||||
win.setSize(width + 1, height);
|
||||
setTimeout(() => {
|
||||
if (!win.isDestroyed()) {
|
||||
win.setSize(width, height);
|
||||
}
|
||||
}, 32);
|
||||
}
|
||||
|
||||
export function setupDarwinPaintRefresh(win: BrowserWindow): void {
|
||||
if (process.platform !== "darwin") {
|
||||
return;
|
||||
}
|
||||
|
||||
win.webContents.setBackgroundThrottling(false);
|
||||
|
||||
const requestSurfaceRefresh = () => {
|
||||
if (!win.isDestroyed()) {
|
||||
win.webContents.invalidate();
|
||||
}
|
||||
};
|
||||
const handleChildProcessGone = (
|
||||
_event: Electron.Event,
|
||||
details: { type?: string; reason?: string },
|
||||
) => {
|
||||
if (details.type !== "GPU") {
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn("[window] GPU process gone:", details.reason);
|
||||
refreshChromiumSurface(win);
|
||||
};
|
||||
|
||||
win.on("restore", requestSurfaceRefresh);
|
||||
win.on("show", requestSurfaceRefresh);
|
||||
app.on("child-process-gone", handleChildProcessGone);
|
||||
win.once("closed", () => {
|
||||
win.off("restore", requestSurfaceRefresh);
|
||||
win.off("show", requestSurfaceRefresh);
|
||||
app.off("child-process-gone", handleChildProcessGone);
|
||||
});
|
||||
}
|
||||
|
||||
export function setupDefaultContextMenu(win: BrowserWindow): void {
|
||||
win.webContents.on("context-menu", (_event, params) => {
|
||||
const menu = Menu.buildFromTemplate([
|
||||
|
||||
Reference in New Issue
Block a user