FreeRDP
Loading...
Searching...
No Matches
sdl_context.cpp
1
20#include <algorithm>
21#include <array>
22#include <cmath>
23#include <cfloat>
24
25#include "sdl_context.hpp"
26#include "sdl_config.hpp"
27#include "sdl_channels.hpp"
28#include "sdl_monitor.hpp"
29#include "sdl_pointer.hpp"
30#include "sdl_touch.hpp"
31#include "sdl_prefs.hpp"
32
33#include <sdl_common_utils.hpp>
34#include <scoped_guard.hpp>
35
36#include "dialogs/sdl_dialogs.hpp"
37#include <freerdp/client/aad_helper.h>
38
39static constexpr auto sdl_allow_screensaver = "sdl-allow-screensaver";
40static constexpr auto sdl_connection_dialog = "sdl-connection-dialog";
41
42SdlContext::SdlContext(rdpContext* context)
43 : _context(context), _log(WLog_Get(CLIENT_TAG("SDL"))), _cursor(nullptr, sdl_Pointer_FreeCopy),
44 _rdpThreadRunning(false), _primary(nullptr, SDL_DestroySurface), _disp(this), _input(this),
45 _clip(this), _rail(this), _dialog(_log)
46{
47 WINPR_ASSERT(context);
48 setMetadata();
49
50 auto instance = _context->instance;
51 WINPR_ASSERT(instance);
52
53 instance->PreConnect = preConnect;
54 instance->PostConnect = postConnect;
55 instance->PostDisconnect = postDisconnect;
56 instance->PostFinalDisconnect = postFinalDisconnect;
57 instance->AuthenticateEx = sdl_authenticate_ex;
58 instance->VerifyCertificateEx = sdl_verify_certificate_ex;
59 instance->VerifyChangedCertificateEx = sdl_verify_changed_certificate_ex;
60 instance->LogonErrorInfo = sdl_logon_error_info;
61 instance->PresentGatewayMessage = sdl_present_gateway_message;
62 instance->ChooseSmartcard = sdl_choose_smartcard;
63 instance->RetryDialog = sdl_retry_dialog;
64 instance->GetAccessToken = client_failsafe_get_access_token;
65
66 /* TODO: Client display set up */
67
68 _args.push_back({ sdl_allow_screensaver, COMMAND_LINE_VALUE_BOOL, nullptr, BoolValueFalse,
69 nullptr, -1, nullptr, "Allow local screensaver to activate" });
70 /* BoolValueTrue does not compile in C++, any non null default means on */
71 _args.push_back({ sdl_connection_dialog, COMMAND_LINE_VALUE_BOOL, nullptr, "on", nullptr, -1,
72 nullptr, "the connection dialog with progress and errors" });
73
74 /* Push a null element used as abort when iterating the array */
75 _args.push_back({ nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr });
76}
77
78void SdlContext::setHasCursor(bool val)
79{
80 this->_cursor_visible = val;
81}
82
83bool SdlContext::hasCursor() const
84{
85 return _cursor_visible;
86}
87
88void SdlContext::setMetadata()
89{
90 auto wmclass = freerdp_settings_get_string(_context->settings, FreeRDP_WmClass);
91 if (!wmclass || (strlen(wmclass) == 0))
92 wmclass = SDL_CLIENT_UUID;
93
94 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING, wmclass);
95 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING, SDL_CLIENT_NAME);
96 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_VERSION_STRING, SDL_CLIENT_VERSION);
97 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_CREATOR_STRING, SDL_CLIENT_VENDOR);
98 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_COPYRIGHT_STRING, SDL_CLIENT_COPYRIGHT);
99 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_URL_STRING, SDL_CLIENT_URL);
100 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_TYPE_STRING, SDL_CLIENT_TYPE);
101}
102
103int SdlContext::start()
104{
105 _thread = std::thread(rdpThreadRun, this);
106 return 0;
107}
108
109int SdlContext::join()
110{
111 /* We do not want to use freerdp_abort_connect_context here.
112 * It would change the exit code and we do not want that. */
113 HANDLE event = freerdp_abort_event(context());
114 if (!SetEvent(event))
115 return -1;
116
117 _thread.join();
118 return 0;
119}
120
121void SdlContext::cleanup()
122{
123 std::unique_lock lock(_critical);
124 _windows.clear();
125 _dialog.destroy();
126 _primary.reset();
127}
128
129bool SdlContext::shallAbort(bool ignoreDialogs)
130{
131 std::unique_lock lock(_critical);
132 if (freerdp_shall_disconnect_context(context()))
133 {
134 if (ignoreDialogs)
135 return true;
136 if (_rdpThreadRunning)
137 return false;
138 return !getDialog().isRunning();
139 }
140 return false;
141}
142
143/* Called before a connection is established.
144 * Set all configuration options to support and load channels here. */
145BOOL SdlContext::preConnect(freerdp* instance)
146{
147 WINPR_ASSERT(instance);
148 WINPR_ASSERT(instance->context);
149
150 auto sdl = get_context(instance->context);
151
152 auto settings = instance->context->settings;
153 WINPR_ASSERT(settings);
154
155 if (!freerdp_settings_set_bool(settings, FreeRDP_CertificateCallbackPreferPEM, TRUE))
156 return FALSE;
157
158 /* Optional OS identifier sent to server */
159 if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX))
160 return FALSE;
161 if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_SDL))
162 return FALSE;
163 /* OrderSupport is initialized at this point.
164 * Only override it if you plan to implement custom order
165 * callbacks or deactivate certain features. */
166 /* Register the channel listeners.
167 * They are required to set up / tear down channels if they are loaded. */
168 if (PubSub_SubscribeChannelConnected(instance->context->pubSub,
169 sdl_OnChannelConnectedEventHandler) < 0)
170 return FALSE;
171 if (PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
172 sdl_OnChannelDisconnectedEventHandler) < 0)
173 return FALSE;
174 if (PubSub_SubscribeUserNotification(instance->context->pubSub,
175 sdl_OnUserNotificationEventHandler) < 0)
176 return FALSE;
177
178 if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
179 {
180 UINT32 maxWidth = 0;
181 UINT32 maxHeight = 0;
182
183 if (!sdl_detect_monitors(sdl, &maxWidth, &maxHeight))
184 return FALSE;
185
186 if ((maxWidth != 0) && (maxHeight != 0) &&
187 !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
188 {
189 WLog_Print(sdl->getWLog(), WLOG_INFO, "Update size to %ux%u", maxWidth, maxHeight);
190 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, maxWidth))
191 return FALSE;
192 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, maxHeight))
193 return FALSE;
194 }
195
201 const uint32_t sw = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth);
202 const uint32_t sh = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight);
203 const BOOL sm = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing);
204 if (sm && (sw > 0) && (sh > 0))
205 {
206 const BOOL mm = freerdp_settings_get_bool(settings, FreeRDP_UseMultimon);
207 if (mm)
208 WLog_Print(sdl->getWLog(), WLOG_WARN,
209 "/smart-sizing and /multimon are currently not supported, ignoring "
210 "/smart-sizing!");
211 else
212 {
213 sdl->_windowWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
214 sdl->_windowHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
215
216 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, sw))
217 return FALSE;
218 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, sh))
219 return FALSE;
220 }
221 }
222 }
223 else
224 {
225 /* Check +auth-only has a username and password. */
226 if (!freerdp_settings_get_string(settings, FreeRDP_Password))
227 {
228 WLog_Print(sdl->getWLog(), WLOG_INFO,
229 "auth-only, but no password set. Please provide one.");
230 return FALSE;
231 }
232
233 if (!freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, TRUE))
234 return FALSE;
235
236 WLog_Print(sdl->getWLog(), WLOG_INFO, "Authentication only. Don't connect SDL.");
237 }
238
239 if (!sdl->getInputChannelContext().initialize())
240 return FALSE;
241
242 sdl->_credentialsRead = false;
243
244 /* Settings are consistent at this point, the dialog can be created. */
245 auto prefs = SdlPref::instance();
246 auto dialog = prefs->get_bool("SDL_ConnectionDialog", true);
247 if (sdl->_connectionDialog && prefs->is_commandline_override_allowed())
248 dialog = *sdl->_connectionDialog;
249 if (dialog && !freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
250 {
251 sdl->getDialog().create(instance->context);
252 sdl->getDialog().setTitle("Connecting to '%s'", freerdp_settings_get_server_name(settings));
253 sdl->getDialog().showInfo("The connection is being established\n\nPlease wait...");
254 }
255
256 /* TODO: Any code your client requires */
257 return TRUE;
258}
259
260/* Called after a RDP connection was successfully established.
261 * Settings might have changed during negotiation of client / server feature
262 * support.
263 *
264 * Set up local framebuffers and paing callbacks.
265 * If required, register pointer callbacks to change the local mouse cursor
266 * when hovering over the RDP window
267 */
268BOOL SdlContext::postConnect(freerdp* instance)
269{
270 WINPR_ASSERT(instance);
271
272 auto context = instance->context;
273 WINPR_ASSERT(context);
274
275 auto sdl = get_context(context);
276
277 // Retry was successful, discard dialog
278 sdl->getDialog().show(false);
279
280 if (freerdp_settings_get_bool(context->settings, FreeRDP_AuthenticationOnly))
281 {
282 /* Check +auth-only has a username and password. */
283 if (!freerdp_settings_get_string(context->settings, FreeRDP_Password))
284 {
285 WLog_Print(sdl->getWLog(), WLOG_INFO,
286 "auth-only, but no password set. Please provide one.");
287 return FALSE;
288 }
289
290 WLog_Print(sdl->getWLog(), WLOG_INFO, "Authentication only. Don't connect to X.");
291 return TRUE;
292 }
293
294 if (!sdl->waitForWindowsCreated())
295 return FALSE;
296
297 sdl->_sdlPixelFormat = SDL_PIXELFORMAT_BGRA32;
298 if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
299 return FALSE;
300
301 if (!sdl->createPrimary())
302 return FALSE;
303
304 if (!sdl_register_pointer(instance->context->graphics))
305 return FALSE;
306
307 WINPR_ASSERT(context->update);
308
309 context->update->BeginPaint = beginPaint;
310 context->update->EndPaint = endPaint;
311 context->update->PlaySound = playSound;
312 context->update->DesktopResize = desktopResize;
313 context->update->SetKeyboardIndicators = sdlInput::keyboard_set_indicators;
314 context->update->SetKeyboardImeStatus = sdlInput::keyboard_set_ime_status;
315
316 if (!sdl->setResizeable(false))
317 return FALSE;
318 if (!sdl->setFullscreen(freerdp_settings_get_bool(context->settings, FreeRDP_Fullscreen) ||
319 freerdp_settings_get_bool(context->settings, FreeRDP_UseMultimon),
320 true))
321 return FALSE;
322 sdl->setConnected(true);
323 return TRUE;
324}
325
326/* This function is called whether a session ends by failure or success.
327 * Clean up everything allocated by pre_connect and post_connect.
328 */
329void SdlContext::postDisconnect(freerdp* instance)
330{
331 if (!instance)
332 return;
333
334 if (!instance->context)
335 return;
336
337 auto sdl = get_context(instance->context);
338 sdl->setConnected(false);
339
340 gdi_free(instance);
341}
342
343void SdlContext::postFinalDisconnect(freerdp* instance)
344{
345 if (!instance)
346 return;
347
348 if (!instance->context)
349 return;
350
351 PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
352 sdl_OnChannelConnectedEventHandler);
353 PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub,
354 sdl_OnChannelDisconnectedEventHandler);
355 PubSub_UnsubscribeUserNotification(instance->context->pubSub,
356 sdl_OnUserNotificationEventHandler);
357}
358
359/* Create a SDL surface from the GDI buffer */
360bool SdlContext::createPrimary()
361{
362 auto gdi = context()->gdi;
363 WINPR_ASSERT(gdi);
364
365 _primary = SDLSurfacePtr(
366 SDL_CreateSurfaceFrom(static_cast<int>(gdi->width), static_cast<int>(gdi->height),
367 pixelFormat(), gdi->primary_buffer, static_cast<int>(gdi->stride)),
368 SDL_DestroySurface);
369 if (!_primary)
370 return false;
371
372 SDL_SetSurfaceBlendMode(_primary.get(), SDL_BLENDMODE_NONE);
373 SDL_Rect surfaceRect = { 0, 0, gdi->width, gdi->height };
374 SDL_FillSurfaceRect(_primary.get(), &surfaceRect,
375 SDL_MapSurfaceRGBA(_primary.get(), 0, 0, 0, 0xff));
376
377 return true;
378}
379
380bool SdlContext::createWindows()
381{
382 auto settings = context()->settings;
383 const auto& title = windowTitle();
384
385 ScopeGuard guard1([&]() { _windowsCreatedEvent.set(); });
386
387 UINT32 windowCount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
388
389 /* The monitor definitions are sorted (primary first), so they need not be in the same
390 * order as the monitor IDs. Match them by display, fall back to the index. */
391 auto monitorFor = [&](UINT32 index, int64_t id)
392 {
393 for (UINT32 y = 0; y < windowCount; y++)
394 {
395 auto cur = static_cast<rdpMonitor*>(
396 freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, y));
397 if (cur && (static_cast<int64_t>(cur->orig_screen) == id))
398 return cur;
399 }
400 return static_cast<rdpMonitor*>(
401 freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, index));
402 };
403
404 Sint32 originX = 0;
405 Sint32 originY = 0;
406 for (UINT32 x = 0; x < windowCount; x++)
407 {
408 auto id = monitorId(x);
409 if (id < 0)
410 return false;
411
412 auto monitor = monitorFor(x, id);
413
414 originX = std::min<Sint32>(monitor->x, originX);
415 originY = std::min<Sint32>(monitor->y, originY);
416 }
417
418 for (UINT32 x = 0; x < windowCount; x++)
419 {
420 auto id = monitorId(x);
421 if (id < 0)
422 return false;
423
424 auto monitor = monitorFor(x, id);
425
426 Uint32 w = WINPR_ASSERTING_INT_CAST(Uint32, monitor->width);
427 Uint32 h = WINPR_ASSERTING_INT_CAST(Uint32, monitor->height);
428 if (!(freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) ||
429 freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)))
430 {
431 if (_windowWidth > 0)
432 w = _windowWidth;
433 else
434 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
435
436 if (_windowHeight > 0)
437 h = _windowHeight;
438 else
439 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
440 }
441
442 Uint32 flags = SDL_WINDOW_HIGH_PIXEL_DENSITY;
443
444 if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) &&
445 !freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
446 {
447 flags |= SDL_WINDOW_FULLSCREEN;
448 }
449
450 if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
451 {
452 flags |= SDL_WINDOW_BORDERLESS;
453 }
454
455 if (!freerdp_settings_get_bool(settings, FreeRDP_Decorations))
456 flags |= SDL_WINDOW_BORDERLESS;
457
458 /* RemoteApp: the desktop only shows when needed (drawToWindows). */
459 if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode))
460 flags |= SDL_WINDOW_HIDDEN;
461
462 auto did = WINPR_ASSERTING_INT_CAST(SDL_DisplayID, id);
463 auto window = SdlWindow::create(did, title, flags, w, h);
464
465 if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
466 {
467 window.setOffsetX(originX - monitor->x);
468 window.setOffsetY(originY - monitor->y);
469 }
470
471 _windows.insert({ window.id(), std::move(window) });
472 }
473
474 return true;
475}
476
477#if defined(__EMSCRIPTEN__)
478/* in sdl_monitors_emscripten.js */
479extern "C" int freerdp_web_monitors_js(int32_t* edges, int max);
480
481/* A browser has one canvas, so one window. The page may show it across several screens and
482 * split it into monitors (sdl_monitors_emscripten.js): the server then places its windows,
483 * panels and maximized windows on each, as on separate physical screens. canvas is the
484 * window's monitor; returns the monitors it is split into, or none. */
485static std::vector<rdpMonitor> sdl_web_monitors(const rdpMonitor& canvas)
486{
487 constexpr int max = 16;
488 std::array<int32_t, 5 * max> edges{};
489 const auto n = freerdp_web_monitors_js(edges.data(), max);
490 if ((n < 2) || (n > max))
491 return {};
492
493 const auto at = [](int32_t edge, int32_t size)
494 { return static_cast<INT32>(static_cast<int64_t>(edge) * size / 10000); };
495 std::vector<rdpMonitor> list;
496 for (int i = 0; i < n; i++)
497 {
498 const int32_t* e = &edges.at(5 * static_cast<size_t>(i));
499 const auto left = at(e[0], canvas.width);
500 const auto top = at(e[1], canvas.height);
501 const auto right = at(e[2], canvas.width);
502 const auto bottom = at(e[3], canvas.height);
503 if ((right <= left) || (bottom <= top))
504 return {};
505
506 rdpMonitor m = canvas;
507 m.x = left;
508 m.y = top;
509 m.width = right - left;
510 m.height = bottom - top;
511 m.is_primary = (e[4] != 0);
512 m.attributes.physicalWidth =
513 static_cast<UINT32>(static_cast<uint64_t>(canvas.attributes.physicalWidth) *
514 static_cast<UINT32>(m.width) / static_cast<UINT32>(canvas.width));
515 m.attributes.physicalHeight =
516 static_cast<UINT32>(static_cast<uint64_t>(canvas.attributes.physicalHeight) *
517 static_cast<UINT32>(m.height) / static_cast<UINT32>(canvas.height));
518 list.push_back(m);
519 }
520 return list;
521}
522#endif
523
524bool SdlContext::updateWindowList()
525{
526 std::vector<rdpMonitor> list;
527 list.reserve(_windows.size());
528 for (const auto& win : _windows)
529 list.push_back(win.second.monitor(_windows.size() == 1));
530
531#if defined(__EMSCRIPTEN__)
532 if ((list.size() == 1) && (list.at(0).width > 0) && (list.at(0).height > 0))
533 {
534 auto split = sdl_web_monitors(list.at(0));
535 if (!split.empty())
536 list = std::move(split);
537 }
538#endif
539
540 // /monitors: subset may exclude the SDL primary. The library requires
541 // the array to mark one monitor as primary, so promote the first when
542 // none of the kept windows cover the original primary.
543 if (!list.empty() &&
544 std::none_of(list.cbegin(), list.cend(), [](const rdpMonitor& m) { return m.is_primary; }))
545 list.at(0).is_primary = true;
546
547 return freerdp_settings_set_monitor_def_array_sorted(context()->settings, list.data(),
548 list.size());
549}
550
551bool SdlContext::updateWindow(SDL_WindowID id)
552{
553 /* In fullscreen the monitor kept the size measured with the probe window at startup and was
554 * never updated, so a session the server started at another size stayed that way. Only the
555 * window knows its real size, so ask it in fullscreen as well. */
556 if (freerdp_settings_get_bool(_context->settings, FreeRDP_UseMultimon))
557 return true;
558
559 auto& w = _windows.at(id);
560 auto m = w.monitor(true);
561 auto r = w.rect();
562 m.width = r.w;
563 m.height = r.h;
564 w.setMonitor(m);
565 return true;
566}
567
568std::string SdlContext::windowTitle() const
569{
570 const char* prefix = "FreeRDP:";
571
572 const auto windowTitle = freerdp_settings_get_string(context()->settings, FreeRDP_WindowTitle);
573 if (windowTitle)
574 return windowTitle;
575
576 const auto name = freerdp_settings_get_server_name(context()->settings);
577 const auto port = freerdp_settings_get_uint32(context()->settings, FreeRDP_ServerPort);
578 const auto addPort = (port != 3389);
579
580 std::stringstream ss;
581 ss << prefix << " " << name;
582
583 if (addPort)
584 ss << ":" << port;
585
586 return ss.str();
587}
588
589bool SdlContext::waitForWindowsCreated()
590{
591 {
592 std::unique_lock<CriticalSection> lock(_critical);
593 _windowsCreatedEvent.clear();
594 if (!sdl_push_user_event(SDL_EVENT_USER_CREATE_WINDOWS, this))
595 return false;
596 }
597
598 HANDLE handles[] = { _windowsCreatedEvent.handle(), freerdp_abort_event(context()) };
599
600 const DWORD rc = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
601 switch (rc)
602 {
603 case WAIT_OBJECT_0:
604 return true;
605 default:
606 return false;
607 }
608}
609
610/* This function is called when the library completed composing a new
611 * frame. Read out the changed areas and blit them to your output device.
612 * The image buffer will have the format specified by gdi_init
613 */
614BOOL SdlContext::endPaint(rdpContext* context)
615{
616 auto sdl = get_context(context);
617 WINPR_ASSERT(sdl);
618
619 auto gdi = context->gdi;
620 WINPR_ASSERT(gdi);
621 WINPR_ASSERT(gdi->primary);
622
623 HGDI_DC hdc = gdi->primary->hdc;
624 WINPR_ASSERT(hdc);
625 if (!hdc->hwnd)
626 return TRUE;
627
628 HGDI_WND hwnd = hdc->hwnd;
629 WINPR_ASSERT(hwnd->invalid || (hwnd->ninvalid == 0));
630
631 if (hwnd->invalid->null)
632 return TRUE;
633
634 WINPR_ASSERT(hwnd->invalid);
635 if (gdi->suppressOutput || hwnd->invalid->null)
636 return TRUE;
637
638 const INT32 ninvalid = hwnd->ninvalid;
639 const GDI_RGN* cinvalid = hwnd->cinvalid;
640
641 if (ninvalid < 1)
642 return TRUE;
643
644 std::vector<SDL_Rect> rects;
645 for (INT32 x = 0; x < ninvalid; x++)
646 {
647 auto& rgn = cinvalid[x];
648 rects.push_back({ rgn.x, rgn.y, rgn.w, rgn.h });
649 }
650
651 sdl->push(std::move(rects));
652 return sdl_push_user_event(SDL_EVENT_USER_UPDATE);
653}
654
655void SdlContext::sdl_client_cleanup(int exit_code, const std::string& error_msg)
656{
657 rdpSettings* settings = context()->settings;
658 WINPR_ASSERT(settings);
659
660 _rdpThreadRunning = false;
661 bool showError = false;
662 if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
663 WLog_Print(getWLog(), WLOG_INFO, "Authentication only, exit status %s [%" PRId32 "]",
664 sdl::error::exitCodeToTag(exit_code), exit_code);
665 else
666 {
667 switch (exit_code)
668 {
669 case sdl::error::SUCCESS:
670 case sdl::error::DISCONNECT:
671 case sdl::error::LOGOFF:
672 case sdl::error::DISCONNECT_BY_USER:
673 case sdl::error::CONNECT_CANCELLED:
674 break;
675 default:
676 {
677 showError = true;
678 getDialog().showError(error_msg);
679 }
680 break;
681 }
682 }
683
684 if (!showError)
685 getDialog().show(false);
686
687 _exitCode = exit_code;
688 std::ignore = sdl_push_user_event(SDL_EVENT_USER_QUIT);
689 SDL_CleanupTLS();
690}
691
692int SdlContext::sdl_client_thread_connect(std::string& error_msg)
693{
694 auto instance = context()->instance;
695 WINPR_ASSERT(instance);
696
697 _rdpThreadRunning = true;
698 BOOL rc = freerdp_connect(instance);
699
700 rdpSettings* settings = context()->settings;
701 WINPR_ASSERT(settings);
702
703 int exit_code = sdl::error::SUCCESS;
704 if (!rc)
705 {
706 UINT32 error = freerdp_get_last_error(context());
707 exit_code = sdl::error::errorToExitCode(error);
708 }
709
710 if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
711 {
712 DWORD code = freerdp_get_last_error(context());
713 freerdp_abort_connect_context(context());
714 WLog_Print(getWLog(), WLOG_ERROR, "Authentication only, %s [0x%08" PRIx32 "] %s",
715 freerdp_get_last_error_name(code), code, freerdp_get_last_error_string(code));
716 return exit_code;
717 }
718
719 if (!rc)
720 {
721 DWORD code = freerdp_error_info(instance);
722 if (exit_code == sdl::error::SUCCESS)
723 {
724 char* msg = nullptr;
725 size_t len = 0;
726 exit_code = error_info_to_error(&code, &msg, &len);
727 if (msg)
728 error_msg = msg;
729 free(msg);
730 }
731
732 auto last = freerdp_get_last_error(context());
733 if (error_msg.empty())
734 {
735 char* msg = nullptr;
736 size_t len = 0;
737 winpr_asprintf(&msg, &len, "%s [0x%08" PRIx32 "]\n%s",
738 freerdp_get_last_error_name(last), last,
739 freerdp_get_last_error_string(last));
740 if (msg)
741 error_msg = msg;
742 free(msg);
743 }
744
745 if (exit_code == sdl::error::SUCCESS)
746 {
747 if (last == FREERDP_ERROR_AUTHENTICATION_FAILED)
748 exit_code = sdl::error::AUTH_FAILURE;
749 else if (code == ERRINFO_SUCCESS)
750 exit_code = sdl::error::CONN_FAILED;
751 }
752
753 getDialog().show(false);
754 }
755
756 return exit_code;
757}
758
759int SdlContext::sdl_client_thread_run(std::string& error_msg)
760{
761 auto instance = context()->instance;
762 WINPR_ASSERT(instance);
763
764 int exit_code = sdl::error::SUCCESS;
765 while (!freerdp_shall_disconnect_context(context()))
766 {
767 HANDLE handles[MAXIMUM_WAIT_OBJECTS] = {};
768 /*
769 * win8 and server 2k12 seem to have some timing issue/race condition
770 * when a initial sync request is send to sync the keyboard indicators
771 * sending the sync event twice fixed this problem
772 */
773 if (freerdp_focus_required(instance))
774 {
775 auto ctx = get_context(context());
776 WINPR_ASSERT(ctx);
777
778 auto& input = ctx->getInputChannelContext();
779 if (!input.keyboard_focus_in())
780 break;
781 if (!input.keyboard_focus_in())
782 break;
783 }
784
785 const DWORD nCount = freerdp_get_event_handles(context(), handles, ARRAYSIZE(handles));
786
787 if (nCount == 0)
788 {
789 WLog_Print(getWLog(), WLOG_ERROR, "freerdp_get_event_handles failed");
790 break;
791 }
792
793 const DWORD status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
794
795 if (status == WAIT_FAILED)
796 {
797 WLog_Print(getWLog(), WLOG_ERROR, "WaitForMultipleObjects WAIT_FAILED");
798 break;
799 }
800
801 if (!freerdp_check_event_handles(context()))
802 {
803 if (client_auto_reconnect(instance))
804 {
805 // Retry was successful, discard dialog
806 getDialog().show(false);
807 continue;
808 }
809 else
810 {
811 /*
812 * Indicate an unsuccessful connection attempt if reconnect
813 * did not succeed and no other error was specified.
814 */
815 if (freerdp_error_info(instance) == 0)
816 exit_code = sdl::error::errorToExitCode(freerdp_get_last_error(context()));
817 }
818
819 if (freerdp_get_last_error(context()) == FREERDP_ERROR_SUCCESS)
820 WLog_Print(getWLog(), WLOG_ERROR, "WaitForMultipleObjects failed with %" PRIu32 "",
821 status);
822 if (freerdp_get_last_error(context()) == FREERDP_ERROR_SUCCESS)
823 WLog_Print(getWLog(), WLOG_ERROR, "Failed to check FreeRDP event handles");
824 break;
825 }
826 }
827
828 if (exit_code == sdl::error::SUCCESS)
829 {
830 DWORD code = 0;
831 {
832 char* emsg = nullptr;
833 size_t elen = 0;
834 exit_code = error_info_to_error(&code, &emsg, &elen);
835 if (emsg)
836 error_msg = emsg;
837 free(emsg);
838 }
839
840 if ((code == ERRINFO_LOGOFF_BY_USER) &&
841 (freerdp_get_disconnect_ultimatum(context()) == Disconnect_Ultimatum_user_requested))
842 {
843 const char* msg = "Error info says user did not initiate but disconnect ultimatum says "
844 "they did; treat this as a user logoff";
845
846 char* emsg = nullptr;
847 size_t elen = 0;
848 winpr_asprintf(&emsg, &elen, "%s", msg);
849 if (emsg)
850 error_msg = emsg;
851 free(emsg);
852
853 /* This situation might be limited to Windows XP. */
854 WLog_Print(getWLog(), WLOG_INFO, "%s", msg);
855 exit_code = sdl::error::LOGOFF;
856 }
857 }
858
859 if ((exit_code != sdl::error::SUCCESS) && error_msg.empty())
860 {
861 const auto last = freerdp_get_last_error(context());
862 char* msg = nullptr;
863 size_t len = 0;
864 winpr_asprintf(&msg, &len, "%s [0x%08" PRIx32 "]\n%s", freerdp_get_last_error_name(last),
865 last, freerdp_get_last_error_string(last));
866 if (msg)
867 error_msg = msg;
868 free(msg);
869 }
870
871 freerdp_disconnect(instance);
872
873 return exit_code;
874}
875
876/* RDP main loop.
877 * Connects RDP, loops while running and handles event and dispatch, cleans up
878 * after the connection ends. */
879DWORD SdlContext::rdpThreadRun(SdlContext* sdl)
880{
881 WINPR_ASSERT(sdl);
882
883 std::string error_msg;
884 int exit_code = sdl->sdl_client_thread_connect(error_msg);
885 if (exit_code == sdl::error::SUCCESS)
886 exit_code = sdl->sdl_client_thread_run(error_msg);
887 sdl->sdl_client_cleanup(exit_code, error_msg);
888
889 return static_cast<DWORD>(exit_code);
890}
891
892int SdlContext::error_info_to_error(DWORD* pcode, char** msg, size_t* len) const
893{
894 const DWORD code = freerdp_error_info(context()->instance);
895 const char* name = freerdp_get_error_info_name(code);
896 const char* str = freerdp_get_error_info_string(code);
897 const int exit_code = sdl::error::errorToExitCode(code);
898
899 winpr_asprintf(msg, len, "Terminate with %s due to ERROR_INFO %s [0x%08" PRIx32 "]: %s",
900 sdl::error::errorToExitCodeTag(code), name, code, str);
901 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%s", *msg);
902 if (pcode)
903 *pcode = code;
904 return exit_code;
905}
906
907void SdlContext::applyMonitorOffset(SDL_WindowID window, float& x, float& y) const
908{
909 if (!freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon))
910 return;
911
912 auto w = getWindowForId(window);
913 x -= static_cast<float>(w->offsetX());
914 y -= static_cast<float>(w->offsetY());
915}
916
917static bool alignX(const SDL_Rect& a, const SDL_Rect& b)
918{
919 if (a.x + a.w == b.x)
920 return true;
921 if (b.x + b.w == a.x)
922 return true;
923 return false;
924}
925
926static bool alignY(const SDL_Rect& a, const SDL_Rect& b)
927{
928 if (a.y + a.h == b.y)
929 return true;
930 if (b.y + b.h == a.y)
931 return true;
932 return false;
933}
934
935std::vector<SDL_DisplayID>
936SdlContext::updateDisplayOffsetsForNeighbours(SDL_DisplayID id,
937 const std::vector<SDL_DisplayID>& ignore)
938{
939 auto first = _offsets.at(id);
940 std::vector<SDL_DisplayID> neighbours;
941
942 for (auto& entry : _offsets)
943 {
944 if (entry.first == id)
945 continue;
946 if (std::find(ignore.begin(), ignore.end(), entry.first) != ignore.end())
947 continue;
948
949 bool neighbor = false;
950 const bool ax = alignX(entry.second.first, first.first);
951 const bool ay = alignY(entry.second.first, first.first);
952
953 /* Offset along the axis that does not touch, converted from logical to pixel
954 * units of the reference display. Without this, a monitor that is adjacent on
955 * one axis but shifted on the other (e.g. a wider monitor above another, or a
956 * rotated monitor that is not top aligned) keeps offset 0 on that axis, the
957 * layout sent to the server no longer matches the local one and pointer input
958 * lands offset by that difference. */
959 auto crossOffset = [](int ePos, int fPos, int fLogical, int fPixel) -> int
960 {
961 if (fLogical <= 0)
962 return ePos - fPos;
963 return static_cast<int>(
964 std::lround(static_cast<double>(ePos - fPos) * fPixel / fLogical));
965 };
966
967 if (ax)
968 {
969 if (entry.second.first.x < first.first.x)
970 entry.second.second.x = first.second.x - entry.second.second.w;
971 else
972 entry.second.second.x = first.second.x + first.second.w;
973 if (!ay)
974 entry.second.second.y =
975 first.second.y +
976 crossOffset(entry.second.first.y, first.first.y, first.first.h, first.second.h);
977 neighbor = true;
978 }
979 if (ay)
980 {
981 if (entry.second.first.y < first.first.y)
982 entry.second.second.y = first.second.y - entry.second.second.h;
983 else
984 entry.second.second.y = first.second.y + first.second.h;
985 if (!ax)
986 entry.second.second.x =
987 first.second.x +
988 crossOffset(entry.second.first.x, first.first.x, first.first.w, first.second.w);
989 neighbor = true;
990 }
991
992 if (neighbor)
993 neighbours.push_back(entry.first);
994 }
995 return neighbours;
996}
997
998void SdlContext::updateMonitorDataFromOffsets()
999{
1000 for (auto& entry : _displays)
1001 {
1002 auto offsets = _offsets.at(entry.first);
1003 entry.second.x = offsets.second.x;
1004 entry.second.y = offsets.second.y;
1005 }
1006
1007 for (auto& entry : _windows)
1008 {
1009 const auto& monitor = _displays.at(entry.first);
1010 entry.second.setMonitor(monitor);
1011 }
1012}
1013
1014bool SdlContext::drawToWindow(SdlWindow& window, const std::vector<SDL_Rect>& rects)
1015{
1016 if (!isConnected())
1017 return true;
1018
1019 auto gdi = context()->gdi;
1020 WINPR_ASSERT(gdi);
1021
1022 auto size = window.rect();
1023
1024 std::unique_lock lock(_critical);
1025 auto surface = _primary.get();
1026
1027 if (useLocalScale())
1028 {
1029 window.setOffsetX(0);
1030 window.setOffsetY(0);
1031 if (gdi->width < size.w)
1032 {
1033 window.setOffsetX((size.w - gdi->width) / 2);
1034 }
1035 if (gdi->height < size.h)
1036 {
1037 window.setOffsetY((size.h - gdi->height) / 2);
1038 }
1039
1040 _localScale = { static_cast<float>(size.w) / static_cast<float>(gdi->width),
1041 static_cast<float>(size.h) / static_cast<float>(gdi->height) };
1042 if (!window.drawScaledRects(surface, _localScale, rects))
1043 return false;
1044 }
1045 else
1046 {
1047 SDL_Point offset{ 0, 0 };
1048 if (freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon))
1049 offset = { window.offsetX(), window.offsetY() };
1050 if (!window.drawRects(surface, offset, rects))
1051 return false;
1052 }
1053
1054 window.updateSurface();
1055 return true;
1056}
1057
1058bool SdlContext::minimizeAllWindows()
1059{
1060 for (auto& w : _windows)
1061 w.second.minimize();
1062 return true;
1063}
1064
1065int SdlContext::exitCode() const
1066{
1067 return _exitCode;
1068}
1069
1070SDL_PixelFormat SdlContext::pixelFormat() const
1071{
1072 return _sdlPixelFormat;
1073}
1074
1075bool SdlContext::addDisplayWindow(SDL_DisplayID id)
1076{
1077 const auto flags =
1078 SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS;
1079 auto title = sdl::utils::windowTitle(context()->settings);
1080 auto w = SdlWindow::create(id, title, flags);
1081 _windows.emplace(w.id(), std::move(w));
1082 return true;
1083}
1084
1085bool SdlContext::removeDisplayWindow(SDL_DisplayID id)
1086{
1087 for (auto& w : _windows)
1088 {
1089 if (w.second.displayIndex() == id)
1090 _windows.erase(w.first);
1091 }
1092 return true;
1093}
1094
1095bool SdlContext::detectDisplays()
1096{
1097 int count = 0;
1098 auto display = SDL_GetDisplays(&count);
1099 if (!display)
1100 return false;
1101 for (int x = 0; x < count; x++)
1102 {
1103 const auto id = display[x];
1104 addOrUpdateDisplay(id);
1105 }
1106 SDL_free(display);
1107 return true;
1108}
1109
1110rdpMonitor SdlContext::getDisplay(SDL_DisplayID id) const
1111{
1112 return _displays.at(id);
1113}
1114
1115std::vector<SDL_DisplayID> SdlContext::getDisplayIds() const
1116{
1117 std::vector<SDL_DisplayID> keys;
1118 keys.reserve(_displays.size());
1119 for (const auto& entry : _displays)
1120 {
1121 keys.push_back(entry.first);
1122 }
1123 return keys;
1124}
1125
1126const SdlWindow* SdlContext::getWindowForId(SDL_WindowID id) const
1127{
1128 auto it = _windows.find(id);
1129 if (it == _windows.end())
1130 return nullptr;
1131 return &it->second;
1132}
1133
1134SdlWindow* SdlContext::getWindowForId(SDL_WindowID id)
1135{
1136 auto it = _windows.find(id);
1137 if (it == _windows.end())
1138 return nullptr;
1139 return &it->second;
1140}
1141
1142bool SdlContext::allWindowsHidden() const
1143{
1144 return std::all_of(_windows.begin(), _windows.end(),
1145 [](const auto& entry)
1146 {
1147 const auto flags = SDL_GetWindowFlags(entry.second.window());
1148 return (flags & (SDL_WINDOW_HIDDEN | SDL_WINDOW_MINIMIZED)) != 0;
1149 });
1150}
1151
1152SdlWindow* SdlContext::getFirstWindow()
1153{
1154 if (_windows.empty())
1155 return nullptr;
1156 return &_windows.begin()->second;
1157}
1158
1159sdlDispContext& SdlContext::getDisplayChannelContext()
1160{
1161 return _disp;
1162}
1163
1164sdlInput& SdlContext::getInputChannelContext()
1165{
1166 return _input;
1167}
1168
1169sdlClip& SdlContext::getClipboardChannelContext()
1170{
1171 return _clip;
1172}
1173
1174SdlRail& SdlContext::getRailChannelContext()
1175{
1176 return _rail;
1177}
1178
1179SdlConnectionDialogWrapper& SdlContext::getDialog()
1180{
1181 return _dialog;
1182}
1183
1184wLog* SdlContext::getWLog()
1185{
1186 return _log;
1187}
1188
1189bool SdlContext::moveMouseTo(const SDL_FPoint& pos)
1190{
1191 auto window = SDL_GetMouseFocus();
1192 if (!window)
1193 return true;
1194
1195 const auto id = SDL_GetWindowID(window);
1196 const auto spos = pixelToScreen(id, pos);
1197 SDL_WarpMouseInWindow(window, spos.x, spos.y);
1198 return true;
1199}
1200
1201static bool onWindowEdge(SDL_WindowID id, float x, float y)
1202{
1203 int w = 0;
1204 int h = 0;
1205 auto* window = SDL_GetWindowFromID(id);
1206 if (!window || !SDL_GetWindowSize(window, &w, &h))
1207 return false;
1208 return (x <= 0.0f) || (y <= 0.0f) || (x >= static_cast<float>(w - 1)) ||
1209 (y >= static_cast<float>(h - 1));
1210}
1211
1212bool SdlContext::handleEvent(const SDL_MouseMotionEvent& ev)
1213{
1214 SDL_Event copy{};
1215 copy.motion = ev;
1216 /* SDL follows a release outside the window with a motion clamped to its edge: drop it. */
1217 if (_rail.enabled() && (ev.state == 0) && (ev.windowID == _lastRelease.window) &&
1218 (ev.timestamp - _lastRelease.time < SDL_MS_TO_NS(2)) &&
1219 onWindowEdge(ev.windowID, ev.x, ev.y))
1220 return true;
1221 /* WM owns the drag (#12447); backstop: button released but button-up swallowed by grab. */
1222 if (_rail.enabled() && _rail.suppressServerMotion(ev.windowID))
1223 {
1224 if (!(SDL_GetGlobalMouseState(nullptr, nullptr) & SDL_BUTTON_LMASK))
1225 _rail.completeLocalMoveIfPending();
1226 return true;
1227 }
1228 if (_rail.enabled() && _rail.translateToServer(ev.windowID, copy.motion.x, copy.motion.y))
1229 {
1230 _lastMotion = { ev.windowID, { ev.x, ev.y }, { copy.motion.x, copy.motion.y } };
1231 _rail.noteServerPointer(copy.motion.x, copy.motion.y);
1232 return SdlTouch::handleEvent(this, copy.motion);
1233 }
1234
1235 if (!getWindowForId(ev.windowID))
1236 return true; /* Event for an untracked window (e.g. closed dialog) */
1237 if (!eventToPixelCoordinates(ev.windowID, copy))
1238 return true;
1239 removeLocalScaling(copy.motion.x, copy.motion.y);
1240 removeLocalScaling(copy.motion.xrel, copy.motion.yrel);
1241 applyMonitorOffset(copy.motion.windowID, copy.motion.x, copy.motion.y);
1242
1243 return SdlTouch::handleEvent(this, copy.motion);
1244}
1245
1246bool SdlContext::handleEvent(const SDL_MouseWheelEvent& ev)
1247{
1248 SDL_Event copy{};
1249 copy.wheel = ev;
1250 if (_rail.enabled() &&
1251 _rail.translateToServer(ev.windowID, copy.wheel.mouse_x, copy.wheel.mouse_y))
1252 return SdlTouch::handleEvent(this, copy.wheel);
1253
1254 if (!getWindowForId(ev.windowID))
1255 return true;
1256 if (!eventToPixelCoordinates(ev.windowID, copy))
1257 return true;
1258 removeLocalScaling(copy.wheel.mouse_x, copy.wheel.mouse_y);
1259 return SdlTouch::handleEvent(this, copy.wheel);
1260}
1261
1262bool SdlContext::handleEvent(const SDL_WindowEvent& ev)
1263{
1264 if (!getDisplayChannelContext().handleEvent(ev))
1265 return false;
1266
1267 auto window = getWindowForId(ev.windowID);
1268 if (!window)
1269 {
1270 /* RAIL windows aren't in _windows; handle their events here. */
1271 if (_rail.enabled() && _rail.ownsWindow(ev.windowID))
1272 {
1273 switch (ev.type)
1274 {
1275 case SDL_EVENT_WINDOW_MOUSE_ENTER:
1276 /* Re-enter fires on move-grab end. */
1277 if (!(SDL_GetGlobalMouseState(nullptr, nullptr) & SDL_BUTTON_LMASK))
1278 _rail.completeLocalMoveIfPending(); /* X11 */
1279 _rail.completeWaylandResize(); /* Wayland: compositor grab ended */
1280 /* Restore the cursor or the pointer stays hidden over RemoteApp windows. */
1281 return restoreCursor();
1282 case SDL_EVENT_WINDOW_MOUSE_LEAVE:
1283 /* Compositor took the pointer for the pending Wayland resize grab. */
1284 _rail.noteResizeGrab(ev.windowID);
1285 return true;
1286 case SDL_EVENT_WINDOW_EXPOSED:
1287 /* Force repaint for exposed windows; skip during local drag to avoid redundant
1288 * frames. */
1289 if (!_rail.dragActive(ev.windowID))
1290 _rail.invalidateWindow(ev.windowID);
1291 return true;
1292 case SDL_EVENT_WINDOW_MAXIMIZED:
1293 /* Maximize must complete move first. */
1294 _rail.completeLocalMoveIfPending();
1295 _rail.handleMaximized(ev.windowID);
1296 return true;
1297 case SDL_EVENT_WINDOW_MINIMIZED:
1298 _rail.handleMinimized(ev.windowID);
1299 return true;
1300 case SDL_EVENT_WINDOW_RESTORED:
1301 _rail.handleRestored(ev.windowID);
1302 return true;
1303 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
1304 /* Send SC_CLOSE, don't abort the session. */
1305 _rail.handleClose(ev.windowID);
1306 return true;
1307 case SDL_EVENT_WINDOW_FOCUS_GAINED:
1308 _rail.handleFocus(ev.windowID, true);
1309 return true;
1310 case SDL_EVENT_WINDOW_FOCUS_LOST:
1311 _rail.handleFocus(ev.windowID, false);
1312 return true;
1313 case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
1314 _rail.handleDisplayChanged(ev.windowID, static_cast<SDL_DisplayID>(ev.data1));
1315 return true;
1316 case SDL_EVENT_WINDOW_MOVED:
1317 _rail.notePopupMoved(ev.windowID);
1318 if (!_rail.dragActive(ev.windowID))
1319 _rail.syncGeometry(ev.windowID);
1320 return true;
1321 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
1322 /* No server frame during the drag: repaint synchronously each configure step;
1323 * also report the new size to catch a compositor snap/tile. */
1324 _rail.noteDragResize(ev.windowID, ev.data1, ev.data2);
1325 _rail.syncGeometry(ev.windowID);
1326 _rail.noteResizeGrab(ev.windowID);
1327 std::ignore = drawToWindows({});
1328 return true;
1329 default:
1330 break;
1331 }
1332 }
1333 return true;
1334 }
1335
1336 {
1337 const auto& r = window->rect();
1338 const auto& b = window->bounds();
1339 const auto& scale = window->scale();
1340 const auto& orientation = window->orientation();
1341 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
1342 "%s: [%u] %dx%d-%dx%d {%dx%d-%dx%d}{scale=%f,orientation=%s}",
1343 sdl::utils::toString(ev.type).c_str(), ev.windowID, r.x, r.y, r.w, r.h, b.x,
1344 b.y, b.w, b.h, static_cast<double>(scale),
1345 sdl::utils::toString(orientation).c_str());
1346 }
1347
1348 switch (ev.type)
1349 {
1350 case SDL_EVENT_WINDOW_MOUSE_ENTER:
1351 return restoreCursor();
1352 case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
1353 if (!resizeToScale(window))
1354 return false;
1355 if (isConnected())
1356 {
1357 if (!window->fill())
1358 return false;
1359 if (!drawToWindow(*window))
1360 return false;
1361 if (!restoreCursor())
1362 return false;
1363 }
1364 break;
1365 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
1366 if (!resizeToScale(window))
1367 return false;
1368 if (!window->fill())
1369 return false;
1370 if (!drawToWindow(*window))
1371 return false;
1372 if (!restoreCursor())
1373 return false;
1374 break;
1375 case SDL_EVENT_WINDOW_MOVED:
1376 {
1377 auto r = window->rect();
1378 auto id = window->id();
1379 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%u: %dx%d-%dx%d", id, r.x, r.y, r.w, r.h);
1380 }
1381 break;
1382 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
1383 {
1384 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Window closed, terminating RDP session...");
1385 freerdp_abort_connect_context(context());
1386 }
1387 break;
1388 default:
1389 break;
1390 }
1391 return true;
1392}
1393
1394bool SdlContext::handleEvent(const SDL_DisplayEvent& ev)
1395{
1396 if (!getDisplayChannelContext().handleEvent(ev))
1397 return false;
1398
1399 switch (ev.type)
1400 {
1401 case SDL_EVENT_DISPLAY_REMOVED: // Can't show details for this one...
1402 break;
1403 default:
1404 {
1405 SDL_Rect r = {};
1406 if (!SDL_GetDisplayBounds(ev.displayID, &r))
1407 return false;
1408 const auto name = SDL_GetDisplayName(ev.displayID);
1409 if (!name)
1410 return false;
1411 const auto orientation = SDL_GetCurrentDisplayOrientation(ev.displayID);
1412 const auto scale = SDL_GetDisplayContentScale(ev.displayID);
1413 const auto mode = SDL_GetCurrentDisplayMode(ev.displayID);
1414 if (!mode)
1415 return false;
1416
1417 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
1418 "%s: [%u, %s] %dx%d-%dx%d {orientation=%s, scale=%f}%s",
1419 sdl::utils::toString(ev.type).c_str(), ev.displayID, name, r.x, r.y, r.w,
1420 r.h, sdl::utils::toString(orientation).c_str(), static_cast<double>(scale),
1421 sdl::utils::toString(mode).c_str());
1422 }
1423 break;
1424 }
1425 return true;
1426}
1427
1428bool SdlContext::handleEvent(const SDL_MouseButtonEvent& ev)
1429{
1430 SDL_Event copy = {};
1431 copy.button = ev;
1432 if (_rail.enabled())
1433 {
1434 /* Suppress X11 raw button during WM op. */
1435 const bool suppress = _rail.dragActive(ev.windowID);
1436 if (ev.type == SDL_EVENT_MOUSE_BUTTON_UP)
1437 _rail.completeLocalMoveIfPending();
1438 /* Backstop for a Wayland resize whose MOUSE_ENTER completion never arrived; a fresh press
1439 * proves the old grab is over, so a no-op click grab is cancelled here too. */
1440 if (ev.type == SDL_EVENT_MOUSE_BUTTON_DOWN)
1441 _rail.completeWaylandResize(true);
1442 /* activate the clicked window before forwarding, so the click routes to it. */
1443 if (ev.type == SDL_EVENT_MOUSE_BUTTON_DOWN)
1444 _rail.ensureActive(ev.windowID);
1445 if (suppress)
1446 return true;
1447 if (ev.type == SDL_EVENT_MOUSE_BUTTON_UP)
1448 {
1449 _lastRelease = { ev.windowID, ev.timestamp };
1450 }
1451 if (_rail.translateToServer(ev.windowID, copy.button.x, copy.button.y))
1452 {
1453 /* The window may have moved since the last motion: keep the server's position. */
1454 if ((ev.windowID == _lastMotion.window) &&
1455 (fabsf(ev.x - _lastMotion.local.x) < FLT_EPSILON) &&
1456 (fabsf(ev.y - _lastMotion.local.y) < FLT_EPSILON))
1457 {
1458 copy.button.x = _lastMotion.server.x;
1459 copy.button.y = _lastMotion.server.y;
1460 }
1461 /* Record the forwarded left press: it is the anchor of any server modal move/size
1462 * loop this press starts (the loop moves the window by release - anchor). */
1463 if ((ev.type == SDL_EVENT_MOUSE_BUTTON_DOWN) && (ev.button == SDL_BUTTON_LEFT))
1464 _rail.noteLeftPress(copy.button.x, copy.button.y);
1465 return SdlTouch::handleEvent(this, copy.button);
1466 }
1467 }
1468
1469 if (!getWindowForId(ev.windowID))
1470 return true;
1471 if (!eventToPixelCoordinates(ev.windowID, copy))
1472 return true;
1473 removeLocalScaling(copy.button.x, copy.button.y);
1474 applyMonitorOffset(copy.button.windowID, copy.button.x, copy.button.y);
1475 return SdlTouch::handleEvent(this, copy.button);
1476}
1477
1478bool SdlContext::handleEvent(const SDL_TouchFingerEvent& ev)
1479{
1480 if (!getWindowForId(ev.windowID))
1481 return true;
1482 SDL_Event copy{};
1483 copy.tfinger = ev;
1484 if (!eventToPixelCoordinates(ev.windowID, copy))
1485 return true;
1486 removeLocalScaling(copy.tfinger.dx, copy.tfinger.dy);
1487 removeLocalScaling(copy.tfinger.x, copy.tfinger.y);
1488 applyMonitorOffset(copy.tfinger.windowID, copy.tfinger.x, copy.tfinger.y);
1489 return SdlTouch::handleEvent(this, copy.tfinger);
1490}
1491
1492void SdlContext::addOrUpdateDisplay(SDL_DisplayID id)
1493{
1494 auto monitor = SdlWindow::query(id, false);
1495 _displays.emplace(id, monitor);
1496
1497 /* Update actual display rectangles:
1498 *
1499 * 1. Get logical display bounds
1500 * 2. Use already known pixel width and height
1501 * 3. Iterate over each display and update the x and y offsets by adding all monitor
1502 * widths/heights from the primary
1503 */
1504 _offsets.clear();
1505 for (auto& entry : _displays)
1506 {
1507 SDL_Rect bounds{};
1508 std::ignore = SDL_GetDisplayBounds(entry.first, &bounds);
1509
1510 SDL_Rect pixel{};
1511 pixel.w = entry.second.width;
1512 pixel.h = entry.second.height;
1513 _offsets.emplace(entry.first, std::pair{ bounds, pixel });
1514 }
1515
1516 /* 1. Find primary and update all neighbors
1517 * 2. For each neighbor update all neighbors
1518 * 3. repeat until all displays updated.
1519 */
1520 const auto primary = SDL_GetPrimaryDisplay();
1521 /* Align display offsets to the primary display origin. */
1522 auto it = _offsets.find(primary);
1523 if (it != _offsets.end())
1524 {
1525 it->second.second.x = it->second.first.x;
1526 it->second.second.y = it->second.first.y;
1527 }
1528 std::vector<SDL_DisplayID> handled;
1529 handled.push_back(primary);
1530
1531 auto neighbors = updateDisplayOffsetsForNeighbours(primary);
1532 while (!neighbors.empty())
1533 {
1534 auto neighbor = neighbors.front();
1535 neighbors.erase(neighbors.begin());
1536
1537 if (std::find(handled.begin(), handled.end(), neighbor) != handled.end())
1538 continue;
1539 handled.push_back(neighbor);
1540
1541 auto next = updateDisplayOffsetsForNeighbours(neighbor, handled);
1542 neighbors.insert(neighbors.end(), next.begin(), next.end());
1543 }
1544 updateMonitorDataFromOffsets();
1545}
1546
1547void SdlContext::deleteDisplay(SDL_DisplayID id)
1548{
1549 _displays.erase(id);
1550}
1551
1552bool SdlContext::eventToPixelCoordinates(SDL_WindowID id, SDL_Event& ev)
1553{
1554 auto w = getWindowForId(id);
1555 if (!w)
1556 return false;
1557
1558 /* Ignore errors here, sometimes SDL has no renderer */
1559 auto renderer = w->renderer();
1560 if (!renderer)
1561 return true;
1562 return SDL_ConvertEventToRenderCoordinates(renderer, &ev);
1563}
1564
1565SDL_FPoint SdlContext::applyLocalScaling(const SDL_FPoint& val) const
1566{
1567 if (!useLocalScale())
1568 return val;
1569
1570 auto rval = val;
1571 rval.x *= _localScale.x;
1572 rval.y *= _localScale.y;
1573 return rval;
1574}
1575
1576void SdlContext::removeLocalScaling(float& x, float& y) const
1577{
1578 if (!useLocalScale())
1579 return;
1580 x /= _localScale.x;
1581 y /= _localScale.y;
1582}
1583
1584SDL_FPoint SdlContext::screenToPixel(SDL_WindowID id, const SDL_FPoint& pos)
1585{
1586 auto w = getWindowForId(id);
1587 if (!w)
1588 {
1589 /* RAIL windows are server-authoritative 1:1 (no local scaling) and not in _windows. */
1590 if (_rail.enabled() && _rail.ownsWindow(id))
1591 return pos;
1592 return {};
1593 }
1594
1595 /* Ignore errors here, sometimes SDL has no renderer */
1596 auto renderer = w->renderer();
1597 if (!renderer)
1598 return pos;
1599
1600 SDL_FPoint rpos{};
1601 if (!SDL_RenderCoordinatesFromWindow(renderer, pos.x, pos.y, &rpos.x, &rpos.y))
1602 return {};
1603 removeLocalScaling(rpos.x, rpos.y);
1604 return rpos;
1605}
1606
1607SDL_FPoint SdlContext::pixelToScreen(SDL_WindowID id, const SDL_FPoint& pos)
1608{
1609 auto w = getWindowForId(id);
1610 if (!w)
1611 {
1612 if (_rail.enabled() && _rail.ownsWindow(id))
1613 return pos;
1614 return {};
1615 }
1616
1617 /* Ignore errors here, sometimes SDL has no renderer */
1618 auto renderer = w->renderer();
1619 if (!renderer)
1620 return pos;
1621
1622 SDL_FPoint rpos{};
1623 if (!SDL_RenderCoordinatesToWindow(renderer, pos.x, pos.y, &rpos.x, &rpos.y))
1624 return {};
1625 return applyLocalScaling(rpos);
1626}
1627
1628SDL_FRect SdlContext::pixelToScreen(SDL_WindowID id, const SDL_FRect& pos, bool round)
1629{
1630 const auto fpos = pixelToScreen(id, SDL_FPoint{ pos.x, pos.y });
1631 const auto size = pixelToScreen(id, SDL_FPoint{ pos.w, pos.h });
1632 SDL_FRect r{ fpos.x, fpos.y, size.x, size.y };
1633 if (round)
1634 {
1635 r.w = std::ceil(r.w);
1636 r.h = std::ceil(r.h);
1637 r.x = std::floor(r.x);
1638 r.y = std::floor(r.y);
1639 }
1640 return r;
1641}
1642
1643bool SdlContext::handleEvent(const SDL_Event& ev)
1644{
1645 if ((ev.type >= SDL_EVENT_DISPLAY_FIRST) && (ev.type <= SDL_EVENT_DISPLAY_LAST))
1646 {
1647 const auto& dev = ev.display;
1648 return handleEvent(dev);
1649 }
1650 if ((ev.type >= SDL_EVENT_WINDOW_FIRST) && (ev.type <= SDL_EVENT_WINDOW_LAST))
1651 {
1652 const auto& wev = ev.window;
1653 return handleEvent(wev);
1654 }
1655 switch (ev.type)
1656 {
1657 case SDL_EVENT_RENDER_TARGETS_RESET:
1658 case SDL_EVENT_RENDER_DEVICE_RESET:
1659 case SDL_EVENT_WILL_ENTER_FOREGROUND:
1660 return redraw();
1661 default:
1662 break;
1663 }
1664
1665 if (!isConnected())
1666 return true;
1667
1668 switch (ev.type)
1669 {
1670 case SDL_EVENT_FINGER_DOWN:
1671 case SDL_EVENT_FINGER_UP:
1672 case SDL_EVENT_FINGER_MOTION:
1673 {
1674 const auto& cev = ev.tfinger;
1675 return handleEvent(cev);
1676 }
1677 case SDL_EVENT_MOUSE_MOTION:
1678
1679 {
1680 const auto& cev = ev.motion;
1681 return handleEvent(cev);
1682 }
1683 case SDL_EVENT_MOUSE_BUTTON_DOWN:
1684 case SDL_EVENT_MOUSE_BUTTON_UP:
1685 {
1686 const auto& cev = ev.button;
1687 return handleEvent(cev);
1688 }
1689 case SDL_EVENT_MOUSE_WHEEL:
1690 {
1691 const auto& cev = ev.wheel;
1692 return handleEvent(cev);
1693 }
1694 case SDL_EVENT_CLIPBOARD_UPDATE:
1695 {
1696 const auto& cev = ev.clipboard;
1697 return getClipboardChannelContext().handleEvent(cev);
1698 }
1699 case SDL_EVENT_KEY_DOWN:
1700 case SDL_EVENT_KEY_UP:
1701 {
1702 const auto& cev = ev.key;
1703 return getInputChannelContext().handleEvent(cev);
1704 }
1705 default:
1706 return true;
1707 }
1708}
1709
1710COMMAND_LINE_ARGUMENT_A* SdlContext::args()
1711{
1712 return _args.data();
1713}
1714
1715size_t SdlContext::argsCount() const
1716{
1717 if (_args.size() <= 1)
1718 return 0;
1719 return _args.size() - 1;
1720}
1721
1722int SdlContext::argumentHandler(const COMMAND_LINE_ARGUMENT_A* arg, void* custom)
1723{
1724 auto sdl = static_cast<SdlContext*>(custom);
1725 if (!sdl)
1726 return -1;
1727
1728 if (arg->Name)
1729 {
1730 if (strcmp(arg->Name, sdl_allow_screensaver) == 0)
1731 {
1732 if (arg->Value != nullptr)
1733 {
1734 if (!SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"))
1735 {
1736 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
1737 "SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER) failed with %s",
1738 SDL_GetError());
1739 return -2;
1740 }
1741 }
1742 }
1743 else if (strcmp(arg->Name, sdl_connection_dialog) == 0)
1744 sdl->_connectionDialog = (arg->Value != nullptr);
1745 }
1746 return 0;
1747}
1748
1749CriticalSection& SdlContext::lock()
1750{
1751 return _critical;
1752}
1753
1754std::vector<rdpPointer*>& SdlContext::pointers()
1755{
1756 return _valid_pointers;
1757}
1758
1759bool SdlContext::contains(const rdpPointer* ptr) const
1760{
1761 for (const auto& cur : _valid_pointers)
1762 {
1763 if (cur == ptr)
1764 return true;
1765 }
1766 return false;
1767}
1768
1769bool SdlContext::credentialsRead() const
1770{
1771 return _credentialsRead;
1772}
1773
1774void SdlContext::setCredentialsRead()
1775{
1776 _credentialsRead = true;
1777}
1778
1779bool SdlContext::resizeToScale(SdlWindow* window)
1780{
1781 if (freerdp_settings_get_bool(context()->settings, FreeRDP_SmartSizing))
1782 return true;
1783 if (!useLocalScale())
1784 return true;
1785 if (!window)
1786 return false;
1787 return window->resizeToScale();
1788}
1789
1790bool SdlContext::useLocalScale() const
1791{
1792 const auto ssize = freerdp_settings_get_bool(context()->settings, FreeRDP_SmartSizing);
1793 if (ssize)
1794 return true;
1795 const auto dynResize =
1796 freerdp_settings_get_bool(context()->settings, FreeRDP_DynamicResolutionUpdate);
1797 const auto fs = freerdp_settings_get_bool(context()->settings, FreeRDP_Fullscreen);
1798 const auto multimon = freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon);
1799 return !dynResize && !fs && !multimon;
1800}
1801
1802bool SdlContext::drawToWindows(const std::vector<SDL_Rect>& rects)
1803{
1804 /* RAIL damage is per-window (_gfxDamage), not in the rects queue: repaint every tick. */
1805 if (_rail.enabled())
1806 {
1807 for (auto& window : _windows)
1808 {
1809 if ((SDL_GetWindowFlags(window.second.window()) & SDL_WINDOW_HIDDEN) == 0)
1810 SDL_HideWindow(window.second.window());
1811 }
1812
1813 std::unique_lock lock(_critical);
1814 _rail.paint(_primary.get(), pixelFormat(), rects);
1815 return true;
1816 }
1817
1818 /* Non-RAIL mode (e.g. Non-Monitored Desktop during UAC / Consent UI): show the desktop window.
1819 * Before the RemoteApp starts, only when the logon needs the user. */
1820 if (freerdp_settings_get_bool(context()->settings, FreeRDP_RemoteApplicationMode) &&
1821 !_rail.desktopMayShow())
1822 return true;
1823 bool firstShow = false;
1824 for (auto& window : _windows)
1825 {
1826 if (SDL_GetWindowFlags(window.second.window()) & SDL_WINDOW_HIDDEN)
1827 {
1828 SDL_ShowWindow(window.second.window());
1829 SDL_RaiseWindow(window.second.window());
1830 firstShow = true;
1831 }
1832 }
1833
1834 if (rects.empty() && !firstShow)
1835 return true;
1836
1837 std::vector<SDL_Rect> drawRects = rects;
1838 {
1839 std::unique_lock lock(_critical);
1840 if ((drawRects.empty() || firstShow) && _primary)
1841 drawRects = { { 0, 0, _primary->w, _primary->h } };
1842 }
1843
1844 for (auto& window : _windows)
1845 {
1846 if (!drawToWindow(window.second, drawRects))
1847 return false;
1848 }
1849
1850 return true;
1851}
1852
1853BOOL SdlContext::desktopResize(rdpContext* context)
1854{
1855 rdpGdi* gdi = nullptr;
1856 rdpSettings* settings = nullptr;
1857 auto sdl = get_context(context);
1858
1859 WINPR_ASSERT(sdl);
1860 WINPR_ASSERT(context);
1861
1862 settings = context->settings;
1863 WINPR_ASSERT(settings);
1864
1865 std::unique_lock lock(sdl->_critical);
1866 gdi = context->gdi;
1867 if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
1868 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
1869 return FALSE;
1870 return sdl->createPrimary();
1871}
1872
1873/* This function is called to output a System BEEP */
1874BOOL SdlContext::playSound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound)
1875{
1876 /* TODO: Implement */
1877 WINPR_UNUSED(context);
1878 WINPR_UNUSED(play_sound);
1879 return TRUE;
1880}
1881
1882/* This function is called whenever a new frame starts.
1883 * It can be used to reset invalidated areas. */
1884BOOL SdlContext::beginPaint(rdpContext* context)
1885{
1886 auto gdi = context->gdi;
1887 WINPR_ASSERT(gdi);
1888 WINPR_ASSERT(gdi->primary);
1889
1890 HGDI_DC hdc = gdi->primary->hdc;
1891 WINPR_ASSERT(hdc);
1892 if (!hdc->hwnd)
1893 return TRUE;
1894
1895 HGDI_WND hwnd = hdc->hwnd;
1896 WINPR_ASSERT(hwnd->invalid);
1897 hwnd->invalid->null = TRUE;
1898 hwnd->ninvalid = 0;
1899
1900 return TRUE;
1901}
1902
1903bool SdlContext::redraw(bool suppress) const
1904{
1905 if (!_connected)
1906 return true;
1907
1908 /* RemoteApp desktop windows are placeholders, hiding one must not stop server output. */
1909 if (suppress && freerdp_settings_get_bool(context()->settings, FreeRDP_RemoteApplicationMode))
1910 return true;
1911
1912 auto gdi = context()->gdi;
1913 WINPR_ASSERT(gdi);
1914 return gdi_send_suppress_output(gdi, suppress);
1915}
1916
1917void SdlContext::setConnected(bool val)
1918{
1919 _connected = val;
1920}
1921
1922bool SdlContext::isConnected() const
1923{
1924 return _connected;
1925}
1926
1927rdpContext* SdlContext::context() const
1928{
1929 WINPR_ASSERT(_context);
1930 return _context;
1931}
1932
1933rdpClientContext* SdlContext::common() const
1934{
1935 return reinterpret_cast<rdpClientContext*>(context());
1936}
1937
1938bool SdlContext::setCursor(CursorType type)
1939{
1940 _cursorType = type;
1941 return restoreCursor();
1942}
1943
1944bool SdlContext::setCursor(const rdpPointer* cursor)
1945{
1946 std::unique_lock lock(_critical);
1947 if (!contains(cursor))
1948 return true;
1949
1950 _cursor = { sdl_Pointer_Copy(cursor), sdl_PointerFreeCopyAll };
1951 return setCursor(CURSOR_IMAGE);
1952}
1953
1954rdpPointer* SdlContext::cursor() const
1955{
1956 return _cursor.get();
1957}
1958
1959bool SdlContext::restoreCursor()
1960{
1961 WLog_Print(getWLog(), WLOG_TRACE, "restore cursor: %d", _cursorType);
1962 switch (_cursorType)
1963 {
1964 case CURSOR_NULL:
1965 if (!SDL_HideCursor())
1966 {
1967 WLog_Print(getWLog(), WLOG_ERROR, "SDL_HideCursor failed");
1968 return false;
1969 }
1970
1971 setHasCursor(false);
1972 return true;
1973
1974 case CURSOR_DEFAULT:
1975 {
1976 auto def = SDL_GetDefaultCursor();
1977 if (!SDL_SetCursor(def))
1978 {
1979 WLog_Print(getWLog(), WLOG_ERROR, "SDL_SetCursor(default=%p) failed",
1980 static_cast<void*>(def));
1981 return false;
1982 }
1983 if (!SDL_ShowCursor())
1984 {
1985 WLog_Print(getWLog(), WLOG_ERROR, "SDL_ShowCursor failed");
1986 return false;
1987 }
1988 setHasCursor(true);
1989 return true;
1990 }
1991 case CURSOR_IMAGE:
1992 setHasCursor(true);
1993 return sdl_Pointer_Set_Process(this);
1994 default:
1995 WLog_Print(getWLog(), WLOG_ERROR, "Unknown cursorType %s",
1996 sdl::utils::toString(_cursorType).c_str());
1997 return false;
1998 }
1999}
2000
2001void SdlContext::setMonitorIds(const std::vector<SDL_DisplayID>& ids)
2002{
2003 _monitorIds.clear();
2004 for (auto id : ids)
2005 {
2006 _monitorIds.push_back(id);
2007 }
2008}
2009
2010const std::vector<SDL_DisplayID>& SdlContext::monitorIds() const
2011{
2012 return _monitorIds;
2013}
2014
2015int64_t SdlContext::monitorId(uint32_t index) const
2016{
2017 if (index >= _monitorIds.size())
2018 {
2019 return -1;
2020 }
2021 return _monitorIds.at(index);
2022}
2023
2024void SdlContext::push(std::vector<SDL_Rect>&& rects)
2025{
2026 std::unique_lock lock(_queue_mux);
2027 _queue.emplace(std::move(rects));
2028}
2029
2030std::vector<SDL_Rect> SdlContext::pop()
2031{
2032 std::unique_lock lock(_queue_mux);
2033 if (_queue.empty())
2034 {
2035 return {};
2036 }
2037 auto val = std::move(_queue.front());
2038 _queue.pop();
2039 return val;
2040}
2041
2042bool SdlContext::setFullscreen(bool enter, bool forceOriginalDisplay)
2043{
2044 for (const auto& window : _windows)
2045 {
2046 if (!sdl_push_user_event(SDL_EVENT_USER_WINDOW_FULLSCREEN, &window.second, enter,
2047 forceOriginalDisplay))
2048 return false;
2049 }
2050 _fullscreen = enter;
2051 return true;
2052}
2053
2054bool SdlContext::setMinimized()
2055{
2056 return sdl_push_user_event(SDL_EVENT_USER_WINDOW_MINIMIZE);
2057}
2058
2059bool SdlContext::grabMouse() const
2060{
2061 return _grabMouse;
2062}
2063
2064bool SdlContext::toggleGrabMouse()
2065{
2066 return setGrabMouse(!grabMouse());
2067}
2068
2069bool SdlContext::setGrabMouse(bool enter)
2070{
2071 _grabMouse = enter;
2072 return true;
2073}
2074
2075bool SdlContext::grabKeyboard() const
2076{
2077 return _grabKeyboard;
2078}
2079
2080bool SdlContext::toggleGrabKeyboard()
2081{
2082 return setGrabKeyboard(!grabKeyboard());
2083}
2084
2085bool SdlContext::setGrabKeyboard(bool enter)
2086{
2087 _grabKeyboard = enter;
2088 return true;
2089}
2090
2091bool SdlContext::setResizeable(bool enable)
2092{
2093 const auto settings = context()->settings;
2094 const bool dyn = freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate);
2095 const bool smart = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing);
2096 bool use = (dyn && enable) || smart;
2097
2098 for (const auto& window : _windows)
2099 {
2100 if (!sdl_push_user_event(SDL_EVENT_USER_WINDOW_RESIZEABLE, &window.second, use))
2101 return false;
2102 }
2103 _resizeable = use;
2104
2105 return true;
2106}
2107
2108bool SdlContext::resizeable() const
2109{
2110 return _resizeable;
2111}
2112
2113bool SdlContext::toggleResizeable()
2114{
2115 return setResizeable(!resizeable());
2116}
2117
2118bool SdlContext::fullscreen() const
2119{
2120 return _fullscreen;
2121}
2122
2123bool SdlContext::toggleFullscreen()
2124{
2125 return setFullscreen(!fullscreen());
2126}
object that handles clipboard context for the SDL3 client
Definition sdl_clip.hpp:77
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_server_name(const rdpSettings *settings)
A helper function to return the correct server name.
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_bool(rdpSettings *settings, FreeRDP_Settings_Keys_Bool id, BOOL val)
Sets a BOOL settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_uint32(rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 val)
Sets a UINT32 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_set_monitor_def_array_sorted(rdpSettings *settings, const rdpMonitor *monitors, size_t count)
Sort monitor array according to:
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.