From a9bc21245bc93f23214e3caafa22efdd33b6249a Mon Sep 17 00:00:00 2001 From: Dave Kempe Date: Thu, 28 May 2026 07:23:01 +1000 Subject: [PATCH] patches: add 005-rdp-resize-dirty-flush (RDP resize black regions) Addresses #118. After a dynamic RDP resize, guac_rdp_gdi_desktop_resize() resized the GDI buffer and display layer but never marked the layer dirty, so close_raw() flushed nothing and newly-exposed/stale regions rendered as solid black until something else repainted them. The patch marks the whole layer dirty and issues a RefreshRect for the new desktop area after the resize. Root cause diagnosed and fix supplied by @Bails309 on the issue; reworked here without the downstream debug logging and scoped to the resize function only (the end-paint handler shares the same close_raw call and must not be touched). Fixes the legacy bitmap path (rustguac's default, enable_gfx=false). The RDPGFX surface cache ignores RefreshRect so GFX sessions aren't addressed, but they have not reproduced the artifact in practice. Verified: applies cleanly via git apply against the pinned guacd 2980cf0; full guacd build with all five patches compiles and links the RDP plugin with no errors (only pre-existing FreeRDP deprecation warnings). --- patches/005-rdp-resize-dirty-flush.patch | 31 ++++++++++++++++++++++++ patches/README.md | 26 ++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 patches/005-rdp-resize-dirty-flush.patch diff --git a/patches/005-rdp-resize-dirty-flush.patch b/patches/005-rdp-resize-dirty-flush.patch new file mode 100644 index 0000000..5d43107 --- /dev/null +++ b/patches/005-rdp-resize-dirty-flush.patch @@ -0,0 +1,31 @@ +--- a/src/protocols/rdp/gdi.c ++++ b/src/protocols/rdp/gdi.c +@@ -188,6 +188,28 @@ + guac_client_log(client, GUAC_LOG_DEBUG, "Server resized display to %ix%i", + gdi->width, gdi->height); + ++ /* Mark the entire resized layer dirty so guac_display_layer_close_raw() ++ * flushes a full repaint to the client. Without this the dirty rect is ++ * empty after a resize, so newly-exposed (or stale) regions of the layer ++ * render as solid black until something else happens to repaint them. ++ * See sol1/rustguac#118. */ ++ guac_rect_init(¤t_context->dirty, 0, 0, gdi->width, gdi->height); ++ ++ /* Ask the server to re-send pixels for the full new desktop area. This ++ * covers the legacy bitmap update path. The RDPGFX surface cache ignores ++ * RefreshRect, so GFX sessions are not fixed by this and need a separate ++ * approach. */ ++ if (context->update != NULL && context->update->RefreshRect != NULL ++ && gdi->width > 0 && gdi->height > 0 ++ && gdi->width <= UINT16_MAX && gdi->height <= UINT16_MAX) { ++ RECTANGLE_16 area; ++ area.left = 0; ++ area.top = 0; ++ area.right = (UINT16) gdi->width; ++ area.bottom = (UINT16) gdi->height; ++ context->update->RefreshRect(context, 1, &area); ++ } ++ + guac_display_layer_close_raw(default_layer, current_context); + + return retval; diff --git a/patches/README.md b/patches/README.md index 8734d19..e2a7aa6 100644 --- a/patches/README.md +++ b/patches/README.md @@ -90,6 +90,32 @@ This approach avoids the socket contention issue that occurred when H.264 was se **Requires:** RDP server with H.264 support (xrdp with x264, or Windows with AVC hardware encoder). Browser must support WebCodecs VideoDecoder (Chrome/Edge 94+, Firefox 130+). +## 005-rdp-resize-dirty-flush.patch + +**Problem:** After a dynamic RDP display resize (browser window resized, +`resize-method=display-update`), regions of the desktop render as solid black +until something repaints them. `guac_rdp_gdi_desktop_resize()` resizes the +FreeRDP GDI buffer and the guac display layer but never marks the layer dirty, +so `guac_display_layer_close_raw()` flushes nothing and the client keeps its +stale/blank canvas for the resized layer. See [sol1/rustguac#118](https://github.com/sol1/rustguac/issues/118) (reported by @Bails309, who diagnosed the root cause and supplied the fix). + +**Fix:** In `guac_rdp_gdi_desktop_resize()`, after the layer resize and before +`guac_display_layer_close_raw()`: + +1. Mark the entire layer dirty (`guac_rect_init(¤t_context->dirty, ...)`) so a full repaint is flushed to the client. +2. Issue a `RefreshRect` for the full new desktop so the server re-sends authoritative pixels (legacy bitmap update path). + +**Scope:** Fixes the legacy bitmap update path, which is rustguac's default +(`enable_gfx` defaults to false). The RDPGFX surface cache ignores +`RefreshRect`, so GFX sessions are not addressed by this patch; in practice +GFX sessions have not reproduced the artifact. + +**Files patched:** + +| File | Fix | +|------|-----| +| `src/protocols/rdp/gdi.c` | Mark layer dirty + `RefreshRect` after resize in `guac_rdp_gdi_desktop_resize()` | + ## Applying patches Patches are applied automatically by all build scripts (`build-deb.sh`, `build-rpm.sh`, `install.sh`, `dev.sh`, `Dockerfile`). To apply manually: