Improve terminal renderer recovery
This commit is contained in:
@@ -158,6 +158,7 @@ export class TerminalEmulatorRuntime {
|
|||||||
convertEol: false,
|
convertEol: false,
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
cursorStyle: "bar",
|
cursorStyle: "bar",
|
||||||
|
customGlyphs: true,
|
||||||
fontFamily: DEFAULT_TERMINAL_FONT_FAMILY,
|
fontFamily: DEFAULT_TERMINAL_FONT_FAMILY,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
lineHeight: 1.0,
|
lineHeight: 1.0,
|
||||||
@@ -173,6 +174,7 @@ export class TerminalEmulatorRuntime {
|
|||||||
const fitAddon = new FitAddon();
|
const fitAddon = new FitAddon();
|
||||||
const unicode11Addon = new Unicode11Addon();
|
const unicode11Addon = new Unicode11Addon();
|
||||||
let webglAddon: WebglAddon | null = null;
|
let webglAddon: WebglAddon | null = null;
|
||||||
|
let imageAddon: ImageAddon | null = null;
|
||||||
terminal.loadAddon(fitAddon);
|
terminal.loadAddon(fitAddon);
|
||||||
terminal.loadAddon(unicode11Addon);
|
terminal.loadAddon(unicode11Addon);
|
||||||
terminal.loadAddon(
|
terminal.loadAddon(
|
||||||
@@ -195,34 +197,46 @@ export class TerminalEmulatorRuntime {
|
|||||||
// Ignore if unicode API isn't available in this build/runtime.
|
// 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
|
const disposeImageAddon = (): void => {
|
||||||
// and improves scroll performance, but must gracefully fall back on platforms without WebGL.
|
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(() => {
|
let webglAddonRaf: number | null = requestAnimationFrame(() => {
|
||||||
webglAddonRaf = null;
|
webglAddonRaf = null;
|
||||||
try {
|
try {
|
||||||
|
disposeWebglRenderer();
|
||||||
webglAddon = new WebglAddon();
|
webglAddon = new WebglAddon();
|
||||||
webglAddon.onContextLoss(() => {
|
webglAddon.onContextLoss(() => {
|
||||||
try {
|
disposeWebglRenderer();
|
||||||
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
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
terminal.loadAddon(webglAddon);
|
terminal.loadAddon(webglAddon);
|
||||||
|
imageAddon = new ImageAddon();
|
||||||
|
terminal.loadAddon(imageAddon);
|
||||||
|
this.fitAndEmitResize?.(true);
|
||||||
} catch {
|
} catch {
|
||||||
webglAddon = null;
|
disposeWebglRenderer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
terminal.loadAddon(new ImageAddon());
|
|
||||||
|
|
||||||
// Suppress terminal query responses — the server-side headless xterm handles these.
|
// Suppress terminal query responses — the server-side headless xterm handles these.
|
||||||
// Without this, xterm.js generates DA/CPR responses via onData that feed back
|
// Without this, xterm.js generates DA/CPR responses via onData that feed back
|
||||||
// to the PTY as visible text.
|
// to the PTY as visible text.
|
||||||
@@ -451,12 +465,8 @@ export class TerminalEmulatorRuntime {
|
|||||||
cancelAnimationFrame(webglAddonRaf);
|
cancelAnimationFrame(webglAddonRaf);
|
||||||
webglAddonRaf = null;
|
webglAddonRaf = null;
|
||||||
}
|
}
|
||||||
try {
|
disposeWebglRenderer();
|
||||||
webglAddon?.dispose();
|
disposeImageAddon();
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
webglAddon = null;
|
|
||||||
},
|
},
|
||||||
disposeTerminal: () => {
|
disposeTerminal: () => {
|
||||||
terminal.dispose();
|
terminal.dispose();
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
getMainWindowChromeOptions,
|
getMainWindowChromeOptions,
|
||||||
getWindowBackgroundColor,
|
getWindowBackgroundColor,
|
||||||
resolveSystemWindowTheme,
|
resolveSystemWindowTheme,
|
||||||
|
setupDarwinPaintRefresh,
|
||||||
setupWindowResizeEvents,
|
setupWindowResizeEvents,
|
||||||
setupDefaultContextMenu,
|
setupDefaultContextMenu,
|
||||||
setupDragDropPrevention,
|
setupDragDropPrevention,
|
||||||
@@ -189,6 +190,7 @@ async function createMainWindow(): Promise<void> {
|
|||||||
app.dock?.setBadge(devWorktreeName);
|
app.dock?.setBadge(devWorktreeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupDarwinPaintRefresh(mainWindow);
|
||||||
setupWindowResizeEvents(mainWindow);
|
setupWindowResizeEvents(mainWindow);
|
||||||
setupDefaultContextMenu(mainWindow);
|
setupDefaultContextMenu(mainWindow);
|
||||||
setupDragDropPrevention(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 {
|
export function setupDefaultContextMenu(win: BrowserWindow): void {
|
||||||
win.webContents.on("context-menu", (_event, params) => {
|
win.webContents.on("context-menu", (_event, params) => {
|
||||||
const menu = Menu.buildFromTemplate([
|
const menu = Menu.buildFromTemplate([
|
||||||
|
|||||||
Reference in New Issue
Block a user