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 : _displayID(id)
32{
33 auto props = SDL_CreateProperties();
34 SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title.c_str());
35 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, rect.x);
36 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, rect.y);
37 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, rect.w);
38 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, rect.h);
39
40 if (flags & SDL_WINDOW_HIGH_PIXEL_DENSITY)
41 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
42
43 if (flags & SDL_WINDOW_FULLSCREEN)
44 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, true);
45
46 if (flags & SDL_WINDOW_BORDERLESS)
47 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN, true);
48
49 _window = SDL_CreateWindowWithProperties(props);
50 SDL_DestroyProperties(props);
51
52 std::ignore = resizeToPixelSize({ rect.w, rect.h });
53 SDL_SetHint(SDL_HINT_APP_NAME, "");
54 std::ignore = SDL_SyncWindow(_window);
55
56 _monitor = query(_window, id, true);
57}
58
59SdlWindow::SdlWindow(SdlWindow&& other) noexcept
60 : _window(other._window), _displayID(other._displayID), _offset_x(other._offset_x),
61 _offset_y(other._offset_y), _monitor(other._monitor)
62{
63 other._window = nullptr;
64}
65
66SdlWindow::~SdlWindow()
67{
68 SDL_DestroyWindow(_window);
69}
70
71SDL_WindowID SdlWindow::id() const
72{
73 if (!_window)
74 return 0;
75 return SDL_GetWindowID(_window);
76}
77
78SDL_DisplayID SdlWindow::displayIndex() const
79{
80 if (!_window)
81 return 0;
82 return SDL_GetDisplayForWindow(_window);
83}
84
85SDL_Rect SdlWindow::rect() const
86{
87 return rect(_window);
88}
89
90SDL_Rect SdlWindow::bounds() const
91{
92 SDL_Rect rect = {};
93 if (_window)
94 {
95 if (!SDL_GetWindowPosition(_window, &rect.x, &rect.y))
96 return {};
97 if (!SDL_GetWindowSize(_window, &rect.w, &rect.h))
98 return {};
99 }
100 return rect;
101}
102
103SDL_Window* SdlWindow::window() const
104{
105 return _window;
106}
107
108Sint32 SdlWindow::offsetX() const
109{
110 return _offset_x;
111}
112
113void SdlWindow::setOffsetX(Sint32 x)
114{
115 _offset_x = x;
116}
117
118void SdlWindow::setOffsetY(Sint32 y)
119{
120 _offset_y = y;
121}
122
123Sint32 SdlWindow::offsetY() const
124{
125 return _offset_y;
126}
127
128rdpMonitor SdlWindow::monitor(bool isPrimary) const
129{
130 auto m = _monitor;
131 if (isPrimary)
132 {
133 m.x = 0;
134 m.y = 0;
135 }
136 return m;
137}
138
139void SdlWindow::setMonitor(rdpMonitor monitor)
140{
141 _monitor = monitor;
142}
143
144float SdlWindow::scale() const
145{
146 return SDL_GetWindowDisplayScale(_window);
147}
148
149SDL_DisplayOrientation SdlWindow::orientation() const
150{
151 const auto did = displayIndex();
152 return SDL_GetCurrentDisplayOrientation(did);
153}
154
155bool SdlWindow::grabKeyboard(bool enable)
156{
157 if (!_window)
158 return false;
159 SDL_SetWindowKeyboardGrab(_window, enable);
160 return true;
161}
162
163bool SdlWindow::grabMouse(bool enable)
164{
165 if (!_window)
166 return false;
167 SDL_SetWindowMouseGrab(_window, enable);
168 return true;
169}
170
171void SdlWindow::setBordered(bool bordered)
172{
173 if (_window)
174 SDL_SetWindowBordered(_window, bordered);
175 std::ignore = SDL_SyncWindow(_window);
176}
177
178void SdlWindow::raise()
179{
180 SDL_RaiseWindow(_window);
181 std::ignore = SDL_SyncWindow(_window);
182}
183
184void SdlWindow::resizeable(bool use)
185{
186 SDL_SetWindowResizable(_window, use);
187 std::ignore = SDL_SyncWindow(_window);
188}
189
190void SdlWindow::fullscreen(bool enter, bool forceOriginalDisplay)
191{
192 if (enter && forceOriginalDisplay && _displayID != 0)
193 {
194 /* Move the window to the desired display. We should not wait
195 * for the window to be moved, because some backends can refuse
196 * the move. The intent of moving the window is enough for SDL
197 * to decide which display will be used for fullscreen. */
198 SDL_Rect rect = {};
199 std::ignore = SDL_GetDisplayBounds(_displayID, &rect);
200 std::ignore = SDL_SetWindowPosition(_window, rect.x, rect.y);
201 }
202 std::ignore = SDL_SetWindowFullscreen(_window, enter);
203 std::ignore = SDL_SyncWindow(_window);
204}
205
206void SdlWindow::minimize()
207{
208 SDL_MinimizeWindow(_window);
209 std::ignore = SDL_SyncWindow(_window);
210}
211
212bool SdlWindow::resizeToPixelSize(const SDL_Point& size)
213{
214 auto sc = scale();
215 const int iscale = static_cast<int>(sc * 100.0f);
216 auto w = 100 * size.x / iscale;
217 auto h = 100 * size.y / iscale;
218 return resize({ w, h });
219}
220
221bool SdlWindow::resize(const SDL_Point& size)
222{
223 return SDL_SetWindowSize(_window, size.x, size.y);
224}
225
226bool SdlWindow::drawRect(SDL_Surface* surface, SDL_Point offset, const SDL_Rect& srcRect)
227{
228 WINPR_ASSERT(surface);
229 SDL_Rect dstRect = { offset.x + srcRect.x, offset.y + srcRect.y, srcRect.w, srcRect.h };
230 return blit(surface, srcRect, dstRect);
231}
232
233bool SdlWindow::drawRects(SDL_Surface* surface, SDL_Point offset,
234 const std::vector<SDL_Rect>& rects)
235{
236 if (rects.empty())
237 {
238 return drawRect(surface, offset, { 0, 0, surface->w, surface->h });
239 }
240 for (auto& srcRect : rects)
241 {
242 if (!drawRect(surface, offset, srcRect))
243 return false;
244 }
245 return true;
246}
247
248bool SdlWindow::drawScaledRect(SDL_Surface* surface, const SDL_FPoint& scale,
249 const SDL_Rect& srcRect)
250{
251 SDL_Rect dstRect = srcRect;
252 dstRect.x = static_cast<Sint32>(static_cast<float>(dstRect.x) * scale.x);
253 dstRect.w = static_cast<Sint32>(static_cast<float>(dstRect.w) * scale.x);
254 dstRect.y = static_cast<Sint32>(static_cast<float>(dstRect.y) * scale.y);
255 dstRect.h = static_cast<Sint32>(static_cast<float>(dstRect.h) * scale.y);
256 return blit(surface, srcRect, dstRect);
257}
258
259bool SdlWindow::drawScaledRects(SDL_Surface* surface, const SDL_FPoint& scale,
260 const std::vector<SDL_Rect>& rects)
261{
262 if (rects.empty())
263 {
264 return drawScaledRect(surface, scale, { 0, 0, surface->w, surface->h });
265 }
266 for (const auto& srcRect : rects)
267 {
268 if (!drawScaledRect(surface, scale, srcRect))
269 return false;
270 }
271 return true;
272}
273
274bool SdlWindow::fill(Uint8 r, Uint8 g, Uint8 b, Uint8 a)
275{
276 return fill(_window, r, g, b, a);
277}
278
279bool SdlWindow::fill(SDL_Window* window, Uint8 r, Uint8 g, Uint8 b, Uint8 a)
280{
281 auto surface = SDL_GetWindowSurface(window);
282 if (!surface)
283 return false;
284 SDL_Rect rect = { 0, 0, surface->w, surface->h };
285 auto color = SDL_MapSurfaceRGBA(surface, r, g, b, a);
286
287 return SDL_FillSurfaceRect(surface, &rect, color);
288}
289
290rdpMonitor SdlWindow::query(SDL_Window* window, SDL_DisplayID id, bool forceAsPrimary)
291{
292 if (!window)
293 return {};
294
295 const auto& r = rect(window, forceAsPrimary);
296 const float factor = SDL_GetWindowDisplayScale(window);
297 const float dpi = std::roundf(factor * 100.0f);
298
299 WINPR_ASSERT(r.w > 0);
300 WINPR_ASSERT(r.h > 0);
301
302 const auto primary = SDL_GetPrimaryDisplay();
303 const auto orientation = SDL_GetCurrentDisplayOrientation(id);
304 const auto rdp_orientation = sdl::utils::orientaion_to_rdp(orientation);
305
306 rdpMonitor monitor{};
307 monitor.orig_screen = id;
308 monitor.x = r.x;
309 monitor.y = r.y;
310 monitor.width = r.w;
311 monitor.height = r.h;
312 monitor.is_primary = forceAsPrimary || (id == primary);
313 monitor.attributes.desktopScaleFactor = static_cast<UINT32>(dpi);
314 monitor.attributes.deviceScaleFactor = 100;
315 monitor.attributes.orientation = rdp_orientation;
316 monitor.attributes.physicalWidth = WINPR_ASSERTING_INT_CAST(uint32_t, r.w);
317 monitor.attributes.physicalHeight = WINPR_ASSERTING_INT_CAST(uint32_t, r.h);
318
319 const auto cat = SDL_LOG_CATEGORY_APPLICATION;
320 SDL_LogDebug(cat, "monitor.orig_screen %" PRIu32, monitor.orig_screen);
321 SDL_LogDebug(cat, "monitor.x %" PRId32, monitor.x);
322 SDL_LogDebug(cat, "monitor.y %" PRId32, monitor.y);
323 SDL_LogDebug(cat, "monitor.width %" PRId32, monitor.width);
324 SDL_LogDebug(cat, "monitor.height %" PRId32, monitor.height);
325 SDL_LogDebug(cat, "monitor.is_primary %" PRIu32, monitor.is_primary);
326 SDL_LogDebug(cat, "monitor.attributes.desktopScaleFactor %" PRIu32,
327 monitor.attributes.desktopScaleFactor);
328 SDL_LogDebug(cat, "monitor.attributes.deviceScaleFactor %" PRIu32,
329 monitor.attributes.deviceScaleFactor);
330 SDL_LogDebug(cat, "monitor.attributes.orientation %s",
331 freerdp_desktop_rotation_flags_to_string(monitor.attributes.orientation));
332 SDL_LogDebug(cat, "monitor.attributes.physicalWidth %" PRIu32,
333 monitor.attributes.physicalWidth);
334 SDL_LogDebug(cat, "monitor.attributes.physicalHeight %" PRIu32,
335 monitor.attributes.physicalHeight);
336 return monitor;
337}
338
339SDL_Rect SdlWindow::rect(SDL_Window* window, bool forceAsPrimary)
340{
341 SDL_Rect rect = {};
342 if (!window)
343 return {};
344
345 if (!forceAsPrimary)
346 {
347 if (!SDL_GetWindowPosition(window, &rect.x, &rect.y))
348 return {};
349 }
350
351 if (!SDL_GetWindowSizeInPixels(window, &rect.w, &rect.h))
352 return {};
353
354 const auto flags = SDL_GetWindowFlags(window);
355 const auto mask = SDL_WINDOW_FULLSCREEN;
356 const auto fs = (flags & mask) == mask;
357 if (tryFallback(fs))
358 {
359 /* On wlroots compositors (Sway, river, etc.), windows that are hidden/unmapped
360 * don't get their actual display dimensions. The dummy window returns its creation size
361 * (64x64) instead of the display size. This causes validation errors since we require >=
362 * 200px. Workaround: If we got dimensions that are too small, query the display directly.
363 */
364
365 const auto displayID = SDL_GetDisplayForWindow(window);
366 SDL_Rect displayBounds = {};
367 if (SDL_GetDisplayBounds(displayID, &displayBounds))
368 {
369 if (forceAsPrimary)
370 {
371 rect.x = 0;
372 rect.y = 0;
373 }
374 rect.w = displayBounds.w;
375 rect.h = displayBounds.h;
376
377 const float contentScale = SDL_GetDisplayContentScale(displayID);
378 if (contentScale > 1.0f)
379 {
380 const auto fw = static_cast<float>(rect.w);
381 const auto fh = static_cast<float>(rect.h);
382 rect.w = static_cast<int>(std::roundf(fw * contentScale));
383 rect.h = static_cast<int>(std::roundf(fh * contentScale));
384 }
385 }
386 }
387
388 return rect;
389}
390
391SdlWindow::HighDPIMode SdlWindow::isHighDPIWindowsMode(SDL_Window* window)
392{
393 if (!window)
394 return MODE_INVALID;
395
396 const auto id = SDL_GetDisplayForWindow(window);
397 if (id == 0)
398 return MODE_INVALID;
399
400 const auto cs = SDL_GetDisplayContentScale(id);
401 const auto ds = SDL_GetWindowDisplayScale(window);
402 const auto pd = SDL_GetWindowPixelDensity(window);
403
404 /* mac os x style, but no HighDPI display */
405 if ((cs == 1.0f) && (ds == 1.0f) && (pd == 1.0f))
406 return MODE_NONE;
407
408 /* mac os x style HighDPI */
409 if ((cs == 1.0f) && (ds > 1.0f) && (pd > 1.0f))
410 return MODE_MACOS;
411
412 /* rest is windows style */
413 return MODE_WINDOWS;
414}
415
416bool SdlWindow::blit(SDL_Surface* surface, const SDL_Rect& srcRect, SDL_Rect& dstRect)
417{
418 auto screen = SDL_GetWindowSurface(_window);
419 if (!screen || !surface)
420 return false;
421 if (!SDL_SetSurfaceClipRect(surface, &srcRect))
422 return true;
423 if (!SDL_SetSurfaceClipRect(screen, &dstRect))
424 return true;
425 if (!SDL_BlitSurfaceScaled(surface, &srcRect, screen, &dstRect, SDL_SCALEMODE_LINEAR))
426 {
427 SDL_LogError(SDL_LOG_CATEGORY_RENDER, "SDL_BlitScaled: %s", SDL_GetError());
428 return false;
429 }
430 return true;
431}
432
433void SdlWindow::updateSurface()
434{
435 SDL_UpdateWindowSurface(_window);
436}
437
438SdlWindow SdlWindow::create(SDL_DisplayID id, const std::string& title, Uint32 flags, Uint32 width,
439 Uint32 height)
440{
441 flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
442
443 SDL_Rect rect = { static_cast<int>(SDL_WINDOWPOS_CENTERED_DISPLAY(id)),
444 static_cast<int>(SDL_WINDOWPOS_CENTERED_DISPLAY(id)), static_cast<int>(width),
445 static_cast<int>(height) };
446
447 if ((flags & SDL_WINDOW_FULLSCREEN) != 0)
448 {
449 std::ignore = SDL_GetDisplayBounds(id, &rect);
450 }
451
452 SdlWindow window{ id, title, rect, flags };
453
454 if ((flags & (SDL_WINDOW_FULLSCREEN)) != 0)
455 {
456 window.setOffsetX(rect.x);
457 window.setOffsetY(rect.y);
458 }
459
460 return window;
461}
462
463static SDL_Window* createDummy(SDL_DisplayID id)
464{
465 const auto x = SDL_WINDOWPOS_CENTERED_DISPLAY(id);
466 const auto y = SDL_WINDOWPOS_CENTERED_DISPLAY(id);
467 const int w = 64;
468 const int h = 64;
469
470 auto props = SDL_CreateProperties();
471 std::stringstream ss;
472 ss << "SdlWindow::query(" << id << ")";
473 SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, ss.str().c_str());
474 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, x);
475 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, y);
476 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, w);
477 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, h);
478
479 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
480 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, false);
481 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN, true);
482 SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIDDEN_BOOLEAN, false);
483
484 auto window = SDL_CreateWindowWithProperties(props);
485 SDL_DestroyProperties(props);
486
487 /* Workaround: we need to properly position the window on the correct monitor
488 * before going fullscreen. Otherwise we will get the primary monitor details.
489 */
490 if (window)
491 {
492 SDL_Rect rect = {};
493 std::ignore = SDL_GetDisplayBounds(id, &rect);
494 std::ignore = SDL_SetWindowPosition(window, rect.x, rect.y);
495 std::ignore = SDL_SetWindowFullscreen(window, true);
496 }
497 return window;
498}
499
500rdpMonitor SdlWindow::query(SDL_DisplayID id, bool forceAsPrimary)
501{
502 std::unique_ptr<SDL_Window, void (*)(SDL_Window*)> window(createDummy(id), SDL_DestroyWindow);
503 if (!window)
504 return {};
505
506 std::unique_ptr<SDL_Renderer, void (*)(SDL_Renderer*)> renderer(
507 SDL_CreateRenderer(window.get(), nullptr), SDL_DestroyRenderer);
508
509 if (!SDL_SyncWindow(window.get()))
510 return {};
511
512 SDL_Event event{};
513 while (SDL_PollEvent(&event))
514 ;
515
516 return query(window.get(), id, forceAsPrimary);
517}
518
519SDL_Rect SdlWindow::rect(SDL_DisplayID id, bool forceAsPrimary)
520{
521 std::unique_ptr<SDL_Window, void (*)(SDL_Window*)> window(createDummy(id), SDL_DestroyWindow);
522 if (!window)
523 return {};
524
525 std::unique_ptr<SDL_Renderer, void (*)(SDL_Renderer*)> renderer(
526 SDL_CreateRenderer(window.get(), nullptr), SDL_DestroyRenderer);
527
528 if (!SDL_SyncWindow(window.get()))
529 return {};
530
531 SDL_Event event{};
532 while (SDL_PollEvent(&event))
533 ;
534
535 return rect(window.get(), forceAsPrimary);
536}
537
538bool SdlWindow::tryFallback(bool isFullscreen)
539{
540 /* If we define a custom env variable to use the wlroots hack
541 * then enable/disable according to this setting only.
542 */
543 const auto wlroots_hack = SDL_getenv("FREERDP_WLROOTS_HACK");
544 if (wlroots_hack != nullptr)
545 {
546 const auto enabled = strcmp(wlroots_hack, "0") != 0;
547 if (strcmp(wlroots_hack, "force") == 0)
548 isFullscreen = true;
549 return enabled && isFullscreen;
550 }
551
552 const auto platform = SDL_GetPlatform();
553 if ((platform == nullptr) || (strcmp(platform, "Linux") != 0))
554 return false;
555
556 const auto driver = SDL_GetCurrentVideoDriver();
557 if ((driver == nullptr) || (strcmp(driver, "wayland") != 0))
558 return false;
559
560 /* check XDG_SESSION_DESKTOP
561 *
562 * if set and the value is
563 * - sway
564 *
565 * then we need the hack.
566 */
567 const auto xdg_session = SDL_getenv("XDG_SESSION_DESKTOP");
568 if (xdg_session != nullptr)
569 {
570 if (strcmp(xdg_session, "sway") == 0)
571 return isFullscreen;
572 }
573
574 /* check XDG_CURRENT_DESKTOP
575 *
576 * if set and the value is
577 * - sway:wlroots
578 *
579 * then we need the hack.
580 */
581 const auto xdg_desktop = SDL_getenv("XDG_CURRENT_DESKTOP");
582 if (xdg_desktop != nullptr)
583 {
584 if (strcmp(xdg_desktop, "sway:wlroots") == 0)
585 return isFullscreen;
586 }
587
588 return false;
589}
SdlWindow(const std::string &title, Sint32 startupX, Sint32 startupY, Sint32 width, Sint32 height, Uint32 flags)