FreeRDP
Loading...
Searching...
No Matches
SDL3/sdl_disp.cpp
1
20#include <vector>
21#include <winpr/sysinfo.h>
22#include <winpr/assert.h>
23
24#include <freerdp/gdi/gdi.h>
25
26#include <SDL3/SDL.h>
27
28#include "sdl_disp.hpp"
29#include "sdl_input.hpp"
30#include "sdl_context.hpp"
31
32#include <freerdp/log.h>
33#define TAG CLIENT_TAG("sdl.disp")
34
35static constexpr UINT64 RESIZE_MIN_DELAY = 500; /* minimum delay in ms between two resizes */
36static constexpr unsigned MAX_RETRIES = 5;
37
38static auto operator==(const DISPLAY_CONTROL_MONITOR_LAYOUT& a,
40{
41 if (a.Flags != b.Flags)
42 return false;
43 if (a.Left != b.Left)
44 return false;
45 if (a.Top != b.Top)
46 return false;
47 if (a.Width != b.Width)
48 return false;
49 if (a.Height != b.Height)
50 return false;
51 if (a.PhysicalWidth != b.PhysicalWidth)
52 return false;
53 if (a.PhysicalHeight != b.PhysicalHeight)
54 return false;
55 if (a.Orientation != b.Orientation)
56 return false;
57 if (a.DesktopScaleFactor != b.DesktopScaleFactor)
58 return false;
59 if (a.DeviceScaleFactor != b.DeviceScaleFactor)
60 return false;
61 return true;
62}
63
64bool sdlDispContext::settings_changed(const std::vector<DISPLAY_CONTROL_MONITOR_LAYOUT>& layout)
65{
66 return (layout != _last_sent_layout);
67}
68
69bool sdlDispContext::sendResize()
70{
71 auto settings = _sdl->context()->settings;
72
73 if (!settings)
74 return false;
75
76 if (!_activated || !_disp)
77 return true;
78
79 if (GetTickCount64() - _lastSentDate < RESIZE_MIN_DELAY)
80 return true;
81
82 _lastSentDate = GetTickCount64();
83
84 const UINT32 mcount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
85 auto monitors = static_cast<const rdpMonitor*>(
86 freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray));
87 return sendLayout(monitors, mcount);
88}
89
90bool sdlDispContext::setWindowResizeable()
91{
92 return _sdl->setResizeable(true);
93}
94
95static bool sdl_disp_check_context(void* context, SdlContext** ppsdl, sdlDispContext** ppsdlDisp,
96 rdpSettings** ppSettings)
97{
98 if (!context)
99 return false;
100
101 auto sdl = get_context(context);
102 if (!sdl)
103 return false;
104
105 if (!sdl->context()->settings)
106 return false;
107
108 *ppsdl = sdl;
109 *ppsdlDisp = &sdl->getDisplayChannelContext();
110 *ppSettings = sdl->context()->settings;
111 return true;
112}
113
114void sdlDispContext::OnActivated(void* context, const ActivatedEventArgs* e)
115{
116 SdlContext* sdl = nullptr;
117 sdlDispContext* sdlDisp = nullptr;
118 rdpSettings* settings = nullptr;
119
120 if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings))
121 return;
122
123 sdlDisp->_waitingResize = false;
124
125 if (!sdlDisp->_activated)
126 return;
127
128 /* The desktop size agreed on at connect time is capped by the protocol (4096x2048,
129 * MS-RDPBCGR 2.2.1.3.2), so a larger window starts out with a smaller session. Only the
130 * display control channel can raise it, and in fullscreen nothing else triggers a layout:
131 * the window is never resized by hand. */
132 if (!freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
133 {
134 if (!sdlDisp->setWindowResizeable())
135 return;
136
137 /* The connect only carries a single monitor, so a canvas the page splits into several
138 * (sdl_monitors_emscripten.js) needs a layout at once: its size may never change. Ask
139 * the page again, a reconnect starts from the settings of the command line. */
140#if defined(__EMSCRIPTEN__)
141 if (!sdl->updateWindowList())
142 return;
143 const bool split = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) > 1;
144#else
145 const bool split = false;
146#endif
147 if (e->firstActivation && !split)
148 return;
149 }
150
151 std::ignore = sdlDisp->addTimer();
152}
153
154void sdlDispContext::OnGraphicsReset(void* context, const GraphicsResetEventArgs* e)
155{
156 SdlContext* sdl = nullptr;
157 sdlDispContext* sdlDisp = nullptr;
158 rdpSettings* settings = nullptr;
159
160 WINPR_UNUSED(e);
161 if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings))
162 return;
163
164 sdlDisp->_waitingResize = false;
165
166 if (!sdlDisp->_activated)
167 return;
168
169 if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
170 std::ignore = sdlDisp->addTimer();
171 else if (sdlDisp->setWindowResizeable())
172 std::ignore = sdlDisp->addTimer();
173}
174
175Uint32 sdlDispContext::OnTimer(void* param, [[maybe_unused]] SDL_TimerID timerID, Uint32 interval)
176{
177 auto ctx = static_cast<sdlDispContext*>(param);
178 if (!ctx)
179 return 0;
180
181 SdlContext* sdl = ctx->_sdl;
182 if (!sdl)
183 return 0;
184
185 sdlDispContext* sdlDisp = nullptr;
186 rdpSettings* settings = nullptr;
187
188 if (!sdl_disp_check_context(sdl->context(), &sdl, &sdlDisp, &settings))
189 return 0;
190
191 WLog_Print(sdl->getWLog(), WLOG_TRACE, "checking for display changes...");
192
193 if (!sdlDisp->sendResize())
194 WLog_Print(sdl->getWLog(), WLOG_TRACE, "failed to send the display layout");
195
196 if (sdlDisp->_timer_retries++ >= MAX_RETRIES)
197 {
198 WLog_Print(sdl->getWLog(), WLOG_TRACE, "deactivate timer, retries exceeded");
199 return 0;
200 }
201
202 WLog_Print(sdl->getWLog(), WLOG_TRACE, "fire timer one more time");
203 return interval;
204}
205
206bool sdlDispContext::sendLayout(const rdpMonitor* monitors, size_t nmonitors)
207{
208 WINPR_ASSERT(monitors);
209 WINPR_ASSERT(nmonitors > 0);
210
211 auto settings = _sdl->context()->settings;
212 WINPR_ASSERT(settings);
213
214 std::vector<DISPLAY_CONTROL_MONITOR_LAYOUT> layouts;
215 layouts.reserve(nmonitors);
216
217 for (size_t i = 0; i < nmonitors; i++)
218 {
219 auto monitor = &monitors[i];
221
222 layout.Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0);
223 layout.Left = monitor->x;
224 layout.Top = monitor->y;
225 layout.Width = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->width);
226 layout.Height = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->height);
227 layout.Orientation = ORIENTATION_LANDSCAPE;
228 layout.PhysicalWidth = monitor->attributes.physicalWidth;
229 layout.PhysicalHeight = monitor->attributes.physicalHeight;
230
231 switch (monitor->attributes.orientation)
232 {
233 case ORIENTATION_PORTRAIT:
234 layout.Orientation = ORIENTATION_PORTRAIT;
235 break;
236
237 case ORIENTATION_LANDSCAPE_FLIPPED:
238 layout.Orientation = ORIENTATION_LANDSCAPE_FLIPPED;
239 break;
240
241 case ORIENTATION_PORTRAIT_FLIPPED:
242 layout.Orientation = ORIENTATION_PORTRAIT_FLIPPED;
243 break;
244
245 case ORIENTATION_LANDSCAPE:
246 default:
247 /* MS-RDPEDISP - 2.2.2.2.1:
248 * Orientation (4 bytes): A 32-bit unsigned integer that specifies the
249 * orientation of the monitor in degrees. Valid values are 0, 90, 180
250 * or 270
251 *
252 * So we default to ORIENTATION_LANDSCAPE
253 */
254 layout.Orientation = ORIENTATION_LANDSCAPE;
255 break;
256 }
257
258 layout.DesktopScaleFactor = monitor->attributes.desktopScaleFactor;
259 layout.DeviceScaleFactor = monitor->attributes.deviceScaleFactor;
260
261 auto mask = freerdp_settings_get_uint64(settings, FreeRDP_MonitorOverrideFlags);
262 if ((mask & FREERDP_MONITOR_OVERRIDE_ORIENTATION) != 0)
263 layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
264 if ((mask & FREERDP_MONITOR_OVERRIDE_DESKTOP_SCALE) != 0)
265 layout.DesktopScaleFactor =
266 freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
267 if ((mask & FREERDP_MONITOR_OVERRIDE_DEVICE_SCALE) != 0)
268 layout.DeviceScaleFactor =
269 freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
270 layouts.emplace_back(layout);
271 }
272
273 if (!settings_changed(layouts))
274 return true;
275
276 WINPR_ASSERT(_disp);
277 const size_t len = layouts.size();
278 WINPR_ASSERT(len <= UINT32_MAX);
279 const auto ret = IFCALLRESULT(CHANNEL_RC_OK, _disp->SendMonitorLayout, _disp,
280 static_cast<UINT32>(len), layouts.data());
281 if (ret != CHANNEL_RC_OK)
282 return false;
283 _last_sent_layout = std::move(layouts);
284 return true;
285}
286
287bool sdlDispContext::addTimer()
288{
289 if (SDL_WasInit(SDL_INIT_EVENTS) == 0)
290 return false;
291
292 SDL_RemoveTimer(_timer);
293 WLog_Print(_sdl->getWLog(), WLOG_TRACE, "adding new display check timer");
294
295 _timer_retries = 0;
296 if (!sendResize())
297 return false;
298 _timer = SDL_AddTimer(1000, sdlDispContext::OnTimer, this);
299 return true;
300}
301
302bool sdlDispContext::updateMonitor(SDL_WindowID id)
303{
304 if (!freerdp_settings_get_bool(_sdl->context()->settings, FreeRDP_DynamicResolutionUpdate))
305 return true;
306
307 if (!_sdl->updateWindow(id))
308 return false;
309
310 if (!_sdl->updateWindowList())
311 return false;
312
313 return addTimer();
314}
315
316bool sdlDispContext::updateMonitors(SDL_EventType type, SDL_DisplayID displayID)
317{
318 auto settings = _sdl->context()->settings;
319 if (!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
320 return true;
321
322 if (!freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
323 return true;
324
325 switch (type)
326 {
327 case SDL_EVENT_DISPLAY_ADDED:
328 if (!_sdl->addDisplayWindow(displayID))
329 return false;
330 break;
331 case SDL_EVENT_DISPLAY_REMOVED:
332 if (!_sdl->removeDisplayWindow(displayID))
333 return false;
334 break;
335 default:
336 break;
337 }
338
339 if (!_sdl->updateWindowList())
340 return false;
341 return addTimer();
342}
343
344bool sdlDispContext::handleEvent(const SDL_DisplayEvent& ev)
345{
346 const auto cat = SDL_LOG_CATEGORY_APPLICATION;
347 switch (ev.type)
348 {
349 case SDL_EVENT_DISPLAY_ADDED:
350 SDL_LogDebug(cat, "A new display with id %u was connected", ev.displayID);
351 return updateMonitors(ev.type, ev.displayID);
352 case SDL_EVENT_DISPLAY_REMOVED:
353 SDL_LogDebug(cat, "The display with id %u was disconnected", ev.displayID);
354 return updateMonitors(ev.type, ev.displayID);
355 case SDL_EVENT_DISPLAY_ORIENTATION:
356 SDL_LogDebug(cat, "The orientation of display with id %u was changed", ev.displayID);
357 return updateMonitors(ev.type, ev.displayID);
358 case SDL_EVENT_DISPLAY_MOVED:
359 SDL_LogDebug(cat, "The display with id %u was moved", ev.displayID);
360 return updateMonitors(ev.type, ev.displayID);
361 case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
362 SDL_LogDebug(cat, "The display with id %u changed scale", ev.displayID);
363 return updateMonitors(ev.type, ev.displayID);
364#if SDL_VERSION_ATLEAST(3, 3, 2)
365 case SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED:
366 SDL_LogDebug(cat, "The display with id %u changed usable bounds", ev.displayID);
367 return updateMonitors(ev.type, ev.displayID);
368#endif
369 case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED:
370 SDL_LogDebug(cat, "The display with id %u changed mode", ev.displayID);
371 return updateMonitors(ev.type, ev.displayID);
372 case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
373 SDL_LogDebug(cat, "The display with id %u changed desktop mode", ev.displayID);
374 return updateMonitors(ev.type, ev.displayID);
375 default:
376 return true;
377 }
378}
379
380bool sdlDispContext::handleEvent(const SDL_WindowEvent& ev)
381{
382 auto window = _sdl->getWindowForId(ev.windowID);
383 if (!window)
384 return true;
385
386 auto bordered = freerdp_settings_get_bool(_sdl->context()->settings, FreeRDP_Decorations);
387 window->setBordered(bordered);
388
389 switch (ev.type)
390 {
391 case SDL_EVENT_WINDOW_HIDDEN:
392 case SDL_EVENT_WINDOW_MINIMIZED:
393 /* Other monitors may still show the session. */
394 if (!_sdl->allWindowsHidden())
395 return true;
396 return _sdl->redraw(true);
397 case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
398 return updateMonitor(ev.windowID);
399 case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
400 return updateMonitor(ev.windowID);
401
402 case SDL_EVENT_WINDOW_EXPOSED:
403 case SDL_EVENT_WINDOW_SHOWN:
404 case SDL_EVENT_WINDOW_MAXIMIZED:
405 case SDL_EVENT_WINDOW_RESTORED:
406 if (!_sdl->redraw())
407 return false;
408
409 /* fallthrough */
410 WINPR_FALLTHROUGH
411 case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
412 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
413 case SDL_EVENT_WINDOW_RESIZED:
414 return updateMonitor(ev.windowID);
415 case SDL_EVENT_WINDOW_MOUSE_LEAVE:
416 WINPR_ASSERT(_sdl);
417 return _sdl->getInputChannelContext().keyboard_grab(ev.windowID, false);
418 case SDL_EVENT_WINDOW_MOUSE_ENTER:
419 WINPR_ASSERT(_sdl);
420 if (!_sdl->getInputChannelContext().keyboard_grab(ev.windowID, true))
421 return false;
422 return _sdl->getInputChannelContext().keyboard_focus_in();
423 case SDL_EVENT_WINDOW_FOCUS_GAINED:
424 return _sdl->getInputChannelContext().keyboard_focus_in();
425
426 default:
427 return true;
428 }
429}
430
431UINT sdlDispContext::DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors,
432 UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB)
433{
434 /* we're called only if dynamic resolution update is activated */
435 WINPR_ASSERT(disp);
436
437 auto sdlDisp = reinterpret_cast<sdlDispContext*>(disp->custom);
438 return sdlDisp->DisplayControlCaps(maxNumMonitors, maxMonitorAreaFactorA,
439 maxMonitorAreaFactorB);
440}
441
442UINT sdlDispContext::DisplayControlCaps(UINT32 maxNumMonitors, UINT32 maxMonitorAreaFactorA,
443 UINT32 maxMonitorAreaFactorB)
444{
445 auto settings = _sdl->context()->settings;
446 WINPR_ASSERT(settings);
447
448 WLog_DBG(TAG,
449 "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32
450 " MaxMonitorAreaFactorB: %" PRIu32 "",
451 maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB);
452 _activated = true;
453
454 /* A new display channel means a new server instance, e.g. after a server redirection (GNOME
455 * Remote Desktop hands the connection over from the login screen to the user session). It
456 * does not know the layout sent to the previous instance, and the window size did not change,
457 * so settings_changed() would suppress it: forget it and send the current layout again. */
458 _last_sent_layout.clear();
459
460 /* Windows ignores a layout sent right after the caps. Count the caps as a send, so the timer
461 * sends the layout once RESIZE_MIN_DELAY has passed instead of addTimer() sending it now. */
462 _lastSentDate = GetTickCount64();
463
464 if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
465 {
466 /* Window events have long passed by the time the channel comes up, and a fullscreen
467 * window produces no further ones, so let the timer send the layout. */
468 std::ignore = addTimer();
469 return CHANNEL_RC_OK;
470 }
471
472 WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable");
473 if (!setWindowResizeable())
474 return CHANNEL_RC_NO_MEMORY;
475 std::ignore = addTimer();
476 return CHANNEL_RC_OK;
477}
478
479bool sdlDispContext::init(DispClientContext* disp)
480{
481 if (!disp)
482 return false;
483
484 auto settings = _sdl->context()->settings;
485
486 if (!settings)
487 return false;
488
489 _disp = disp;
490 disp->custom = this;
491
492 if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
493 {
494 disp->DisplayControlCaps = sdlDispContext::DisplayControlCaps;
495 }
496
497 return _sdl->setResizeable(true);
498}
499
500bool sdlDispContext::uninit(DispClientContext* disp)
501{
502 if (!disp)
503 return false;
504
505 _disp = nullptr;
506 return _sdl->setResizeable(false);
507}
508
509sdlDispContext::sdlDispContext(SdlContext* sdl) : _sdl(sdl)
510{
511 WINPR_ASSERT(_sdl);
512 WINPR_ASSERT(_sdl->context()->settings);
513 WINPR_ASSERT(_sdl->context()->pubSub);
514
515 auto pubSub = _sdl->context()->pubSub;
516
517 if (PubSub_SubscribeActivated(pubSub, sdlDispContext::OnActivated) < 0)
518 throw std::exception();
519 if (PubSub_SubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset) < 0)
520 throw std::exception();
521 std::ignore = addTimer();
522}
523
524sdlDispContext::~sdlDispContext()
525{
526 wPubSub* pubSub = _sdl->context()->pubSub;
527 WINPR_ASSERT(pubSub);
528
529 PubSub_UnsubscribeActivated(pubSub, sdlDispContext::OnActivated);
530 PubSub_UnsubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset);
531 SDL_RemoveTimer(_timer);
532}
WINPR_ATTR_NODISCARD FREERDP_API const void * freerdp_settings_get_pointer(const rdpSettings *settings, FreeRDP_Settings_Keys_Pointer id)
Returns a immutable pointer settings value.
WINPR_ATTR_NODISCARD FREERDP_API UINT16 freerdp_settings_get_uint16(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt16 id)
Returns a UINT16 settings value.
WINPR_ATTR_NODISCARD FREERDP_API UINT64 freerdp_settings_get_uint64(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt64 id)
Returns a UINT64 settings value.
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.