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