Skip to content

Commit 3cd71b0

Browse files
committed
fix(terminal): render terminal canvas at correct DPR when webview is zoomed
Tauri/Electron native zoom does not update window.devicePixelRatio, causing the xterm.js WebGL canvas to render at 1x resolution regardless of zoom level. The native zoom then upscales the bitmap, producing blurry terminal text at zoom levels above 100%. Fix by overriding the devicePixelRatio getter in zoomWebView() to reflect the effective DPR (baseDPR * zoomFactor). The terminal detects DPR changes during fit() and calls clearTextureAtlas() to rebuild glyphs at the new resolution. Also guards _cropDataUrlToRect to use the original base DPR so screenshot cropping is unaffected.
1 parent fe46411 commit 3cd71b0

2 files changed

Lines changed: 44 additions & 6 deletions

File tree

src/extensionsIntegrated/Terminal/TerminalInstance.js

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ define(function (require, exports, module) {
105105
this.$container = null;
106106
this._resizeTimeout = null;
107107
this._disposed = false;
108+
this._webglAddon = null;
109+
this._lastDpr = null;
108110

109111
// Bound event handlers for cleanup
110112
this._onTerminalData = this._onTerminalData.bind(this);
@@ -144,11 +146,7 @@ define(function (require, exports, module) {
144146
this.terminal.open(this.$container[0]);
145147

146148
// Load WebGL renderer for better performance
147-
try {
148-
this.terminal.loadAddon(new WebglAddon());
149-
} catch (e) {
150-
console.warn("Terminal: WebglAddon failed to load, using default renderer:", e);
151-
}
149+
this._loadWebGLAddon();
152150

153151
// Fit to container
154152
this._fit();
@@ -270,6 +268,22 @@ define(function (require, exports, module) {
270268
return true; // Let xterm handle it
271269
};
272270

271+
/**
272+
* Load (or reload) the WebGL renderer addon, recording the current DPR
273+
* so we can detect changes later and recreate the addon at the correct
274+
* resolution (e.g. after a zoom change).
275+
*/
276+
TerminalInstance.prototype._loadWebGLAddon = function () {
277+
try {
278+
this._webglAddon = new WebglAddon();
279+
this.terminal.loadAddon(this._webglAddon);
280+
this._lastDpr = window.devicePixelRatio;
281+
} catch (e) {
282+
console.warn("Terminal: WebglAddon failed to load, using default renderer:", e);
283+
this._webglAddon = null;
284+
}
285+
};
286+
273287
/**
274288
* Fit the terminal to its container.
275289
*
@@ -288,6 +302,15 @@ define(function (require, exports, module) {
288302
return;
289303
}
290304

305+
// When the effective DPR changes (e.g. after a webview zoom change),
306+
// clear the glyph texture atlas so the WebGL renderer rebuilds it at
307+
// the new resolution. The subsequent fit() will resize the canvas.
308+
const currentDpr = window.devicePixelRatio;
309+
if (this._lastDpr !== null && this._lastDpr !== currentDpr) {
310+
this._lastDpr = currentDpr;
311+
this.terminal.clearTextureAtlas();
312+
}
313+
291314
try {
292315
// Only clear the prompt region when dimensions are actually
293316
// changing — i.e. a real reflow will happen. When dimensions
@@ -416,6 +439,10 @@ define(function (require, exports, module) {
416439

417440
// Dispose xterm
418441
clearTimeout(this._resizeTimeout);
442+
if (this._webglAddon) {
443+
this._webglAddon.dispose();
444+
this._webglAddon = null;
445+
}
419446
if (this.terminal) {
420447
this.terminal.dispose();
421448
this.terminal = null;

src/phoenix/shell.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ function _cropDataUrlToRect(dataUrl, rect) {
203203
const img = new Image();
204204
img.onload = function () {
205205
try {
206-
const dpr = window.devicePixelRatio || 1;
206+
const dpr = window._origDevicePixelRatio || window.devicePixelRatio || 1;
207207
const canvas = document.createElement("canvas");
208208
const sx = Math.round(rect.x * dpr);
209209
const sy = Math.round(rect.y * dpr);
@@ -859,6 +859,17 @@ Phoenix.app = {
859859
if(scaleFactor < .1 || scaleFactor > 2) {
860860
throw new Error("zoomWebView scale factor should be between .1 and 2");
861861
}
862+
// Native webview zoom (Tauri/Electron) does not update
863+
// window.devicePixelRatio, causing canvas-based renderers
864+
// (e.g. xterm.js WebGL) to render at the wrong resolution.
865+
// Override the getter so it reflects the effective DPR.
866+
if(window._origDevicePixelRatio === undefined) {
867+
window._origDevicePixelRatio = window.devicePixelRatio;
868+
}
869+
Object.defineProperty(window, 'devicePixelRatio', {
870+
get() { return window._origDevicePixelRatio * scaleFactor; },
871+
configurable: true
872+
});
862873
if(window.__TAURI__) {
863874
return window.__TAURI__.tauri.invoke("zoom_window", {scaleFactor: scaleFactor});
864875
}

0 commit comments

Comments
 (0)