From 8d1f6c24d7096a3ee787251da556da3577f7f509 Mon Sep 17 00:00:00 2001 From: Dave Kempe Date: Tue, 28 Jul 2026 09:46:40 +1000 Subject: [PATCH] fix(multimon): correct SPICE layout when the guest rearranges a secondary head 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. --- patches/008-spice-protocol.patch | 35 ++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/patches/008-spice-protocol.patch b/patches/008-spice-protocol.patch index e1462c8..1243374 100644 --- a/patches/008-spice-protocol.patch +++ b/patches/008-spice-protocol.patch @@ -9666,10 +9666,10 @@ index 00000000..f6638394 + diff --git a/src/protocols/spice/display.c b/src/protocols/spice/display.c new file mode 100644 -index 00000000..c00e0e7e +index 00000000..55970c69 --- /dev/null +++ b/src/protocols/spice/display.c -@@ -0,0 +1,619 @@ +@@ -0,0 +1,646 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file @@ -9910,6 +9910,17 @@ index 00000000..c00e0e7e + int written = 0; + int index = 0; + ++ /* Count active display channels. Multiple channels means each channel's ++ * surface is a single monitor (e.g. a Windows multi-QXL guest); a single ++ * channel may instead carry several monitor regions within one combined ++ * surface (e.g. a Linux multi-head QXL). These need opposite positioning. */ ++ int active = 0; ++ for (int i = 0; i < GUAC_SPICE_MAX_MONITORS; i++) { ++ guac_spice_display_state* d = &spice_client->displays[i]; ++ if (d->channel != NULL && d->data != NULL && d->width > 0 && d->height > 0) ++ active++; ++ } ++ + json[pos++] = '{'; + + for (int i = 0; i < GUAC_SPICE_MAX_MONITORS; i++) { @@ -9919,8 +9930,24 @@ index 00000000..c00e0e7e + || display->width <= 0 || display->height <= 0) + continue; + -+ /* Prefer the guest's actual monitor regions within this channel's -+ * surface, but only once a surface exists to validate them against */ ++ /* Multiple display channels: each channel's surface IS one monitor, ++ * placed at its composited origin (exactly where the compositor blits ++ * it). The guest's per-channel monitor x/y describe its OWN desktop ++ * arrangement, which need not match our left-to-right tiling and can ++ * even fall outside the channel's own surface — adding them on top of ++ * the origin sends a rearranged secondary head far off-canvas with a ++ * clamped sliver width. So publish the whole surface at the origin. */ ++ if (active > 1) { ++ int next = guac_spice_layout_append(json, pos, sizeof(json), ++ written, index, display->origin_x, display->origin_y, ++ display->width, display->height); ++ if (next >= 0) { pos = next; written++; index++; } ++ continue; ++ } ++ ++ /* Single display channel: its combined surface may itself hold several ++ * monitor regions — split it using the guest-reported regions, which in ++ * this case ARE offsets within this one surface. */ + GArray* monitors = NULL; + g_object_get(SPICE_DISPLAY_CHANNEL(display->channel), "monitors", + &monitors, NULL);