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_kbd.hpp"
30#include "sdl_utils.hpp"
31#include "sdl_freerdp.hpp"
32
33#include <freerdp/log.h>
34#define TAG CLIENT_TAG("sdl.disp")
35
36static constexpr UINT64 RESIZE_MIN_DELAY = 200; /* minimum delay in ms between two resizes */
37static constexpr unsigned MAX_RETRIES = 5;
38
39static auto operator==(const DISPLAY_CONTROL_MONITOR_LAYOUT& a,
41{
42 if (a.Flags != b.Flags)
43 return false;
44 if (a.Left != b.Left)
45 return false;
46 if (a.Top != b.Top)
47 return false;
48 if (a.Width != b.Width)
49 return false;
50 if (a.Height != b.Height)
51 return false;
52 if (a.PhysicalWidth != b.PhysicalWidth)
53 return false;
54 if (a.PhysicalHeight != b.PhysicalHeight)
55 return false;
56 if (a.Orientation != b.Orientation)
57 return false;
58 if (a.DesktopScaleFactor != b.DesktopScaleFactor)
59 return false;
60 if (a.DeviceScaleFactor != b.DeviceScaleFactor)
61 return false;
62 return true;
63}
64
65bool sdlDispContext::settings_changed(const std::vector<DISPLAY_CONTROL_MONITOR_LAYOUT>& layout)
66{
67 return (layout != _last_sent_layout);
68}
69
70bool sdlDispContext::sendResize()
71{
72 auto settings = _sdl->context()->settings;
73
74 if (!settings)
75 return false;
76
77 if (!_activated || !_disp)
78 return true;
79
80 if (GetTickCount64() - _lastSentDate < RESIZE_MIN_DELAY)
81 return true;
82
83 _lastSentDate = GetTickCount64();
84
85 const UINT32 mcount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
86 auto monitors = static_cast<const rdpMonitor*>(
87 freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray));
88 return sendLayout(monitors, mcount) != CHANNEL_RC_OK;
89}
90
91bool sdlDispContext::set_window_resizable()
92{
93 return _sdl->update_resizeable(true);
94}
95
96static bool sdl_disp_check_context(void* context, SdlContext** ppsdl, sdlDispContext** ppsdlDisp,
97 rdpSettings** ppSettings)
98{
99 if (!context)
100 return false;
101
102 auto sdl = get_context(context);
103 if (!sdl)
104 return false;
105
106 if (!sdl->context()->settings)
107 return false;
108
109 *ppsdl = sdl;
110 *ppsdlDisp = &sdl->disp;
111 *ppSettings = sdl->context()->settings;
112 return true;
113}
114
115void sdlDispContext::OnActivated(void* context, const ActivatedEventArgs* e)
116{
117 SdlContext* sdl = nullptr;
118 sdlDispContext* sdlDisp = nullptr;
119 rdpSettings* settings = nullptr;
120
121 if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings))
122 return;
123
124 sdlDisp->_waitingResize = false;
125
126 if (sdlDisp->_activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
127 {
128 sdlDisp->set_window_resizable();
129
130 if (e->firstActivation)
131 return;
132
133 sdlDisp->addTimer();
134 }
135}
136
137void sdlDispContext::OnGraphicsReset(void* context, const GraphicsResetEventArgs* e)
138{
139 SdlContext* sdl = nullptr;
140 sdlDispContext* sdlDisp = nullptr;
141 rdpSettings* settings = nullptr;
142
143 WINPR_UNUSED(e);
144 if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings))
145 return;
146
147 sdlDisp->_waitingResize = false;
148
149 if (sdlDisp->_activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
150 {
151 sdlDisp->set_window_resizable();
152 sdlDisp->addTimer();
153 }
154}
155
156Uint32 sdlDispContext::OnTimer(void* param, [[maybe_unused]] SDL_TimerID timerID, Uint32 interval)
157{
158 auto ctx = static_cast<sdlDispContext*>(param);
159 if (!ctx)
160 return 0;
161
162 SdlContext* sdl = ctx->_sdl;
163 if (!sdl)
164 return 0;
165
166 sdlDispContext* sdlDisp = nullptr;
167 rdpSettings* settings = nullptr;
168
169 if (!sdl_disp_check_context(sdl->context(), &sdl, &sdlDisp, &settings))
170 return 0;
171
172 WLog_Print(sdl->log, WLOG_TRACE, "checking for display changes...");
173 if (!sdlDisp->_activated || freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
174 return 0;
175
176 auto rc = sdlDisp->sendResize();
177 if (!rc)
178 WLog_Print(sdl->log, WLOG_TRACE, "sent new display layout, result %d", rc);
179
180 if (sdlDisp->_timer_retries++ >= MAX_RETRIES)
181 {
182 WLog_Print(sdl->log, WLOG_TRACE, "deactivate timer, retries exceeded");
183 return 0;
184 }
185
186 WLog_Print(sdl->log, WLOG_TRACE, "fire timer one more time");
187 return interval;
188}
189
190UINT sdlDispContext::sendLayout(const rdpMonitor* monitors, size_t nmonitors)
191{
192 UINT ret = CHANNEL_RC_OK;
193
194 WINPR_ASSERT(monitors);
195 WINPR_ASSERT(nmonitors > 0);
196
197 auto settings = _sdl->context()->settings;
198 WINPR_ASSERT(settings);
199
200 std::vector<DISPLAY_CONTROL_MONITOR_LAYOUT> layouts;
201 layouts.reserve(nmonitors);
202
203 for (size_t i = 0; i < nmonitors; i++)
204 {
205 auto monitor = &monitors[i];
207
208 layout.Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0);
209 layout.Left = monitor->x;
210 layout.Top = monitor->y;
211 layout.Width = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->width);
212 layout.Height = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->height);
213 layout.Orientation = ORIENTATION_LANDSCAPE;
214 layout.PhysicalWidth = monitor->attributes.physicalWidth;
215 layout.PhysicalHeight = monitor->attributes.physicalHeight;
216
217 switch (monitor->attributes.orientation)
218 {
219 case ORIENTATION_PORTRAIT:
220 layout.Orientation = ORIENTATION_PORTRAIT;
221 break;
222
223 case ORIENTATION_LANDSCAPE_FLIPPED:
224 layout.Orientation = ORIENTATION_LANDSCAPE_FLIPPED;
225 break;
226
227 case ORIENTATION_PORTRAIT_FLIPPED:
228 layout.Orientation = ORIENTATION_PORTRAIT_FLIPPED;
229 break;
230
231 case ORIENTATION_LANDSCAPE:
232 default:
233 /* MS-RDPEDISP - 2.2.2.2.1:
234 * Orientation (4 bytes): A 32-bit unsigned integer that specifies the
235 * orientation of the monitor in degrees. Valid values are 0, 90, 180
236 * or 270
237 *
238 * So we default to ORIENTATION_LANDSCAPE
239 */
240 layout.Orientation = ORIENTATION_LANDSCAPE;
241 break;
242 }
243
244 layout.DesktopScaleFactor = monitor->attributes.desktopScaleFactor;
245 layout.DeviceScaleFactor = monitor->attributes.deviceScaleFactor;
246
247 auto mask = freerdp_settings_get_uint64(settings, FreeRDP_MonitorOverrideFlags);
248 if ((mask & FREERDP_MONITOR_OVERRIDE_ORIENTATION) != 0)
249 layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
250 if ((mask & FREERDP_MONITOR_OVERRIDE_DESKTOP_SCALE) != 0)
251 layout.DesktopScaleFactor =
252 freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
253 if ((mask & FREERDP_MONITOR_OVERRIDE_DEVICE_SCALE) != 0)
254 layout.DeviceScaleFactor =
255 freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
256 layouts.emplace_back(layout);
257 }
258
259 if (!settings_changed(layouts))
260 return true;
261
262 WINPR_ASSERT(_disp);
263 const size_t len = layouts.size();
264 WINPR_ASSERT(len <= UINT32_MAX);
265 ret = IFCALLRESULT(CHANNEL_RC_OK, _disp->SendMonitorLayout, _disp, static_cast<UINT32>(len),
266 layouts.data());
267 if (ret != CHANNEL_RC_OK)
268 return ret;
269 _last_sent_layout = layouts;
270 return ret;
271}
272
273bool sdlDispContext::addTimer()
274{
275 if (SDL_WasInit(SDL_INIT_EVENTS) == 0)
276 return false;
277
278 SDL_RemoveTimer(_timer);
279 WLog_Print(_sdl->log, WLOG_TRACE, "adding new display check timer");
280
281 _timer_retries = 0;
282 sendResize();
283 _timer = SDL_AddTimer(1000, sdlDispContext::OnTimer, this);
284 return true;
285}
286
287bool sdlDispContext::updateMonitor(SDL_WindowID id)
288{
289 auto settings = _sdl->context()->settings;
290 if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
291 return updateMonitors(SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED);
292
293 if (!freerdp_settings_get_bool(_sdl->context()->settings, FreeRDP_DynamicResolutionUpdate))
294 return true;
295
296 const auto& window = _sdl->windows.at(id);
297 auto monitor = window.monitor();
298
299 monitor.is_primary = TRUE;
300 if (!freerdp_settings_set_monitor_def_array_sorted(settings, &monitor, 1))
301 return false;
302
303 return addTimer();
304}
305
306bool sdlDispContext::updateMonitors(SDL_EventType type)
307{
308 switch (type)
309 {
310 case SDL_EVENT_DISPLAY_ADDED:
311 case SDL_EVENT_DISPLAY_REMOVED:
312 case SDL_EVENT_DISPLAY_MOVED:
313 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "TODO [%s] Not fully supported yet",
314 sdl_event_type_str(type));
315 break;
316 default:
317 break;
318 }
319
320 auto settings = _sdl->context()->settings;
321 if (!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
322 return true;
323
324 if (!freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
325 return true;
326
327 std::vector<rdpMonitor> monitors;
328 monitors.reserve(_sdl->windows.size());
329 for (auto& smon : _sdl->windows)
330 {
331 monitors.emplace_back(smon.second.monitor());
332 }
333
334 if (!freerdp_settings_set_monitor_def_array_sorted(settings, monitors.data(), monitors.size()))
335 return false;
336 return addTimer();
337}
338
339bool sdlDispContext::handle_display_event(const SDL_DisplayEvent* ev)
340{
341 WINPR_ASSERT(ev);
342
343 switch (ev->type)
344 {
345 case SDL_EVENT_DISPLAY_ADDED:
346 SDL_Log("A new display with id %u was connected", ev->displayID);
347 return updateMonitors(ev->type);
348 case SDL_EVENT_DISPLAY_REMOVED:
349 SDL_Log("The display with id %u was disconnected", ev->displayID);
350 return updateMonitors(ev->type);
351 case SDL_EVENT_DISPLAY_ORIENTATION:
352 SDL_Log("The orientation of display with id %u was changed", ev->displayID);
353 return updateMonitors(ev->type);
354 case SDL_EVENT_DISPLAY_MOVED:
355 SDL_Log("The display with id %u was moved", ev->displayID);
356 return updateMonitors(ev->type);
357 case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
358 SDL_Log("The display with id %u changed scale", ev->displayID);
359 return updateMonitors(ev->type);
360 case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED:
361 SDL_Log("The display with id %u changed mode", ev->displayID);
362 return updateMonitors(ev->type);
363 case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
364 SDL_Log("The display with id %u changed desktop mode", ev->displayID);
365 return updateMonitors(ev->type);
366 default:
367 return true;
368 }
369}
370
371bool sdlDispContext::handle_window_event(const SDL_WindowEvent* ev)
372{
373 WINPR_ASSERT(ev);
374
375 auto bordered = freerdp_settings_get_bool(_sdl->context()->settings, FreeRDP_Decorations);
376
377 auto it = _sdl->windows.find(ev->windowID);
378 if (it != _sdl->windows.end())
379 it->second.setBordered(bordered);
380
381 switch (ev->type)
382 {
383 case SDL_EVENT_WINDOW_HIDDEN:
384 case SDL_EVENT_WINDOW_MINIMIZED:
385 return _sdl->redraw(true);
386 case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
387 return updateMonitor(ev->windowID);
388 case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
389 return updateMonitor(ev->windowID);
390
391 case SDL_EVENT_WINDOW_EXPOSED:
392 case SDL_EVENT_WINDOW_SHOWN:
393 case SDL_EVENT_WINDOW_MAXIMIZED:
394 case SDL_EVENT_WINDOW_RESTORED:
395 if (!_sdl->redraw())
396 return false;
397
398 /* fallthrough */
399 WINPR_FALLTHROUGH
400 case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
401 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
402 case SDL_EVENT_WINDOW_RESIZED:
403 return updateMonitor(ev->windowID);
404 case SDL_EVENT_WINDOW_MOUSE_LEAVE:
405 WINPR_ASSERT(_sdl);
406 _sdl->input.keyboard_grab(ev->windowID, false);
407 return true;
408 case SDL_EVENT_WINDOW_MOUSE_ENTER:
409 WINPR_ASSERT(_sdl);
410 _sdl->input.keyboard_grab(ev->windowID, true);
411 return _sdl->input.keyboard_focus_in();
412 case SDL_EVENT_WINDOW_FOCUS_GAINED:
413 return _sdl->input.keyboard_focus_in();
414
415 default:
416 return true;
417 }
418}
419
420UINT sdlDispContext::DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors,
421 UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB)
422{
423 /* we're called only if dynamic resolution update is activated */
424 WINPR_ASSERT(disp);
425
426 auto sdlDisp = reinterpret_cast<sdlDispContext*>(disp->custom);
427 return sdlDisp->DisplayControlCaps(maxNumMonitors, maxMonitorAreaFactorA,
428 maxMonitorAreaFactorB);
429}
430
431UINT sdlDispContext::DisplayControlCaps(UINT32 maxNumMonitors, UINT32 maxMonitorAreaFactorA,
432 UINT32 maxMonitorAreaFactorB)
433{
434 auto settings = _sdl->context()->settings;
435 WINPR_ASSERT(settings);
436
437 WLog_DBG(TAG,
438 "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32
439 " MaxMonitorAreaFactorB: %" PRIu32 "",
440 maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB);
441 _activated = true;
442
443 if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
444 return CHANNEL_RC_OK;
445
446 WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable");
447 return set_window_resizable() ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY;
448}
449
450bool sdlDispContext::init(DispClientContext* disp)
451{
452 if (!disp)
453 return false;
454
455 auto settings = _sdl->context()->settings;
456
457 if (!settings)
458 return false;
459
460 _disp = disp;
461 disp->custom = this;
462
463 if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
464 {
465 disp->DisplayControlCaps = sdlDispContext::DisplayControlCaps;
466 }
467
468 return _sdl->update_resizeable(true);
469}
470
471bool sdlDispContext::uninit(DispClientContext* disp)
472{
473 if (!disp)
474 return false;
475
476 _disp = nullptr;
477 return _sdl->update_resizeable(false);
478}
479
480sdlDispContext::sdlDispContext(SdlContext* sdl) : _sdl(sdl)
481{
482 SDL_Init(SDL_INIT_EVENTS | SDL_INIT_VIDEO);
483
484 WINPR_ASSERT(_sdl);
485 WINPR_ASSERT(_sdl->context()->settings);
486 WINPR_ASSERT(_sdl->context()->pubSub);
487
488 auto pubSub = _sdl->context()->pubSub;
489
490 PubSub_SubscribeActivated(pubSub, sdlDispContext::OnActivated);
491 PubSub_SubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset);
492 addTimer();
493}
494
495sdlDispContext::~sdlDispContext()
496{
497 wPubSub* pubSub = _sdl->context()->pubSub;
498 WINPR_ASSERT(pubSub);
499
500 PubSub_UnsubscribeActivated(pubSub, sdlDispContext::OnActivated);
501 PubSub_UnsubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset);
502 SDL_RemoveTimer(_timer);
503 SDL_Quit();
504}
FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.
FREERDP_API UINT64 freerdp_settings_get_uint64(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt64 id)
Returns a UINT64 settings value.
FREERDP_API BOOL freerdp_settings_set_monitor_def_array_sorted(rdpSettings *settings, const rdpMonitor *monitors, size_t count)
Sort monitor array according to:
FREERDP_API UINT16 freerdp_settings_get_uint16(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt16 id)
Returns a UINT16 settings value.
FREERDP_API const void * freerdp_settings_get_pointer(const rdpSettings *settings, FreeRDP_Settings_Keys_Pointer id)
Returns a immutable pointer settings value.