Package: freerdp3-sdl
Version: 3.32.2+dfsg-1
Severity: important
Tags: patch upstream

Dear Maintainer,

On sway, sdl-freerdp3 cannot start a fullscreen (/f) or /multimon session.
It aborts in pre-connect, before any network traffic:

  $ sdl-freerdp3 /cert:ignore /f /v:127.0.0.1:9 /u:x /p:x
  [INFO][com.freerdp.client.SDL] - [preConnect]: Update size to 64x64
  [ERROR][com.freerdp.settings] - 
[freerdp_settings_client_monitors_check_primary_and_origin]: Monitor 
configuration virtual desktop width must be 200 <= 64 <= 32766
  [ERROR][com.freerdp.settings] - 
[freerdp_settings_client_monitors_check_primary_and_origin]: Monitor 
configuration virtual desktop height must be 200 <= 64 <= 32766
  [ERROR][com.freerdp.settings] - [log_monitor]: [0] [primary] {0x0-64x64} [3] 
{64x64, orientation: ORIENTATION_LANDSCAPE [0], desktopScale: 100, deviceScale: 
100}
  [ERROR][com.freerdp.core] - [freerdp_connect_begin]: 
ERRCONNECT_PRE_CONNECT_FAILED [0x00020001]
  A configuration error prevented a connection to be established.

(Any target works, because the failure comes before connecting. The output is
a single 1920x1080 sway output, and 
XDG_SESSION_DESKTOP=XDG_CURRENT_DESKTOP=sway.)

Windowed sessions (/size:WxH) work. SDL_VIDEODRIVER=x11 (XWayland) also fails
in the same way, reporting 75x64.

Analysis
--------

SdlContext::addOrUpdateDisplay() calls SdlWindow::query(id), which creates
a 64x64 probe window in createDummy(), calls SDL_SetWindowFullscreen(true),
and reads its size back. SdlWindow::rect() contains a workaround for exactly
this wlroots behaviour (the probe keeps its 64x64 creation size, so it falls
back to SDL_GetDisplayBounds()), but the workaround is gated by

    const auto fs = (flags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN;
    if (tryFallback(fs))

and on sway the probe window never reports SDL_WINDOW_FULLSCREEN. Because of
that, tryFallback() returns false in the default auto-detect mode and with
FREERDP_WLROOTS_HACK=1. Only FREERDP_WLROOTS_HACK=force, which overrides
isFullscreen, makes it work:

  default                      -> Update size to 64x64   -> PRE_CONNECT_FAILED
  FREERDP_WLROOTS_HACK=1       -> Update size to 64x64   -> PRE_CONNECT_FAILED
  FREERDP_WLROOTS_HACK=force   -> Update size to 1920x1080, connects

Upstream has the same root cause in
https://github.com/FreeRDP/FreeRDP/issues/13275 (reported for /multimon on
Hyprland, 3.30.0). That issue was closed with "use FREERDP_WLROOTS_HACK=force".
The code is unchanged in current upstream master.

Fix
---

The attached patch passes an "isProbe" flag from the two probe paths
(SdlWindow::query(SDL_DisplayID) and SdlWindow::rect(SDL_DisplayID)) into
SdlWindow::rect(SDL_Window*), which then treats the probe as fullscreen, since
that is what createDummy() requested. Real session windows
(SdlWindow::rect() const) still use the actual window flags, so windowed
sessions are unaffected. The fallback is still limited to detected wlroots
sessions under the Wayland driver, and FREERDP_WLROOTS_HACK=0 still turns it
off.

I built 3.32.2+dfsg-1 with the patch and tested it on sway 1.12 /
libsdl3-0 3.4.18:

  /f                          -> Update size to 1920x1080, reaches connect
  /f, FREERDP_WLROOTS_HACK=0  -> 64x64 / PRE_CONNECT_FAILED (opt-out honoured)
  /size:1280x720              -> reaches connect (unchanged)
  /multimon                   -> reaches connect

Workaround until fixed: export FREERDP_WLROOTS_HACK=force, or use /size:WxH
instead of /f.

-- System Information:
Debian Release: forky/sid
Architecture: amd64 (x86_64)
Kernel: Linux 7.2.12+deb14-amd64 (SMP w/32 CPU threads; PREEMPT)
Locale: LANG=C
Compositor: sway 1.12-1 (libwlroots-0.20 0.20.2-1)

Versions of packages freerdp3-sdl depends on:
ii  libfreerdp-client3-3  3.32.2+dfsg-1
ii  libfreerdp3-3         3.32.2+dfsg-1
ii  libsdl3-0             3.4.18+ds-1
ii  libwinpr3-3           3.32.2+dfsg-1

-- Patch (debian/patches/sdl-wlroots-probe-fullscreen.patch):

Description: sdl: let the wlroots fallback trigger for the display probe window
 SdlWindow::query()/rect() probe each display with a 64x64 window created by
 createDummy(), which requests fullscreen. On wlroots compositors (sway,
 Hyprland, river) the probe never reports SDL_WINDOW_FULLSCREEN, so
 tryFallback() returns false in its default (auto-detect) and "1" modes and the
 64x64 creation size is taken as the monitor size. FreeRDP then rejects the
 configuration ("virtual desktop width must be 200 <= 64 <= 32766") and /f and
 /multimon sessions abort with ERRCONNECT_PRE_CONNECT_FAILED. Only
 FREERDP_WLROOTS_HACK=force worked.
 .
 Treat the probe window as fullscreen, since that is what it asked for. Real
 session windows still use the actual window flags, and FREERDP_WLROOTS_HACK=0
 still turns the fallback off.
Author: Thomas Glanzmann <[email protected]>
Bug: https://github.com/FreeRDP/FreeRDP/issues/13275
Forwarded: no
Last-Update: 2026-11-02

--- a/client/SDL/SDL3/sdl_window.cpp    2026-10-12 13:45:30.000000000 +0200
+++ b/client/SDL/SDL3/sdl_window.cpp    2026-11-02 13:35:48.739790742 +0100
@@ -394,12 +394,13 @@
        return SDL_FillSurfaceRect(surface, &rect, color);
 }
 
-rdpMonitor SdlWindow::query(SDL_Window* window, SDL_DisplayID id, bool 
forceAsPrimary)
+rdpMonitor SdlWindow::query(SDL_Window* window, SDL_DisplayID id, bool 
forceAsPrimary,
+                             bool isProbe)
 {
        if (!window)
                return {};
 
-       const auto& r = rect(window, forceAsPrimary);
+       const auto& r = rect(window, forceAsPrimary, isProbe);
        const float factor = SDL_GetWindowDisplayScale(window);
        const float dpi = std::roundf(factor * 100.0f);
 
@@ -443,7 +444,7 @@
        return monitor;
 }
 
-SDL_Rect SdlWindow::rect(SDL_Window* window, bool forceAsPrimary)
+SDL_Rect SdlWindow::rect(SDL_Window* window, bool forceAsPrimary, bool isProbe)
 {
        SDL_Rect rect = {};
        if (!window)
@@ -460,7 +461,10 @@
 
        const auto flags = SDL_GetWindowFlags(window);
        const auto mask = SDL_WINDOW_FULLSCREEN;
-       const auto fs = (flags & mask) == mask;
+       /* The probe window from createDummy() always requests fullscreen, but 
wlroots
+        * compositors do not report it as fullscreen (it keeps its 64x64 
creation size).
+        * Treat it as fullscreen so the wlroots fallback below can trigger. */
+       const auto fs = isProbe || ((flags & mask) == mask);
        if (tryFallback(fs))
        {
                /* On wlroots compositors (Sway, river, etc.), windows that are 
hidden/unmapped
@@ -806,7 +810,7 @@
        while (SDL_PollEvent(&event))
                ;
 
-       return query(window.get(), id, forceAsPrimary);
+       return query(window.get(), id, forceAsPrimary, true);
 }
 
 SDL_Rect SdlWindow::rect(SDL_DisplayID id, bool forceAsPrimary)
@@ -825,7 +829,7 @@
        while (SDL_PollEvent(&event))
                ;
 
-       return rect(window.get(), forceAsPrimary);
+       return rect(window.get(), forceAsPrimary, true);
 }
 
 bool SdlWindow::tryFallback(bool isFullscreen)
--- a/client/SDL/SDL3/sdl_window.hpp    2026-10-12 13:45:30.000000000 +0200
+++ b/client/SDL/SDL3/sdl_window.hpp    2026-11-02 13:35:48.739514263 +0100
@@ -101,8 +101,9 @@
        [[nodiscard]] static bool fill(SDL_Window* window, Uint8 r = 0x00, 
Uint8 g = 0x00,
                                       Uint8 b = 0x00, Uint8 a = 0xff);
        [[nodiscard]] static rdpMonitor query(SDL_Window* window, SDL_DisplayID 
id,
-                                             bool forceAsPrimary = false);
-       [[nodiscard]] static SDL_Rect rect(SDL_Window* window, bool 
forceAsPrimary = false);
+                                             bool forceAsPrimary = false, bool 
isProbe = false);
+       [[nodiscard]] static SDL_Rect rect(SDL_Window* window, bool 
forceAsPrimary = false,
+                                          bool isProbe = false);
        [[nodiscard]] static SDL_Rect rect(SDL_DisplayID id, bool 
forceAsPrimary = false);
 
        [[nodiscard]] static bool tryFallback(bool isFullscreen);

Reply via email to