For a multi-channel SPICE guest (each head is a separate display channel, e.g. a Windows multi-QXL guest), publish_layout positioned each monitor at origin_x + the guest-reported config->x/y. But config->x/y is the guest's own virtual-desktop arrangement, not an offset within the channel's surface: once the guest rearranges a secondary head (which happens as soon as the primary is resized, e.g. by fit-on-connect), it reports config->x = primary width, so the monitor was placed at origin_x + config->x (double-counted) and clamped to a sliver off-canvas. This is exactly where the compositor already blits the surface (at origin_x), so publish each channel's whole surface at its origin and ignore the guest x/y. Single-channel guests (one combined surface with several monitor regions) still split by the reported regions.
guacamole-server patches
These patches are applied to the apache/guacamole-server source tree before building guacd. They fix compilation and runtime issues when building against FreeRDP 3.x on Debian 13 (Trixie).
001-freerdp3-debian13.patch
Problem: guacamole-server 1.6.1 fails to compile against FreeRDP 3.15+ (as shipped in Debian 13) due to:
-
Autoconf feature detection failure — FreeRDP 3.15 marks
codecs_free()as deprecated. The-Werrorflag inconfigure.accauses all compile-time feature-detection tests to fail, cascading into 10+ undefined macros and wrong#ifdefcode paths. -
Deprecated function pointer API — FreeRDP 3.x deprecates direct struct member access for
->input->KeyboardEvent(),->input->MouseEvent(), etc. behindWITH_FREERDP_DEPRECATED. The safe replacement functions arefreerdp_input_send_keyboard_event(),freerdp_input_send_mouse_event(), etc. -
NULL pointer dereference in display channel — FreeRDP 3.x fires PubSub
ChannelConnectedevents beforeguac_rdp_dispis allocated, causing a segfault when the callback writes todisp->requested_width(offset 0x18 of NULL).
Files patched:
| File | Fix |
|---|---|
configure.ac |
Add -Wno-error=deprecated-declarations to both FreeRDP 2.x and 3.x PKG_CHECK_MODULES blocks so autoconf feature detection works |
src/protocols/rdp/Makefile.am |
Add -Wno-error=deprecated-declarations to all three CFLAGS targets |
src/protocols/rdp/tests/Makefile.am |
Same for test CFLAGS |
src/protocols/rdp/keyboard.c |
Replace ->input->KeyboardEvent(), ->input->UnicodeKeyboardEvent(), ->input->SynchronizeEvent() with safe API functions |
src/protocols/rdp/input-queue.c |
Replace all ->input->MouseEvent() calls with freerdp_input_send_mouse_event() |
src/protocols/rdp/channels/disp.c |
Add NULL guards in guac_rdp_disp_channel_connected() and guac_rdp_disp_channel_disconnected() |
002-kerberos-nla.patch
Feature: Adds Kerberos NLA authentication support to guacd's RDP protocol, based on GUACAMOLE-2057 (PR #581). This allows RDP connections to use Kerberos instead of NTLM for NLA, which is required as Microsoft phases out NTLM.
Three new connection parameters:
| Parameter | Values | FreeRDP3 Setting |
|---|---|---|
auth-pkg |
"" (negotiate), "kerberos", "ntlm" |
FreeRDP_AuthenticationPackageList |
kdc-url |
KDC server URL (optional) | FreeRDP_KerberosKdcUrl |
kerberos-cache |
Path to ccache file (optional) | FreeRDP_KerberosCache |
Files patched:
| File | Fix |
|---|---|
src/protocols/rdp/settings.h |
Add guac_rdp_auth_package enum, add auth_pkg, kdc_url, kerberos_cache fields to guac_rdp_settings |
src/protocols/rdp/settings.c |
Add connection parameter parsing, FreeRDP3 settings push, memory cleanup |
Differences from upstream PR #581:
- Dropped FreeRDP2 code path (not needed on Debian 13)
- Fixed
guac_strdup()leak infreerdp_settings_set_string()calls (FreeRDP3 copies internally) - Fixed typos ("NTML" -> "NTLM", "negotiatoin" -> "negotiation")
Requires: FreeRDP 3.x built with Kerberos support (-DWITH_KRB5=ON). Debian 13's freerdp3-dev includes this by default.
003-null-guard-and-config-h.patch
Problem: Two related issues causing RDP display resize to silently fail:
-
Missing
config.hinclude — Several RDP channel source files andinput.cdo not includeconfig.h, soENABLE_COMMON_SSHis undefined in those compilation units. This causes theguac_rdp_clientstruct to have a different layout (missing 3 SSH pointer fields = 24 bytes), making all field accesses after the#ifdef ENABLE_COMMON_SSHblock read/write wrong memory offsets. Specifically,rdp_client->dispreads NULL (actually therecordingfield), so RDP display resizing silently fails. -
Early size instructions — Browser may send
sizeinstructions before the RDP connection is fully established, causing NULL pointer dereferences in the resize handler.
Files patched:
| File | Fix |
|---|---|
src/protocols/rdp/channels/common-svc.c |
Add #include "config.h" |
src/protocols/rdp/channels/disp.c |
Add #include "config.h", add NULL guard in guac_rdp_disp_set_size() |
src/protocols/rdp/channels/pipe-svc.c |
Add #include "config.h" |
src/protocols/rdp/channels/rdpei.c |
Add #include "config.h" |
src/protocols/rdp/channels/rdpgfx.c |
Add #include "config.h" |
src/protocols/rdp/input.c |
Add #include "config.h", add NULL guard in guac_rdp_user_size_handler() |
004-h264-display-worker.patch
Feature: H.264 passthrough via guac_display worker integration. When the RDP server sends AVC420 encoded frames (H.264), the raw NAL units are passed through to the browser's WebCodecs VideoDecoder instead of being decoded server-side and re-encoded as JPEG/PNG/WebP.
AVC420 only — AVC444 is deliberately disabled. The passthrough forwards a single H.264 bitstream per surface command. AVC420 carries a complete YUV420 frame, which WebCodecs decodes directly. AVC444 instead splits the image across two bitstreams (bitstream[0] luma/main view + bitstream[1] auxiliary chroma) to reconstruct YUV444; the passthrough only forwards bitstream[0], so a Windows host that negotiated AVC444 renders as a corrupted luma+chroma split — two blocks with green and magenta casts. We therefore advertise GfxH264 without GfxAVC444/GfxAVC444v2 so the server always uses AVC420. (RemoteFX/RFX is unaffected — it is a separate codec path and renders correctly.) Properly supporting AVC444 would require decoding both bitstreams and recombining the chroma planes in the browser (e.g. a second VideoDecoder + a WebGL merge shader), which is not implemented.
Architecture: The SurfaceCommand callback intercepts H.264 data and stores it on the display layer. During the normal frame flush cycle (guac_display_plan_apply), the H.264 data is sent to clients as a custom h264 instruction before worker threads start encoding. All IMG operations for the H.264 layer are skipped, eliminating the decode→re-encode overhead.
This approach avoids the socket contention issue that occurred when H.264 was sent directly from FreeRDP's SurfaceCommand callback thread, which raced with guac_display's worker threads writing to the same socket.
Files patched:
| File | Fix |
|---|---|
src/libguac/display-priv.h |
Add H.264 buffer fields to guac_display_layer (data, length, keyframe, rect) |
src/libguac/guacamole/display.h |
Add guac_display_layer_set_h264() public API |
src/libguac/display-layer.c |
Implement guac_display_layer_set_h264() with lock management |
src/libguac/display-layer-list.c |
Free H.264 data in layer cleanup |
src/libguac/display-plan.c |
Send H.264 data during plan apply, skip IMG ops for H.264 layers |
src/protocols/rdp/channels/rdpgfx.c |
Wrap SurfaceCommand to store H.264 on display layer after GDI decode |
src/protocols/rdp/settings.c |
Enable GfxH264 (AVC420) in FreeRDP settings; leave GfxAVC444 disabled (see AVC420-only note above) |
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 (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():
- Mark the entire layer dirty (
guac_rect_init(¤t_context->dirty, ...)) so a full repaint is flushed to the client. - Issue a
RefreshRectfor 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() |
007-rdp-disp-mod16.patch
Problem: When the negotiated RDP display dimensions aren't a multiple of 16,
the H.264 graphics pipeline (16x16 macroblocks) pads encoded frames with all-zero
YUV macroblocks. The chroma plane straddling the real/padding boundary contaminates
the bottom-most real chroma row, which after client-side bilinear scaling spreads
into a saturated green band (YUV(0,0,0) -> RGB ~ #008700) along the bottom edge.
Mod-2 rounding (the upstream default) is insufficient — the whole bottom 16-row
macroblock strip is affected.
Fix: In guac_rdp_disp_set_size(), round both width and height down to a
multiple of 16 (replacing the existing "width must be even" mod-2 rounding). Costs
up to 15px of unused canvas margin, avoidable by sizing the viewport so the
requested height is already mod-16.
Ported from pletch/guacamole-server@b28bdac (fixes-1.6.0). Complements 005-rdp-resize-dirty-flush.patch: 005 fixes black regions on the legacy bitmap path, 007 fixes the green band on the H.264/GFX path.
Files patched:
| File | Fix |
|---|---|
src/protocols/rdp/channels/disp.c |
Round display dimensions down to mod-16 in guac_rdp_disp_set_size() |
008-spice-protocol.patch
Feature: Adds native SPICE protocol support (libguac-client-spice), vendored from the upstream PR apache/guacamole-server#688 (GUACAMOLE-261). Enables connecting to SPICE displays (e.g. Proxmox VE / QEMU consoles). Requires libspice-client-glib-2.0-dev (>= 0.38) at build time; guacd is configured --with-spice.
Vendored as the diff of the PR branch against its merge-base with our pinned guacd. The PR's incidental, non-SPICE change to src/terminal/terminal.c (SSH terminal keyboard-modifier handling) is excluded here: it is unrelated to SPICE and conflicted with our pinned base. The bundled guacclip tool is included in the source but not built (--disable-guacclip, matching how we treat guacenc/guaclog); see sol1/rustguac#181 for a possible future clipboard-audit feature.
Files patched: new src/protocols/spice/* and src/guacclip/* trees, plus additive hooks in configure.ac, Makefile.am, src/libguac/* (protocol constants, user handlers, rect), and per-protocol input.c.
009-spice-empty-port.patch
Bug: For TLS-only SPICE (e.g. Proxmox VE consoles) rustguac sends an empty port connect arg so guacd connects via tls-port. guac_spice_session_configure() set the spice-gtk port property whenever settings->port != NULL, but the parsed value for an omitted arg is an empty string (non-NULL), so spice-gtk logged GSpice: Invalid port value on every channel while parsing "".
Fix: Only set the plain port when it is non-empty (settings->port[0] != '\0'), so TLS-only connections use tls-port cleanly with no warning.
Files patched: src/protocols/spice/auth.c.
010-rdp-multimonitor.patch
Feature: Adds RDP multi-monitor support. A secondary-monitors arg enables it; the Display Update module (channels/disp.c) tracks a per-monitor layout (tiled left-to-right, top-aligned, with RDP-valid geometry) and sends the full DISPLAY_CONTROL_MONITOR_LAYOUT array via SendMonitorLayout instead of a single monitor. The RDP host extends the desktop across the monitors and streams one combined framebuffer, so no client-side compositing is needed (unlike SPICE). guacd advertises secondary-monitors on user join and publishes the multimon-layout layer parameter so a multi-monitor client can split the framebuffer into per-monitor windows. Reuses the protocol-agnostic client machinery added with 008.
Files patched: src/protocols/rdp/settings.{c,h}, src/protocols/rdp/channels/disp.{c,h}, src/protocols/rdp/input.c, src/protocols/rdp/user.c.
Applying patches
Patches are applied automatically by all build scripts (build-deb.sh, build-rpm.sh, install.sh, dev.sh, Dockerfile). To apply manually:
cd ../guacamole-server
git apply ../rustguac/patches/001-freerdp3-debian13.patch
To check if patches are already applied:
cd ../guacamole-server
git apply --check ../rustguac/patches/001-freerdp3-debian13.patch 2>&1 || echo "Already applied or conflict"
Adding new patches
- Make changes in the
../guacamole-serverworking tree - Export:
cd ../guacamole-server && git diff > ../rustguac/patches/NNN-description.patch - Patches are applied in numeric order by the build scripts