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
This commit is contained in:
Jad Seifeddine
2026-10-11 04:11:10 +07:00
parent 4ae28efec0
commit ebfab63ea7
+38 -2
View File
@@ -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 = '';