From 14317feebd73b9f459d77574a92d2a20ffb01222 Mon Sep 17 00:00:00 2001 From: Gabe Rudy Date: Sun, 3 May 2026 07:24:00 -0600 Subject: [PATCH] Flush pending pseudo-encodings in writeLosslessRefresh under video mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When `video_mode_available` is true, `writeLosslessRefresh()` early-returns without emitting an FBU. That's correct for the lossless-refresh data path (video mode owns the pixel stream), but it also drops any pending pseudo-encoding rects: cursor shape, cursor position, LED state. The downstream effect: cursor SHAPE updates (e.g. xfwm4 swapping in the N-S-resize cursor as the pointer approaches a window edge) sit in `needSetCursor` until some other framebuffer activity triggers a fresh FBU. On an idle desktop the cursor stays whatever it was last drawn as, even after seconds of pointer movement. Trace into `VNCSConnectionST::writeDataUpdate()`: if (ui.is_empty() && !writer()->needFakeUpdate() && ...) return; ... if (!ui.is_empty()) { encodeManager.writeUpdate(...); } else { encodeManager.writeLosslessRefresh(...); // ← we land here } So when only a pseudo-encoding is pending and there are no pixel changes, control routes to `writeLosslessRefresh`, which then bails on video mode without flushing the pseudo-rects. Fix: before bailing, send a pseudo-only FBU if `needFakeUpdate()` is set. Uses `pseudoEncodingLastRect` (or counted nRects when the client doesn't support last-rect) so the message is self-terminating. --- common/rfb/EncodeManager.cxx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/common/rfb/EncodeManager.cxx b/common/rfb/EncodeManager.cxx index 01cb5a1e1..c3361693f 100644 --- a/common/rfb/EncodeManager.cxx +++ b/common/rfb/EncodeManager.cxx @@ -365,8 +365,21 @@ void EncodeManager::writeLosslessRefresh(const Region& req, const ScreenSet &la const RenderedCursor* renderedCursor, size_t maxUpdateSize) { - if (videoDetected || video_mode_available) + if (videoDetected || video_mode_available) { + // In video mode there's no lossless refresh to send, but pending + // pseudo-encodings (cursor shape, LED state, etc.) must still be + // flushed — otherwise the cursor shape lags whenever the desktop + // is idle (e.g. user hovering a window edge to resize). Without + // this, writeFramebufferUpdate() reaches us via writeDataUpdate's + // "ui.is_empty() but needFakeUpdate()" branch and we early-return, + // never emitting the FBU that would carry the pseudo-rects. + if (conn->writer()->needFakeUpdate()) { + int nRects = conn->cp.supportsLastRect ? 0xFFFF : 0; + conn->writer()->writeFramebufferUpdateStart(nRects); + conn->writer()->writeFramebufferUpdateEnd(); + } return; + } doUpdate(false, getLosslessRefresh(req, maxUpdateSize), Region(), Point(), std::vector(), layout, pb, renderedCursor);