From ebfab63ea702de08bd054febacadb2dea5dfb8a9 Mon Sep 17 00:00:00 2001 From: Jad Seifeddine Date: Sun, 11 Oct 2026 04:11:10 +0700 Subject: [PATCH] client: fill a small framebuffer shortfall instead of leaving a strip Windows RDP without GFX rounds the requested width down to a multiple of 16, so a 1710-wide window gets a 1696-wide desktop. The uniform fit in scaleDisplay() then keeps the scale at 1 (the height matches) and the missing 14px shows as a strip of bare page down the right edge. After the uniform fit, stretch the display element by the remaining fraction on each axis when it is at most 2.5%, and divide the same factors out of pointer coordinates before sendMouseState. A larger gap is a real aspect mismatch and keeps the letterbox. Multi-monitor pop-outs reset the fill. Fixes #251 --- static/client.html | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/static/client.html b/static/client.html index d5f7166..37f2924 100644 --- a/static/client.html +++ b/static/client.html @@ -1859,12 +1859,46 @@ if (document.hidden) keyboard.reset(); }); - // Mouse input + // Mouse input. Pointer positions come in screen pixels of the + // display element; sendMouseState divides out the uniform display + // scale, and the fill factors from scaleDisplay() are divided out + // here, since they are a transform the display knows nothing about. + var fillX = 1, fillY = 1; var mouse = new Guacamole.Mouse(client.getDisplay().getElement()); mouse.onEach(['mousedown', 'mousemove', 'mouseup'], function(e) { - client.sendMouseState(e.state, true); + var state = e.state; + if (fillX !== 1 || fillY !== 1) { + state = new Guacamole.Mouse.State(state); + state.x /= fillX; + state.y /= fillY; + } + client.sendMouseState(state, true); }); + // Absorb a small shortfall between the framebuffer and the + // viewport. The uniform fit in scaleDisplay() makes one axis match + // exactly and leaves the other short when the server could not + // honour the requested size: Windows RDP without GFX rounds the + // width down to a multiple of 16, so a 1710-wide window gets a + // 1696-wide desktop and a 14px strip of bare page down the right + // edge (#251). A stretch of that last percent or two is invisible; + // the strip is not. A larger gap is a real aspect mismatch (fixed + // resolution, no resize support) and keeps the letterbox. + var FILL_MAX = 1.025; + function applyFill(el, w, h, availW, availH) { + fillX = 1; + fillY = 1; + if (w > 0 && w < availW && availW / w <= FILL_MAX) fillX = availW / w; + if (h > 0 && h < availH && availH / h <= FILL_MAX) fillY = availH / h; + if (fillX !== 1 || fillY !== 1) { + el.style.transformOrigin = '0 0'; + el.style.transform = 'scale(' + fillX + ', ' + fillY + ')'; + } else { + el.style.transformOrigin = ''; + el.style.transform = ''; + } + } + // Show the remote pointer image as the browser's own pointer, and // only fall back to the display's software cursor layer where CSS // cursors are unsupported. Guacamole.Display puts that layer in @@ -1942,10 +1976,12 @@ // Monitor 0 is at combined (0,0); no negative offset needed. el.style.marginLeft = '0'; el.style.marginTop = '0'; + applyFill(el, 0, 0, 0, 0); } else { // Single display: scale the whole framebuffer to fit. display.scale(Math.min(availW / dw, availH / dh)); + applyFill(el, dw * display.getScale(), dh * display.getScale(), availW, availH); displayEl.style.overflow = ''; displayEl.style.position = ''; displayEl.style.width = '';