FreeRDP
Loading...
Searching...
No Matches
SDL3/sdl_window.cpp
1
20#include <limits>
21#include <sstream>
22#include <cmath>
23
24#include "sdl_window.hpp"
25#include "sdl_utils.hpp"
26
27#include <freerdp/utils/string.h>
28
29SdlWindow::SdlWindow(SDL_DisplayID id, const std::string& title, const SDL_Rect& rect,
30 [[maybe_unused]] Uint32 flags)
31 : _initialW(rect.w), _initialH(rect.h), _displayID(id)
32{
33 float pd = SDL_GetDisplayContentScale(id);
34 if (pd <= 0.0f)
35 pd = 1.0f;
36 const int createW = static_cast<int>(std::ceil(static_cast<float>(rect.w) / pd));
37 const int createH = static_cast<int>(std::ceil(static_cast<float>(rect.h) / pd));
38
39 auto props = SDL_CreateProperties();
40 SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title.c_str());
41 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, rect.x);
42 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, rect.y);
43 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, createW);
44 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, createH);
45 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true);
46
47 if (flags & SDL_WINDOW_HIGH_PIXEL_DENSITY)
48 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
49
50 if (flags & SDL_WINDOW_FULLSCREEN)
51 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, true);
52
53 if (flags & SDL_WINDOW_BORDERLESS)
54 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN, true);
55
56 _window = SDL_CreateWindowWithProperties(props);
57 SDL_DestroyProperties(props);
58 SDL_SetHint(SDL_HINT_APP_NAME, "");
59 std::ignore = SDL_SyncWindow(_window);
60
61 _renderer = SDL_CreateRenderer(_window, nullptr);
62
63 std::ignore = resizeToScale();
64
65 _monitor = query(_window, id, true);
66}
67
68SdlWindow::SdlWindow(SdlWindow&& other) noexcept
69 : _window(other._window), _renderer(other._renderer), _renderTarget(other._renderTarget),
70 _gdiTexture(other._gdiTexture), _gdiTextureW(other._gdiTextureW),
71 _gdiTextureH(other._gdiTextureH), _initialW(other._initialW), _initialH(other._initialH),
72 _displayID(other._displayID), _offset_x(other._offset_x), _offset_y(other._offset_y),
73 _monitor(other._monitor)
74{
75 other._window = nullptr;
76 other._renderer = nullptr;
77 other._renderTarget = nullptr;
78 other._gdiTexture = nullptr;
79}
80
81SdlWindow::~SdlWindow()
82{
83 if (_gdiTexture)
84 SDL_DestroyTexture(_gdiTexture);
85 if (_renderTarget)
86 SDL_DestroyTexture(_renderTarget);
87 if (_renderer)
88 SDL_DestroyRenderer(_renderer);
89 if (_window)
90 SDL_DestroyWindow(_window);
91}
92
93SDL_WindowID SdlWindow::id() const
94{
95 if (!_window)
96 return 0;
97 return SDL_GetWindowID(_window);
98}
99
100SDL_DisplayID SdlWindow::displayIndex() const
101{
102 if (!_window)
103 return 0;
104 return SDL_GetDisplayForWindow(_window);
105}
106
107SDL_Rect SdlWindow::rect() const
108{
109 return rect(_window);
110}
111
112SDL_Rect SdlWindow::bounds() const
113{
114 SDL_Rect rect = {};
115 if (_window)
116 {
117 if (!SDL_GetWindowPosition(_window, &rect.x, &rect.y))
118 return {};
119 if (!SDL_GetWindowSize(_window, &rect.w, &rect.h))
120 return {};
121 }
122 return rect;
123}
124
125SDL_Window* SdlWindow::window() const
126{
127 return _window;
128}
129
130SDL_Renderer* SdlWindow::renderer() const
131{
132 return _renderer;
133}
134
135Sint32 SdlWindow::offsetX() const
136{
137 return _offset_x;
138}
139
140void SdlWindow::setOffsetX(Sint32 x)
141{
142 _offset_x = x;
143}
144
145void SdlWindow::setOffsetY(Sint32 y)
146{
147 _offset_y = y;
148}
149
150Sint32 SdlWindow::offsetY() const
151{
152 return _offset_y;
153}
154
155rdpMonitor SdlWindow::monitor(bool isPrimary) const
156{
157 auto m = _monitor;
158 if (isPrimary)
159 {
160 m.x = 0;
161 m.y = 0;
162 }
163 return m;
164}
165
166void SdlWindow::setMonitor(rdpMonitor monitor)
167{
168 _monitor = monitor;
169}
170
171float SdlWindow::scale() const
172{
173 return SDL_GetWindowDisplayScale(_window);
174}
175
176SDL_DisplayOrientation SdlWindow::orientation() const
177{
178 const auto did = displayIndex();
179 return SDL_GetCurrentDisplayOrientation(did);
180}
181
182bool SdlWindow::grabKeyboard(bool enable)
183{
184 if (!_window)
185 return false;
186 SDL_SetWindowKeyboardGrab(_window, enable);
187 return true;
188}
189
190bool SdlWindow::grabMouse(bool enable)
191{
192 if (!_window)
193 return false;
194 SDL_SetWindowMouseGrab(_window, enable);
195 return true;
196}
197
198void SdlWindow::setBordered(bool bordered)
199{
200 if (_window)
201 SDL_SetWindowBordered(_window, bordered);
202 std::ignore = SDL_SyncWindow(_window);
203}
204
205void SdlWindow::raise()
206{
207 SDL_RaiseWindow(_window);
208 std::ignore = SDL_SyncWindow(_window);
209}
210
211void SdlWindow::resizeable(bool use)
212{
213 SDL_SetWindowResizable(_window, use);
214 std::ignore = SDL_SyncWindow(_window);
215}
216
217void SdlWindow::fullscreen(bool enter, bool forceOriginalDisplay)
218{
219 if (enter && forceOriginalDisplay && _displayID != 0)
220 {
221 /* Move the window to the desired display. We should not wait
222 * for the window to be moved, because some backends can refuse
223 * the move. The intent of moving the window is enough for SDL
224 * to decide which display will be used for fullscreen. */
225 SDL_Rect rect = {};
226 std::ignore = SDL_GetDisplayBounds(_displayID, &rect);
227 std::ignore = SDL_SetWindowPosition(_window, rect.x, rect.y);
228 }
229 std::ignore = SDL_SetWindowFullscreen(_window, enter);
230 std::ignore = SDL_SyncWindow(_window);
231}
232
233void SdlWindow::minimize()
234{
235 SDL_MinimizeWindow(_window);
236 std::ignore = SDL_SyncWindow(_window);
237}
238
239bool SdlWindow::resizeToScale()
240{
241 if (!_window || _initialW <= 0 || _initialH <= 0)
242 return false;
243 if ((SDL_GetWindowFlags(_window) & SDL_WINDOW_FULLSCREEN) != 0)
244 return true;
245
246 float pd = SDL_GetWindowPixelDensity(_window);
247 if (pd <= 0.0f)
248 pd = 1.0f;
249
250 const int targetW = static_cast<int>(std::ceil(static_cast<float>(_initialW) / pd));
251 const int targetH = static_cast<int>(std::ceil(static_cast<float>(_initialH) / pd));
252
253 int curW = 0;
254 int curH = 0;
255 if (!SDL_GetWindowSize(_window, &curW, &curH))
256 return false;
257
258 if (curW == targetW && curH == targetH)
259 return true;
260
261 return resize({ targetW, targetH });
262}
263
264bool SdlWindow::resize(const SDL_Point& size)
265{
266 return SDL_SetWindowSize(_window, size.x, size.y);
267}
268
269void SdlWindow::ensureRenderTarget()
270{
271 if (!_renderer)
272 return;
273
274 int w = 0;
275 int h = 0;
276 SDL_GetWindowSizeInPixels(_window, &w, &h);
277 if (w <= 0 || h <= 0)
278 return;
279
280 /* Recreate if missing or if window size changed */
281 if (_renderTarget)
282 {
283 float tw = 0;
284 float th = 0;
285 if (!SDL_GetTextureSize(_renderTarget, &tw, &th))
286 return;
287 if (static_cast<int>(tw) == w && static_cast<int>(th) == h)
288 return;
289 SDL_DestroyTexture(_renderTarget);
290 }
291
292 _renderTarget =
293 SDL_CreateTexture(_renderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_TARGET, w, h);
294 if (!_renderTarget)
295 SDL_LogError(SDL_LOG_CATEGORY_RENDER, "SDL_CreateTexture (render target): %s",
296 SDL_GetError());
297}
298
299bool SdlWindow::drawRect(SDL_Surface* surface, SDL_Point offset, const SDL_Rect& srcRect)
300{
301 WINPR_ASSERT(surface);
302 SDL_Rect dstRect = { offset.x + srcRect.x, offset.y + srcRect.y, srcRect.w, srcRect.h };
303 return blit(surface, srcRect, dstRect);
304}
305
306bool SdlWindow::drawRects(SDL_Surface* surface, SDL_Point offset,
307 const std::vector<SDL_Rect>& rects)
308{
309 if (rects.empty())
310 {
311 return drawRect(surface, offset, { 0, 0, surface->w, surface->h });
312 }
313 for (auto& srcRect : rects)
314 {
315 if (!drawRect(surface, offset, srcRect))
316 return false;
317 }
318 return true;
319}
320
321bool SdlWindow::drawScaledRect(SDL_Surface* surface, const SDL_FPoint& scale,
322 const SDL_Rect& srcRect)
323{
324 SDL_Rect dstRect = {};
325 float ix = 0.0f;
326 float iy = 0.0f;
327 const auto modx = std::modf(static_cast<float>(srcRect.x) * scale.x, &ix);
328 const auto mody = std::modf(static_cast<float>(srcRect.y) * scale.y, &iy);
329 auto sw = std::ceil(static_cast<float>(srcRect.w) * scale.x) + std::ceil(modx);
330 auto sh = std::ceil(static_cast<float>(srcRect.h) * scale.y) + std::ceil(mody);
331 dstRect.x = static_cast<Sint32>(ix);
332 dstRect.w = static_cast<Sint32>(sw);
333 dstRect.y = static_cast<Sint32>(iy);
334 dstRect.h = static_cast<Sint32>(sh);
335 return blit(surface, srcRect, dstRect);
336}
337
338bool SdlWindow::drawScaledRects(SDL_Surface* surface, const SDL_FPoint& scale,
339 const std::vector<SDL_Rect>& rects)
340{
341 if (rects.empty())
342 {
343 return drawScaledRect(surface, scale, { 0, 0, surface->w, surface->h });
344 }
345 for (const auto& srcRect : rects)
346 {
347 if (!drawScaledRect(surface, scale, srcRect))
348 return false;
349 }
350 return true;
351}
352
353bool SdlWindow::fill(Uint8 r, Uint8 g, Uint8 b, Uint8 a)
354{
355 if (_renderer)
356 {
357 ensureRenderTarget();
358 if (!SDL_SetRenderTarget(_renderer, _renderTarget))
359 return false;
360 if (!SDL_SetRenderDrawColor(_renderer, r, g, b, a))
361 return false;
362 return SDL_RenderClear(_renderer);
363 }
364 return fill(_window, r, g, b, a);
365}
366
367bool SdlWindow::fill(SDL_Window* window, Uint8 r, Uint8 g, Uint8 b, Uint8 a)
368{
369 auto surface = SDL_GetWindowSurface(window);
370 if (!surface)
371 return false;
372 SDL_Rect rect = { 0, 0, surface->w, surface->h };
373 auto color = SDL_MapSurfaceRGBA(surface, r, g, b, a);
374
375 return SDL_FillSurfaceRect(surface, &rect, color);
376}
377
378rdpMonitor SdlWindow::query(SDL_Window* window, SDL_DisplayID id, bool forceAsPrimary)
379{
380 if (!window)
381 return {};
382
383 const auto& r = rect(window, forceAsPrimary);
384 const float factor = SDL_GetWindowDisplayScale(window);
385 const float dpi = std::roundf(factor * 100.0f);
386
387 WINPR_ASSERT(r.w > 0);
388 WINPR_ASSERT(r.h > 0);
389
390 const auto primary = SDL_GetPrimaryDisplay();
391 const auto orientation = SDL_GetCurrentDisplayOrientation(id);
392 const auto rdp_orientation = sdl::utils::orientaion_to_rdp(orientation);
393
394 rdpMonitor monitor{};
395 monitor.orig_screen = id;
396 monitor.x = r.x;
397 monitor.y = r.y;
398 monitor.width = r.w;
399 monitor.height = r.h;
400 monitor.is_primary = forceAsPrimary || (id == primary);
401 monitor.attributes.desktopScaleFactor = static_cast<UINT32>(dpi);
402 monitor.attributes.deviceScaleFactor = 100;
403 monitor.attributes.orientation = rdp_orientation;
404 monitor.attributes.physicalWidth = WINPR_ASSERTING_INT_CAST(uint32_t, r.w);
405 monitor.attributes.physicalHeight = WINPR_ASSERTING_INT_CAST(uint32_t, r.h);
406
407 const auto cat = SDL_LOG_CATEGORY_APPLICATION;
408 SDL_LogDebug(cat, "monitor.orig_screen %" PRIu32, monitor.orig_screen);
409 SDL_LogDebug(cat, "monitor.x %" PRId32, monitor.x);
410 SDL_LogDebug(cat, "monitor.y %" PRId32, monitor.y);
411 SDL_LogDebug(cat, "monitor.width %" PRId32, monitor.width);
412 SDL_LogDebug(cat, "monitor.height %" PRId32, monitor.height);
413 SDL_LogDebug(cat, "monitor.is_primary %" PRIu32, monitor.is_primary);
414 SDL_LogDebug(cat, "monitor.attributes.desktopScaleFactor %" PRIu32,
415 monitor.attributes.desktopScaleFactor);
416 SDL_LogDebug(cat, "monitor.attributes.deviceScaleFactor %" PRIu32,
417 monitor.attributes.deviceScaleFactor);
418 SDL_LogDebug(cat, "monitor.attributes.orientation %s",
419 freerdp_desktop_rotation_flags_to_string(monitor.attributes.orientation));
420 SDL_LogDebug(cat, "monitor.attributes.physicalWidth %" PRIu32,
421 monitor.attributes.physicalWidth);
422 SDL_LogDebug(cat, "monitor.attributes.physicalHeight %" PRIu32,
423 monitor.attributes.physicalHeight);
424 return monitor;
425}
426
427SDL_Rect SdlWindow::rect(SDL_Window* window, bool forceAsPrimary)
428{
429 SDL_Rect rect = {};
430 if (!window)
431 return {};
432
433 if (!forceAsPrimary)
434 {
435 if (!SDL_GetWindowPosition(window, &rect.x, &rect.y))
436 return {};
437 }
438
439 if (!SDL_GetWindowSizeInPixels(window, &rect.w, &rect.h))
440 return {};
441
442 const auto flags = SDL_GetWindowFlags(window);
443 const auto mask = SDL_WINDOW_FULLSCREEN;
444 const auto fs = (flags & mask) == mask;
445 if (tryFallback(fs))
446 {
447 /* On wlroots compositors (Sway, river, etc.), windows that are hidden/unmapped
448 * don't get their actual display dimensions. The dummy window returns its creation size
449 * (64x64) instead of the display size. This causes validation errors since we require >=
450 * 200px. Workaround: If we got dimensions that are too small, query the display directly.
451 */
452
453 const auto displayID = SDL_GetDisplayForWindow(window);
454 SDL_Rect displayBounds = {};
455 if (SDL_GetDisplayBounds(displayID, &displayBounds))
456 {
457 if (forceAsPrimary)
458 {
459 rect.x = 0;
460 rect.y = 0;
461 }
462 rect.w = displayBounds.w;
463 rect.h = displayBounds.h;
464
465 const float contentScale = SDL_GetDisplayContentScale(displayID);
466 if (contentScale > 1.0f)
467 {
468 const auto fw = static_cast<float>(rect.w);
469 const auto fh = static_cast<float>(rect.h);
470 rect.w = static_cast<int>(std::roundf(fw * contentScale));
471 rect.h = static_cast<int>(std::roundf(fh * contentScale));
472 }
473 }
474 }
475
476 return rect;
477}
478
479SdlWindow::HighDPIMode SdlWindow::isHighDPIWindowsMode(SDL_Window* window)
480{
481 if (!window)
482 return MODE_INVALID;
483
484 const auto id = SDL_GetDisplayForWindow(window);
485 if (id == 0)
486 return MODE_INVALID;
487
488 const auto cs = SDL_GetDisplayContentScale(id);
489 const auto ds = SDL_GetWindowDisplayScale(window);
490 const auto pd = SDL_GetWindowPixelDensity(window);
491
492 /* mac os x style, but no HighDPI display */
493 if ((cs == 1.0f) && (ds == 1.0f) && (pd == 1.0f))
494 return MODE_NONE;
495
496 /* mac os x style HighDPI */
497 if ((cs == 1.0f) && (ds > 1.0f) && (pd > 1.0f))
498 return MODE_MACOS;
499
500 /* rest is windows style */
501 return MODE_WINDOWS;
502}
503
504bool SdlWindow::blit(SDL_Surface* surface, const SDL_Rect& srcRect, SDL_Rect& dstRect)
505{
506 if (!_renderer || !surface)
507 return false;
508
509 /* Lazily create or recreate the persistent GDI texture */
510 if (!_gdiTexture || _gdiTextureW != surface->w || _gdiTextureH != surface->h)
511 {
512 if (_gdiTexture)
513 SDL_DestroyTexture(_gdiTexture);
514 _gdiTexture = SDL_CreateTexture(_renderer, surface->format, SDL_TEXTUREACCESS_STREAMING,
515 surface->w, surface->h);
516 if (!_gdiTexture)
517 {
518 SDL_LogError(SDL_LOG_CATEGORY_RENDER, "SDL_CreateTexture: %s", SDL_GetError());
519 return false;
520 }
521 _gdiTextureW = surface->w;
522 _gdiTextureH = surface->h;
523 }
524
525 /* Upload only the dirty region */
526 const auto* details = SDL_GetPixelFormatDetails(surface->format);
527 const int bpp = details ? details->bytes_per_pixel : 4;
528 const auto* pixels = static_cast<const uint8_t*>(surface->pixels) +
529 (1ll * srcRect.y * surface->pitch) + (1ll * srcRect.x * bpp);
530 if (!SDL_UpdateTexture(_gdiTexture, &srcRect, pixels, surface->pitch))
531 {
532 SDL_LogError(SDL_LOG_CATEGORY_RENDER, "SDL_UpdateTexture: %s", SDL_GetError());
533 return false;
534 }
535
536 /* Render onto persistent render target to accumulate dirty rects */
537 if (!SDL_SetRenderTarget(_renderer, _renderTarget))
538 return false;
539
540 SDL_FRect fsrc = { static_cast<float>(srcRect.x), static_cast<float>(srcRect.y),
541 static_cast<float>(srcRect.w), static_cast<float>(srcRect.h) };
542 SDL_FRect fdst = { static_cast<float>(dstRect.x), static_cast<float>(dstRect.y),
543 static_cast<float>(dstRect.w), static_cast<float>(dstRect.h) };
544 if (!SDL_RenderTexture(_renderer, _gdiTexture, &fsrc, &fdst))
545 {
546 SDL_LogError(SDL_LOG_CATEGORY_RENDER, "SDL_RenderTexture: %s", SDL_GetError());
547 return false;
548 }
549 return true;
550}
551
552void SdlWindow::updateSurface()
553{
554 if (!_renderer)
555 return;
556
557 ensureRenderTarget();
558
559 /* Copy accumulated render target to screen and present */
560 if (!SDL_SetRenderTarget(_renderer, nullptr))
561 return;
562 if (!SDL_RenderTexture(_renderer, _renderTarget, nullptr, nullptr))
563 return;
564 if (!SDL_RenderPresent(_renderer))
565 return;
566}
567
568SdlWindow SdlWindow::create(SDL_DisplayID id, const std::string& title, Uint32 flags, Uint32 width,
569 Uint32 height)
570{
571 flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
572
573 SDL_Rect rect = { static_cast<int>(SDL_WINDOWPOS_CENTERED_DISPLAY(id)),
574 static_cast<int>(SDL_WINDOWPOS_CENTERED_DISPLAY(id)), static_cast<int>(width),
575 static_cast<int>(height) };
576
577 if ((flags & SDL_WINDOW_FULLSCREEN) != 0)
578 {
579 std::ignore = SDL_GetDisplayBounds(id, &rect);
580 }
581
582 SdlWindow window{ id, title, rect, flags };
583
584 if ((flags & SDL_WINDOW_FULLSCREEN) != 0)
585 {
586 window.setOffsetX(rect.x);
587 window.setOffsetY(rect.y);
588 }
589
590 return window;
591}
592
593static SDL_Window* createDummy(SDL_DisplayID id)
594{
595 const auto x = SDL_WINDOWPOS_CENTERED_DISPLAY(id);
596 const auto y = SDL_WINDOWPOS_CENTERED_DISPLAY(id);
597 const int w = 64;
598 const int h = 64;
599
600 auto props = SDL_CreateProperties();
601 std::stringstream ss;
602 ss << "SdlWindow::query(" << id << ")";
603 SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, ss.str().c_str());
604 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, x);
605 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, y);
606 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, w);
607 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, h);
608
609 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
610 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, false);
611 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN, true);
612 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIDDEN_BOOLEAN, false);
613
614 auto window = SDL_CreateWindowWithProperties(props);
615 SDL_DestroyProperties(props);
616
617 /* Workaround: we need to properly position the window on the correct monitor
618 * before going fullscreen. Otherwise we will get the primary monitor details.
619 */
620 if (window)
621 {
622 SDL_Rect rect = {};
623 std::ignore = SDL_GetDisplayBounds(id, &rect);
624 std::ignore = SDL_SetWindowPosition(window, rect.x, rect.y);
625 std::ignore = SDL_SetWindowFullscreen(window, true);
626 }
627 return window;
628}
629
630rdpMonitor SdlWindow::query(SDL_DisplayID id, bool forceAsPrimary)
631{
632 std::unique_ptr<SDL_Window, void (*)(SDL_Window*)> window(createDummy(id), SDL_DestroyWindow);
633 if (!window)
634 return {};
635
636 std::unique_ptr<SDL_Renderer, void (*)(SDL_Renderer*)> renderer(
637 SDL_CreateRenderer(window.get(), nullptr), SDL_DestroyRenderer);
638
639 if (!SDL_SyncWindow(window.get()))
640 return {};
641
642 SDL_Event event{};
643 while (SDL_PollEvent(&event))
644 ;
645
646 return query(window.get(), id, forceAsPrimary);
647}
648
649SDL_Rect SdlWindow::rect(SDL_DisplayID id, bool forceAsPrimary)
650{
651 std::unique_ptr<SDL_Window, void (*)(SDL_Window*)> window(createDummy(id), SDL_DestroyWindow);
652 if (!window)
653 return {};
654
655 std::unique_ptr<SDL_Renderer, void (*)(SDL_Renderer*)> renderer(
656 SDL_CreateRenderer(window.get(), nullptr), SDL_DestroyRenderer);
657
658 if (!SDL_SyncWindow(window.get()))
659 return {};
660
661 SDL_Event event{};
662 while (SDL_PollEvent(&event))
663 ;
664
665 return rect(window.get(), forceAsPrimary);
666}
667
668bool SdlWindow::tryFallback(bool isFullscreen)
669{
670 /* If we define a custom env variable to use the wlroots hack
671 * then enable/disable according to this setting only.
672 */
673 const auto wlroots_hack = SDL_getenv("FREERDP_WLROOTS_HACK");
674 if (wlroots_hack != nullptr)
675 {
676 const auto enabled = strcmp(wlroots_hack, "0") != 0;
677 if (strcmp(wlroots_hack, "force") == 0)
678 isFullscreen = true;
679 return enabled && isFullscreen;
680 }
681
682 const auto platform = SDL_GetPlatform();
683 if ((platform == nullptr) || (strcmp(platform, "Linux") != 0))
684 return false;
685
686 const auto driver = SDL_GetCurrentVideoDriver();
687 if ((driver == nullptr) || (strcmp(driver, "wayland") != 0))
688 return false;
689
690 /* Check XDG_SESSION_DESKTOP and XDG_CURRENT_DESKTOP for wlroots-based
691 * compositors. The original check only matched Sway, but other wlroots
692 * compositors (Hyprland, river, etc.) have the same dummy-window sizing
693 * behavior where hidden/unmapped windows return 64x64 instead of the
694 * display size. Use strstr for substring matching since XDG_CURRENT_DESKTOP
695 * can be a colon-separated list (e.g. "sway:wlroots", "Hyprland").
696 */
697 auto isWlrootsCompositor = [](const char* value) -> bool
698 {
699 if (!value)
700 return false;
701 if (strstr(value, "sway") || strstr(value, "Sway") || strstr(value, "Hyprland") ||
702 strstr(value, "hyprland") || strstr(value, "river") || strstr(value, "wlroots"))
703 return true;
704 return false;
705 };
706
707 const auto xdg_session = SDL_getenv("XDG_SESSION_DESKTOP");
708 if (isWlrootsCompositor(xdg_session))
709 return isFullscreen;
710
711 const auto xdg_desktop = SDL_getenv("XDG_CURRENT_DESKTOP");
712 if (isWlrootsCompositor(xdg_desktop))
713 return isFullscreen;
714
715 return false;
716}
SdlWindow(const std::string &title, Sint32 startupX, Sint32 startupY, Sint32 width, Sint32 height, Uint32 flags)