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 = 200; /* 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 && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
126 {
127 if (!sdlDisp->setWindowResizeable())
128 return;
129
130 if (e->firstActivation)
131 return;
132
133 std::ignore = 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 if (sdlDisp->setWindowResizeable())
152 std::ignore = 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->getWLog(), WLOG_TRACE, "checking for display changes...");
173
174 auto rc = sdlDisp->sendResize();
175 if (!rc)
176 WLog_Print(sdl->getWLog(), WLOG_TRACE, "sent new display layout, result %d", rc);
177
178 if (sdlDisp->_timer_retries++ >= MAX_RETRIES)
179 {
180 WLog_Print(sdl->getWLog(), WLOG_TRACE, "deactivate timer, retries exceeded");
181 return 0;
182 }
183
184 WLog_Print(sdl->getWLog(), WLOG_TRACE, "fire timer one more time");
185 return interval;
186}
187
188bool sdlDispContext::sendLayout(const rdpMonitor* monitors, size_t nmonitors)
189{
190 WINPR_ASSERT(monitors);
191 WINPR_ASSERT(nmonitors > 0);
192
193 auto settings = _sdl->context()->settings;
194 WINPR_ASSERT(settings);
195
196 std::vector<DISPLAY_CONTROL_MONITOR_LAYOUT> layouts;
197 layouts.reserve(nmonitors);
198
199 for (size_t i = 0; i < nmonitors; i++)
200 {
201 auto monitor = &monitors[i];
203
204 layout.Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0);
205 layout.Left = monitor->x;
206 layout.Top = monitor->y;
207 layout.Width = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->width);
208 layout.Height = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->height);
209 layout.Orientation = ORIENTATION_LANDSCAPE;
210 layout.PhysicalWidth = monitor->attributes.physicalWidth;
211 layout.PhysicalHeight = monitor->attributes.physicalHeight;
212
213 switch (monitor->attributes.orientation)
214 {
215 case ORIENTATION_PORTRAIT:
216 layout.Orientation = ORIENTATION_PORTRAIT;
217 break;
218
219 case ORIENTATION_LANDSCAPE_FLIPPED:
220 layout.Orientation = ORIENTATION_LANDSCAPE_FLIPPED;
221 break;
222
223 case ORIENTATION_PORTRAIT_FLIPPED:
224 layout.Orientation = ORIENTATION_PORTRAIT_FLIPPED;
225 break;
226
227 case ORIENTATION_LANDSCAPE:
228 default:
229 /* MS-RDPEDISP - 2.2.2.2.1:
230 * Orientation (4 bytes): A 32-bit unsigned integer that specifies the
231 * orientation of the monitor in degrees. Valid values are 0, 90, 180
232 * or 270
233 *
234 * So we default to ORIENTATION_LANDSCAPE
235 */
236 layout.Orientation = ORIENTATION_LANDSCAPE;
237 break;
238 }
239
240 layout.DesktopScaleFactor = monitor->attributes.desktopScaleFactor;
241 layout.DeviceScaleFactor = monitor->attributes.deviceScaleFactor;
242
243 auto mask = freerdp_settings_get_uint64(settings, FreeRDP_MonitorOverrideFlags);
244 if ((mask & FREERDP_MONITOR_OVERRIDE_ORIENTATION) != 0)
245 layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
246 if ((mask & FREERDP_MONITOR_OVERRIDE_DESKTOP_SCALE) != 0)
247 layout.DesktopScaleFactor =
248 freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
249 if ((mask & FREERDP_MONITOR_OVERRIDE_DEVICE_SCALE) != 0)
250 layout.DeviceScaleFactor =
251 freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
252 layouts.emplace_back(layout);
253 }
254
255 if (!settings_changed(layouts))
256 return true;
257
258 WINPR_ASSERT(_disp);
259 const size_t len = layouts.size();
260 WINPR_ASSERT(len <= UINT32_MAX);
261 const auto ret = IFCALLRESULT(CHANNEL_RC_OK, _disp->SendMonitorLayout, _disp,
262 static_cast<UINT32>(len), layouts.data());
263 if (ret != CHANNEL_RC_OK)
264 return false;
265 _last_sent_layout = layouts;
266 return true;
267}
268
269bool sdlDispContext::addTimer()
270{
271 if (SDL_WasInit(SDL_INIT_EVENTS) == 0)
272 return false;
273
274 SDL_RemoveTimer(_timer);
275 WLog_Print(_sdl->getWLog(), WLOG_TRACE, "adding new display check timer");
276
277 _timer_retries = 0;
278 if (!sendResize())
279 return false;
280 _timer = SDL_AddTimer(1000, sdlDispContext::OnTimer, this);
281 return true;
282}
283
284bool sdlDispContext::updateMonitor(SDL_WindowID id)
285{
286 if (!freerdp_settings_get_bool(_sdl->context()->settings, FreeRDP_DynamicResolutionUpdate))
287 return true;
288
289 if (!_sdl->updateWindow(id))
290 return false;
291
292 if (!_sdl->updateWindowList())
293 return false;
294
295 return addTimer();
296}
297
298bool sdlDispContext::updateMonitors(SDL_EventType type, SDL_DisplayID displayID)
299{
300 auto settings = _sdl->context()->settings;
301 if (!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
302 return true;
303
304 if (!freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
305 return true;
306
307 switch (type)
308 {
309 case SDL_EVENT_DISPLAY_ADDED:
310 if (!_sdl->addDisplayWindow(displayID))
311 return false;
312 break;
313 case SDL_EVENT_DISPLAY_REMOVED:
314 if (!_sdl->removeDisplayWindow(displayID))
315 return false;
316 break;
317 default:
318 break;
319 }
320
321 if (!_sdl->updateWindowList())
322 return false;
323 return addTimer();
324}
325
326bool sdlDispContext::handleEvent(const SDL_DisplayEvent& ev)
327{
328 const auto cat = SDL_LOG_CATEGORY_APPLICATION;
329 switch (ev.type)
330 {
331 case SDL_EVENT_DISPLAY_ADDED:
332 SDL_LogDebug(cat, "A new display with id %u was connected", ev.displayID);
333 return updateMonitors(ev.type, ev.displayID);
334 case SDL_EVENT_DISPLAY_REMOVED:
335 SDL_LogDebug(cat, "The display with id %u was disconnected", ev.displayID);
336 return updateMonitors(ev.type, ev.displayID);
337 case SDL_EVENT_DISPLAY_ORIENTATION:
338 SDL_LogDebug(cat, "The orientation of display with id %u was changed", ev.displayID);
339 return updateMonitors(ev.type, ev.displayID);
340 case SDL_EVENT_DISPLAY_MOVED:
341 SDL_LogDebug(cat, "The display with id %u was moved", ev.displayID);
342 return updateMonitors(ev.type, ev.displayID);
343 case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
344 SDL_LogDebug(cat, "The display with id %u changed scale", ev.displayID);
345 return updateMonitors(ev.type, ev.displayID);
346 case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED:
347 SDL_LogDebug(cat, "The display with id %u changed mode", ev.displayID);
348 return updateMonitors(ev.type, ev.displayID);
349 case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
350 SDL_LogDebug(cat, "The display with id %u changed desktop mode", ev.displayID);
351 return updateMonitors(ev.type, ev.displayID);
352 default:
353 return true;
354 }
355}
356
357bool sdlDispContext::handleEvent(const SDL_WindowEvent& ev)
358{
359 auto window = _sdl->getWindowForId(ev.windowID);
360 if (!window)
361 return true;
362
363 auto bordered = freerdp_settings_get_bool(_sdl->context()->settings, FreeRDP_Decorations);
364 window->setBordered(bordered);
365
366 switch (ev.type)
367 {
368 case SDL_EVENT_WINDOW_HIDDEN:
369 case SDL_EVENT_WINDOW_MINIMIZED:
370 return _sdl->redraw(true);
371 case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
372 return updateMonitor(ev.windowID);
373 case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
374 return updateMonitor(ev.windowID);
375
376 case SDL_EVENT_WINDOW_EXPOSED:
377 case SDL_EVENT_WINDOW_SHOWN:
378 case SDL_EVENT_WINDOW_MAXIMIZED:
379 case SDL_EVENT_WINDOW_RESTORED:
380 if (!_sdl->redraw())
381 return false;
382
383 /* fallthrough */
384 WINPR_FALLTHROUGH
385 case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
386 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
387 case SDL_EVENT_WINDOW_RESIZED:
388 return updateMonitor(ev.windowID);
389 case SDL_EVENT_WINDOW_MOUSE_LEAVE:
390 WINPR_ASSERT(_sdl);
391 return _sdl->getInputChannelContext().keyboard_grab(ev.windowID, false);
392 case SDL_EVENT_WINDOW_MOUSE_ENTER:
393 WINPR_ASSERT(_sdl);
394 if (!_sdl->getInputChannelContext().keyboard_grab(ev.windowID, true))
395 return false;
396 return _sdl->getInputChannelContext().keyboard_focus_in();
397 case SDL_EVENT_WINDOW_FOCUS_GAINED:
398 return _sdl->getInputChannelContext().keyboard_focus_in();
399
400 default:
401 return true;
402 }
403}
404
405UINT sdlDispContext::DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors,
406 UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB)
407{
408 /* we're called only if dynamic resolution update is activated */
409 WINPR_ASSERT(disp);
410
411 auto sdlDisp = reinterpret_cast<sdlDispContext*>(disp->custom);
412 return sdlDisp->DisplayControlCaps(maxNumMonitors, maxMonitorAreaFactorA,
413 maxMonitorAreaFactorB);
414}
415
416UINT sdlDispContext::DisplayControlCaps(UINT32 maxNumMonitors, UINT32 maxMonitorAreaFactorA,
417 UINT32 maxMonitorAreaFactorB)
418{
419 auto settings = _sdl->context()->settings;
420 WINPR_ASSERT(settings);
421
422 WLog_DBG(TAG,
423 "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32
424 " MaxMonitorAreaFactorB: %" PRIu32 "",
425 maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB);
426 _activated = true;
427
428 if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
429 return CHANNEL_RC_OK;
430
431 WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable");
432 return setWindowResizeable() ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY;
433}
434
435bool sdlDispContext::init(DispClientContext* disp)
436{
437 if (!disp)
438 return false;
439
440 auto settings = _sdl->context()->settings;
441
442 if (!settings)
443 return false;
444
445 _disp = disp;
446 disp->custom = this;
447
448 if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
449 {
450 disp->DisplayControlCaps = sdlDispContext::DisplayControlCaps;
451 }
452
453 return _sdl->setResizeable(true);
454}
455
456bool sdlDispContext::uninit(DispClientContext* disp)
457{
458 if (!disp)
459 return false;
460
461 _disp = nullptr;
462 return _sdl->setResizeable(false);
463}
464
465sdlDispContext::sdlDispContext(SdlContext* sdl) : _sdl(sdl)
466{
467 WINPR_ASSERT(_sdl);
468 WINPR_ASSERT(_sdl->context()->settings);
469 WINPR_ASSERT(_sdl->context()->pubSub);
470
471 auto pubSub = _sdl->context()->pubSub;
472
473 if (PubSub_SubscribeActivated(pubSub, sdlDispContext::OnActivated) < 0)
474 throw std::exception();
475 if (PubSub_SubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset) < 0)
476 throw std::exception();
477 std::ignore = addTimer();
478}
479
480sdlDispContext::~sdlDispContext()
481{
482 wPubSub* pubSub = _sdl->context()->pubSub;
483 WINPR_ASSERT(pubSub);
484
485 PubSub_UnsubscribeActivated(pubSub, sdlDispContext::OnActivated);
486 PubSub_UnsubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset);
487 SDL_RemoveTimer(_timer);
488}
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.