FreeRDP
Loading...
Searching...
No Matches
sdl_context.cpp
1
20#include <algorithm>
21#include <cmath>
22
23#include "sdl_context.hpp"
24#include "sdl_config.hpp"
25#include "sdl_channels.hpp"
26#include "sdl_monitor.hpp"
27#include "sdl_pointer.hpp"
28#include "sdl_touch.hpp"
29
30#include <sdl_common_utils.hpp>
31#include <scoped_guard.hpp>
32
33#include "dialogs/sdl_dialogs.hpp"
34
35#if defined(WITH_WEBVIEW)
36#include <aad/sdl_webview.hpp>
37#endif
38
39static constexpr auto sdl_allow_screensaver = "sdl-allow-screensaver";
40
41SdlContext::SdlContext(rdpContext* context)
42 : _context(context), _log(WLog_Get(CLIENT_TAG("SDL"))), _cursor(nullptr, sdl_Pointer_FreeCopy),
43 _rdpThreadRunning(false), _primary(nullptr, SDL_DestroySurface), _disp(this), _input(this),
44 _clip(this), _dialog(_log)
45{
46 WINPR_ASSERT(context);
47 setMetadata();
48
49 auto instance = _context->instance;
50 WINPR_ASSERT(instance);
51
52 instance->PreConnect = preConnect;
53 instance->PostConnect = postConnect;
54 instance->PostDisconnect = postDisconnect;
55 instance->PostFinalDisconnect = postFinalDisconnect;
56 instance->AuthenticateEx = sdl_authenticate_ex;
57 instance->VerifyCertificateEx = sdl_verify_certificate_ex;
58 instance->VerifyChangedCertificateEx = sdl_verify_changed_certificate_ex;
59 instance->LogonErrorInfo = sdl_logon_error_info;
60 instance->PresentGatewayMessage = sdl_present_gateway_message;
61 instance->ChooseSmartcard = sdl_choose_smartcard;
62 instance->RetryDialog = sdl_retry_dialog;
63
64#ifdef WITH_WEBVIEW
65 instance->GetAccessToken = sdl_webview_get_access_token;
66#else
67 instance->GetAccessToken = client_cli_get_access_token;
68#endif
69 /* TODO: Client display set up */
70
71 _args.push_back({ sdl_allow_screensaver, COMMAND_LINE_VALUE_BOOL, nullptr, BoolValueFalse,
72 nullptr, -1, nullptr, "Allow local screensaver to activate" });
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->_windowHeigth = 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 /* TODO: Any code your client requires */
244 return TRUE;
245}
246
247/* Called after a RDP connection was successfully established.
248 * Settings might have changed during negotiation of client / server feature
249 * support.
250 *
251 * Set up local framebuffers and paing callbacks.
252 * If required, register pointer callbacks to change the local mouse cursor
253 * when hovering over the RDP window
254 */
255BOOL SdlContext::postConnect(freerdp* instance)
256{
257 WINPR_ASSERT(instance);
258
259 auto context = instance->context;
260 WINPR_ASSERT(context);
261
262 auto sdl = get_context(context);
263
264 // Retry was successful, discard dialog
265 sdl->getDialog().show(false);
266
267 if (freerdp_settings_get_bool(context->settings, FreeRDP_AuthenticationOnly))
268 {
269 /* Check +auth-only has a username and password. */
270 if (!freerdp_settings_get_string(context->settings, FreeRDP_Password))
271 {
272 WLog_Print(sdl->getWLog(), WLOG_INFO,
273 "auth-only, but no password set. Please provide one.");
274 return FALSE;
275 }
276
277 WLog_Print(sdl->getWLog(), WLOG_INFO, "Authentication only. Don't connect to X.");
278 return TRUE;
279 }
280
281 if (!sdl->waitForWindowsCreated())
282 return FALSE;
283
284 sdl->_sdlPixelFormat = SDL_PIXELFORMAT_BGRA32;
285 if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
286 return FALSE;
287
288 if (!sdl->createPrimary())
289 return FALSE;
290
291 if (!sdl_register_pointer(instance->context->graphics))
292 return FALSE;
293
294 WINPR_ASSERT(context->update);
295
296 context->update->BeginPaint = beginPaint;
297 context->update->EndPaint = endPaint;
298 context->update->PlaySound = playSound;
299 context->update->DesktopResize = desktopResize;
300 context->update->SetKeyboardIndicators = sdlInput::keyboard_set_indicators;
301 context->update->SetKeyboardImeStatus = sdlInput::keyboard_set_ime_status;
302
303 if (!sdl->setResizeable(false))
304 return FALSE;
305 if (!sdl->setFullscreen(freerdp_settings_get_bool(context->settings, FreeRDP_Fullscreen) ||
306 freerdp_settings_get_bool(context->settings, FreeRDP_UseMultimon),
307 true))
308 return FALSE;
309 sdl->setConnected(true);
310 return TRUE;
311}
312
313/* This function is called whether a session ends by failure or success.
314 * Clean up everything allocated by pre_connect and post_connect.
315 */
316void SdlContext::postDisconnect(freerdp* instance)
317{
318 if (!instance)
319 return;
320
321 if (!instance->context)
322 return;
323
324 auto sdl = get_context(instance->context);
325 sdl->setConnected(false);
326
327 gdi_free(instance);
328}
329
330void SdlContext::postFinalDisconnect(freerdp* instance)
331{
332 if (!instance)
333 return;
334
335 if (!instance->context)
336 return;
337
338 PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
339 sdl_OnChannelConnectedEventHandler);
340 PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub,
341 sdl_OnChannelDisconnectedEventHandler);
342 PubSub_UnsubscribeUserNotification(instance->context->pubSub,
343 sdl_OnUserNotificationEventHandler);
344}
345
346/* Create a SDL surface from the GDI buffer */
347bool SdlContext::createPrimary()
348{
349 auto gdi = context()->gdi;
350 WINPR_ASSERT(gdi);
351
352 _primary = SDLSurfacePtr(
353 SDL_CreateSurfaceFrom(static_cast<int>(gdi->width), static_cast<int>(gdi->height),
354 pixelFormat(), gdi->primary_buffer, static_cast<int>(gdi->stride)),
355 SDL_DestroySurface);
356 if (!_primary)
357 return false;
358
359 SDL_SetSurfaceBlendMode(_primary.get(), SDL_BLENDMODE_NONE);
360 SDL_Rect surfaceRect = { 0, 0, gdi->width, gdi->height };
361 SDL_FillSurfaceRect(_primary.get(), &surfaceRect,
362 SDL_MapSurfaceRGBA(_primary.get(), 0, 0, 0, 0xff));
363
364 return true;
365}
366
367bool SdlContext::createWindows()
368{
369 auto settings = context()->settings;
370 const auto& title = windowTitle();
371
372 ScopeGuard guard1([&]() { _windowsCreatedEvent.set(); });
373
374 UINT32 windowCount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
375
376 Sint32 originX = 0;
377 Sint32 originY = 0;
378 for (UINT32 x = 0; x < windowCount; x++)
379 {
380 auto id = monitorId(x);
381 if (id < 0)
382 return false;
383
384 auto monitor = static_cast<rdpMonitor*>(
385 freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x));
386
387 originX = std::min<Sint32>(monitor->x, originX);
388 originY = std::min<Sint32>(monitor->y, originY);
389 }
390
391 for (UINT32 x = 0; x < windowCount; x++)
392 {
393 auto id = monitorId(x);
394 if (id < 0)
395 return false;
396
397 auto monitor = static_cast<rdpMonitor*>(
398 freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x));
399
400 Uint32 w = WINPR_ASSERTING_INT_CAST(Uint32, monitor->width);
401 Uint32 h = WINPR_ASSERTING_INT_CAST(Uint32, monitor->height);
402 if (!(freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) ||
403 freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)))
404 {
405 if (_windowWidth > 0)
406 w = _windowWidth;
407 else
408 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
409
410 if (_windowHeigth > 0)
411 h = _windowHeigth;
412 else
413 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
414 }
415
416 Uint32 flags = SDL_WINDOW_HIGH_PIXEL_DENSITY;
417
418 if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) &&
419 !freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
420 {
421 flags |= SDL_WINDOW_FULLSCREEN;
422 }
423
424 if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
425 {
426 flags |= SDL_WINDOW_BORDERLESS;
427 }
428
429 if (!freerdp_settings_get_bool(settings, FreeRDP_Decorations))
430 flags |= SDL_WINDOW_BORDERLESS;
431
432 auto did = WINPR_ASSERTING_INT_CAST(SDL_DisplayID, id);
433 auto window = SdlWindow::create(did, title, flags, w, h);
434
435 if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
436 {
437 window.setOffsetX(originX - monitor->x);
438 window.setOffsetY(originY - monitor->y);
439 }
440
441 _windows.insert({ window.id(), std::move(window) });
442 }
443
444 return true;
445}
446
447bool SdlContext::updateWindowList()
448{
449 std::vector<rdpMonitor> list;
450 list.reserve(_windows.size());
451 for (const auto& win : _windows)
452 list.push_back(win.second.monitor(_windows.size() == 1));
453
454 // /monitors: subset may exclude the SDL primary. The library requires
455 // the array to mark one monitor as primary, so promote the first when
456 // none of the kept windows cover the original primary.
457 if (!list.empty() &&
458 std::none_of(list.cbegin(), list.cend(), [](const rdpMonitor& m) { return m.is_primary; }))
459 list.at(0).is_primary = true;
460
461 return freerdp_settings_set_monitor_def_array_sorted(context()->settings, list.data(),
462 list.size());
463}
464
465bool SdlContext::updateWindow(SDL_WindowID id)
466{
467 if (freerdp_settings_get_bool(_context->settings, FreeRDP_Fullscreen) ||
468 freerdp_settings_get_bool(_context->settings, FreeRDP_UseMultimon))
469 return true;
470
471 auto& w = _windows.at(id);
472 auto m = w.monitor(true);
473 auto r = w.rect();
474 m.width = r.w;
475 m.height = r.h;
476 m.attributes.physicalWidth = static_cast<UINT32>(r.w);
477 m.attributes.physicalHeight = static_cast<UINT32>(r.h);
478 w.setMonitor(m);
479 return true;
480}
481
482std::string SdlContext::windowTitle() const
483{
484 const char* prefix = "FreeRDP:";
485
486 const auto windowTitle = freerdp_settings_get_string(context()->settings, FreeRDP_WindowTitle);
487 if (windowTitle)
488 return windowTitle;
489
490 const auto name = freerdp_settings_get_server_name(context()->settings);
491 const auto port = freerdp_settings_get_uint32(context()->settings, FreeRDP_ServerPort);
492 const auto addPort = (port != 3389);
493
494 std::stringstream ss;
495 ss << prefix << " " << name;
496
497 if (addPort)
498 ss << ":" << port;
499
500 return ss.str();
501}
502
503bool SdlContext::waitForWindowsCreated()
504{
505 {
506 std::unique_lock<CriticalSection> lock(_critical);
507 _windowsCreatedEvent.clear();
508 if (!sdl_push_user_event(SDL_EVENT_USER_CREATE_WINDOWS, this))
509 return false;
510 }
511
512 HANDLE handles[] = { _windowsCreatedEvent.handle(), freerdp_abort_event(context()) };
513
514 const DWORD rc = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
515 switch (rc)
516 {
517 case WAIT_OBJECT_0:
518 return true;
519 default:
520 return false;
521 }
522}
523
524/* This function is called when the library completed composing a new
525 * frame. Read out the changed areas and blit them to your output device.
526 * The image buffer will have the format specified by gdi_init
527 */
528BOOL SdlContext::endPaint(rdpContext* context)
529{
530 auto sdl = get_context(context);
531 WINPR_ASSERT(sdl);
532
533 auto gdi = context->gdi;
534 WINPR_ASSERT(gdi);
535 WINPR_ASSERT(gdi->primary);
536
537 HGDI_DC hdc = gdi->primary->hdc;
538 WINPR_ASSERT(hdc);
539 if (!hdc->hwnd)
540 return TRUE;
541
542 HGDI_WND hwnd = hdc->hwnd;
543 WINPR_ASSERT(hwnd->invalid || (hwnd->ninvalid == 0));
544
545 if (hwnd->invalid->null)
546 return TRUE;
547
548 WINPR_ASSERT(hwnd->invalid);
549 if (gdi->suppressOutput || hwnd->invalid->null)
550 return TRUE;
551
552 const INT32 ninvalid = hwnd->ninvalid;
553 const GDI_RGN* cinvalid = hwnd->cinvalid;
554
555 if (ninvalid < 1)
556 return TRUE;
557
558 std::vector<SDL_Rect> rects;
559 for (INT32 x = 0; x < ninvalid; x++)
560 {
561 auto& rgn = cinvalid[x];
562 rects.push_back({ rgn.x, rgn.y, rgn.w, rgn.h });
563 }
564
565 sdl->push(std::move(rects));
566 return sdl_push_user_event(SDL_EVENT_USER_UPDATE);
567}
568
569void SdlContext::sdl_client_cleanup(int exit_code, const std::string& error_msg)
570{
571 rdpSettings* settings = context()->settings;
572 WINPR_ASSERT(settings);
573
574 _rdpThreadRunning = false;
575 bool showError = false;
576 if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
577 WLog_Print(getWLog(), WLOG_INFO, "Authentication only, exit status %s [%" PRId32 "]",
578 sdl::error::exitCodeToTag(exit_code), exit_code);
579 else
580 {
581 switch (exit_code)
582 {
583 case sdl::error::SUCCESS:
584 case sdl::error::DISCONNECT:
585 case sdl::error::LOGOFF:
586 case sdl::error::DISCONNECT_BY_USER:
587 case sdl::error::CONNECT_CANCELLED:
588 break;
589 default:
590 {
591 getDialog().showError(error_msg);
592 }
593 break;
594 }
595 }
596
597 if (!showError)
598 getDialog().show(false);
599
600 _exitCode = exit_code;
601 std::ignore = sdl_push_user_event(SDL_EVENT_USER_QUIT);
602 SDL_CleanupTLS();
603}
604
605int SdlContext::sdl_client_thread_connect(std::string& error_msg)
606{
607 auto instance = context()->instance;
608 WINPR_ASSERT(instance);
609
610 _rdpThreadRunning = true;
611 BOOL rc = freerdp_connect(instance);
612
613 rdpSettings* settings = context()->settings;
614 WINPR_ASSERT(settings);
615
616 int exit_code = sdl::error::SUCCESS;
617 if (!rc)
618 {
619 UINT32 error = freerdp_get_last_error(context());
620 exit_code = sdl::error::errorToExitCode(error);
621 }
622
623 if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
624 {
625 DWORD code = freerdp_get_last_error(context());
626 freerdp_abort_connect_context(context());
627 WLog_Print(getWLog(), WLOG_ERROR, "Authentication only, %s [0x%08" PRIx32 "] %s",
628 freerdp_get_last_error_name(code), code, freerdp_get_last_error_string(code));
629 return exit_code;
630 }
631
632 if (!rc)
633 {
634 DWORD code = freerdp_error_info(instance);
635 if (exit_code == sdl::error::SUCCESS)
636 {
637 char* msg = nullptr;
638 size_t len = 0;
639 exit_code = error_info_to_error(&code, &msg, &len);
640 if (msg)
641 error_msg = msg;
642 free(msg);
643 }
644
645 auto last = freerdp_get_last_error(context());
646 if (error_msg.empty())
647 {
648 char* msg = nullptr;
649 size_t len = 0;
650 winpr_asprintf(&msg, &len, "%s [0x%08" PRIx32 "]\n%s",
651 freerdp_get_last_error_name(last), last,
652 freerdp_get_last_error_string(last));
653 if (msg)
654 error_msg = msg;
655 free(msg);
656 }
657
658 if (exit_code == sdl::error::SUCCESS)
659 {
660 if (last == FREERDP_ERROR_AUTHENTICATION_FAILED)
661 exit_code = sdl::error::AUTH_FAILURE;
662 else if (code == ERRINFO_SUCCESS)
663 exit_code = sdl::error::CONN_FAILED;
664 }
665
666 getDialog().show(false);
667 }
668
669 return exit_code;
670}
671
672int SdlContext::sdl_client_thread_run(std::string& error_msg)
673{
674 auto instance = context()->instance;
675 WINPR_ASSERT(instance);
676
677 int exit_code = sdl::error::SUCCESS;
678 while (!freerdp_shall_disconnect_context(context()))
679 {
680 HANDLE handles[MAXIMUM_WAIT_OBJECTS] = {};
681 /*
682 * win8 and server 2k12 seem to have some timing issue/race condition
683 * when a initial sync request is send to sync the keyboard indicators
684 * sending the sync event twice fixed this problem
685 */
686 if (freerdp_focus_required(instance))
687 {
688 auto ctx = get_context(context());
689 WINPR_ASSERT(ctx);
690
691 auto& input = ctx->getInputChannelContext();
692 if (!input.keyboard_focus_in())
693 break;
694 if (!input.keyboard_focus_in())
695 break;
696 }
697
698 const DWORD nCount = freerdp_get_event_handles(context(), handles, ARRAYSIZE(handles));
699
700 if (nCount == 0)
701 {
702 WLog_Print(getWLog(), WLOG_ERROR, "freerdp_get_event_handles failed");
703 break;
704 }
705
706 const DWORD status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
707
708 if (status == WAIT_FAILED)
709 {
710 WLog_Print(getWLog(), WLOG_ERROR, "WaitForMultipleObjects WAIT_FAILED");
711 break;
712 }
713
714 if (!freerdp_check_event_handles(context()))
715 {
716 if (client_auto_reconnect(instance))
717 {
718 // Retry was successful, discard dialog
719 getDialog().show(false);
720 continue;
721 }
722 else
723 {
724 /*
725 * Indicate an unsuccessful connection attempt if reconnect
726 * did not succeed and no other error was specified.
727 */
728 if (freerdp_error_info(instance) == 0)
729 exit_code = sdl::error::CONN_FAILED;
730 }
731
732 if (freerdp_get_last_error(context()) == FREERDP_ERROR_SUCCESS)
733 WLog_Print(getWLog(), WLOG_ERROR, "WaitForMultipleObjects failed with %" PRIu32 "",
734 status);
735 if (freerdp_get_last_error(context()) == FREERDP_ERROR_SUCCESS)
736 WLog_Print(getWLog(), WLOG_ERROR, "Failed to check FreeRDP event handles");
737 break;
738 }
739 }
740
741 if (exit_code == sdl::error::SUCCESS)
742 {
743 DWORD code = 0;
744 {
745 char* emsg = nullptr;
746 size_t elen = 0;
747 exit_code = error_info_to_error(&code, &emsg, &elen);
748 if (emsg)
749 error_msg = emsg;
750 free(emsg);
751 }
752
753 if ((code == ERRINFO_LOGOFF_BY_USER) &&
754 (freerdp_get_disconnect_ultimatum(context()) == Disconnect_Ultimatum_user_requested))
755 {
756 const char* msg = "Error info says user did not initiate but disconnect ultimatum says "
757 "they did; treat this as a user logoff";
758
759 char* emsg = nullptr;
760 size_t elen = 0;
761 winpr_asprintf(&emsg, &elen, "%s", msg);
762 if (emsg)
763 error_msg = emsg;
764 free(emsg);
765
766 /* This situation might be limited to Windows XP. */
767 WLog_Print(getWLog(), WLOG_INFO, "%s", msg);
768 exit_code = sdl::error::LOGOFF;
769 }
770 }
771
772 freerdp_disconnect(instance);
773
774 return exit_code;
775}
776
777/* RDP main loop.
778 * Connects RDP, loops while running and handles event and dispatch, cleans up
779 * after the connection ends. */
780DWORD SdlContext::rdpThreadRun(SdlContext* sdl)
781{
782 WINPR_ASSERT(sdl);
783
784 std::string error_msg;
785 int exit_code = sdl->sdl_client_thread_connect(error_msg);
786 if (exit_code == sdl::error::SUCCESS)
787 exit_code = sdl->sdl_client_thread_run(error_msg);
788 sdl->sdl_client_cleanup(exit_code, error_msg);
789
790 return static_cast<DWORD>(exit_code);
791}
792
793int SdlContext::error_info_to_error(DWORD* pcode, char** msg, size_t* len) const
794{
795 const DWORD code = freerdp_error_info(context()->instance);
796 const char* name = freerdp_get_error_info_name(code);
797 const char* str = freerdp_get_error_info_string(code);
798 const int exit_code = sdl::error::errorToExitCode(code);
799
800 winpr_asprintf(msg, len, "Terminate with %s due to ERROR_INFO %s [0x%08" PRIx32 "]: %s",
801 sdl::error::errorToExitCodeTag(code), name, code, str);
802 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%s", *msg);
803 if (pcode)
804 *pcode = code;
805 return exit_code;
806}
807
808void SdlContext::applyMonitorOffset(SDL_WindowID window, float& x, float& y) const
809{
810 if (!freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon))
811 return;
812
813 auto w = getWindowForId(window);
814 x -= static_cast<float>(w->offsetX());
815 y -= static_cast<float>(w->offsetY());
816}
817
818static bool alignX(const SDL_Rect& a, const SDL_Rect& b)
819{
820 if (a.x + a.w == b.x)
821 return true;
822 if (b.x + b.w == a.x)
823 return true;
824 return false;
825}
826
827static bool alignY(const SDL_Rect& a, const SDL_Rect& b)
828{
829 if (a.y + a.h == b.y)
830 return true;
831 if (b.y + b.h == a.y)
832 return true;
833 return false;
834}
835
836std::vector<SDL_DisplayID>
837SdlContext::updateDisplayOffsetsForNeighbours(SDL_DisplayID id,
838 const std::vector<SDL_DisplayID>& ignore)
839{
840 auto first = _offsets.at(id);
841 std::vector<SDL_DisplayID> neighbours;
842
843 for (auto& entry : _offsets)
844 {
845 if (entry.first == id)
846 continue;
847 if (std::find(ignore.begin(), ignore.end(), entry.first) != ignore.end())
848 continue;
849
850 bool neighbor = false;
851 if (alignX(entry.second.first, first.first))
852 {
853 if (entry.second.first.x < first.first.x)
854 entry.second.second.x = first.second.x - entry.second.second.w;
855 else
856 entry.second.second.x = first.second.x + first.second.w;
857 neighbor = true;
858 }
859 if (alignY(entry.second.first, first.first))
860 {
861 if (entry.second.first.y < first.first.y)
862 entry.second.second.y = first.second.y - entry.second.second.h;
863 else
864 entry.second.second.y = first.second.y + first.second.h;
865 neighbor = true;
866 }
867
868 if (neighbor)
869 neighbours.push_back(entry.first);
870 }
871 return neighbours;
872}
873
874void SdlContext::updateMonitorDataFromOffsets()
875{
876 for (auto& entry : _displays)
877 {
878 auto offsets = _offsets.at(entry.first);
879 entry.second.x = offsets.second.x;
880 entry.second.y = offsets.second.y;
881 }
882
883 for (auto& entry : _windows)
884 {
885 const auto& monitor = _displays.at(entry.first);
886 entry.second.setMonitor(monitor);
887 }
888}
889
890bool SdlContext::drawToWindow(SdlWindow& window, const std::vector<SDL_Rect>& rects)
891{
892 if (!isConnected())
893 return true;
894
895 auto gdi = context()->gdi;
896 WINPR_ASSERT(gdi);
897
898 auto size = window.rect();
899
900 std::unique_lock lock(_critical);
901 auto surface = _primary.get();
902
903 if (useLocalScale())
904 {
905 window.setOffsetX(0);
906 window.setOffsetY(0);
907 if (gdi->width < size.w)
908 {
909 window.setOffsetX((size.w - gdi->width) / 2);
910 }
911 if (gdi->height < size.h)
912 {
913 window.setOffsetY((size.h - gdi->height) / 2);
914 }
915
916 _localScale = { static_cast<float>(size.w) / static_cast<float>(gdi->width),
917 static_cast<float>(size.h) / static_cast<float>(gdi->height) };
918 if (!window.drawScaledRects(surface, _localScale, rects))
919 return false;
920 }
921 else
922 {
923 SDL_Point offset{ 0, 0 };
924 if (freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon))
925 offset = { window.offsetX(), window.offsetY() };
926 if (!window.drawRects(surface, offset, rects))
927 return false;
928 }
929
930 window.updateSurface();
931 return true;
932}
933
934bool SdlContext::minimizeAllWindows()
935{
936 for (auto& w : _windows)
937 w.second.minimize();
938 return true;
939}
940
941int SdlContext::exitCode() const
942{
943 return _exitCode;
944}
945
946SDL_PixelFormat SdlContext::pixelFormat() const
947{
948 return _sdlPixelFormat;
949}
950
951bool SdlContext::addDisplayWindow(SDL_DisplayID id)
952{
953 const auto flags =
954 SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS;
955 auto title = sdl::utils::windowTitle(context()->settings);
956 auto w = SdlWindow::create(id, title, flags);
957 _windows.emplace(w.id(), std::move(w));
958 return true;
959}
960
961bool SdlContext::removeDisplayWindow(SDL_DisplayID id)
962{
963 for (auto& w : _windows)
964 {
965 if (w.second.displayIndex() == id)
966 _windows.erase(w.first);
967 }
968 return true;
969}
970
971bool SdlContext::detectDisplays()
972{
973 int count = 0;
974 auto display = SDL_GetDisplays(&count);
975 if (!display)
976 return false;
977 for (int x = 0; x < count; x++)
978 {
979 const auto id = display[x];
980 addOrUpdateDisplay(id);
981 }
982 SDL_free(display);
983 return true;
984}
985
986rdpMonitor SdlContext::getDisplay(SDL_DisplayID id) const
987{
988 return _displays.at(id);
989}
990
991std::vector<SDL_DisplayID> SdlContext::getDisplayIds() const
992{
993 std::vector<SDL_DisplayID> keys;
994 keys.reserve(_displays.size());
995 for (const auto& entry : _displays)
996 {
997 keys.push_back(entry.first);
998 }
999 return keys;
1000}
1001
1002const SdlWindow* SdlContext::getWindowForId(SDL_WindowID id) const
1003{
1004 auto it = _windows.find(id);
1005 if (it == _windows.end())
1006 return nullptr;
1007 return &it->second;
1008}
1009
1010SdlWindow* SdlContext::getWindowForId(SDL_WindowID id)
1011{
1012 auto it = _windows.find(id);
1013 if (it == _windows.end())
1014 return nullptr;
1015 return &it->second;
1016}
1017
1018SdlWindow* SdlContext::getFirstWindow()
1019{
1020 if (_windows.empty())
1021 return nullptr;
1022 return &_windows.begin()->second;
1023}
1024
1025sdlDispContext& SdlContext::getDisplayChannelContext()
1026{
1027 return _disp;
1028}
1029
1030sdlInput& SdlContext::getInputChannelContext()
1031{
1032 return _input;
1033}
1034
1035sdlClip& SdlContext::getClipboardChannelContext()
1036{
1037 return _clip;
1038}
1039
1040SdlConnectionDialogWrapper& SdlContext::getDialog()
1041{
1042 return _dialog;
1043}
1044
1045wLog* SdlContext::getWLog()
1046{
1047 return _log;
1048}
1049
1050bool SdlContext::moveMouseTo(const SDL_FPoint& pos)
1051{
1052 auto window = SDL_GetMouseFocus();
1053 if (!window)
1054 return true;
1055
1056 const auto id = SDL_GetWindowID(window);
1057 const auto spos = pixelToScreen(id, pos);
1058 SDL_WarpMouseInWindow(window, spos.x, spos.y);
1059 return true;
1060}
1061
1062bool SdlContext::handleEvent(const SDL_MouseMotionEvent& ev)
1063{
1064 if (!getWindowForId(ev.windowID))
1065 return true; /* Event for an untracked window (e.g. closed dialog) */
1066 SDL_Event copy{};
1067 copy.motion = ev;
1068 if (!eventToPixelCoordinates(ev.windowID, copy))
1069 return true;
1070 removeLocalScaling(copy.motion.x, copy.motion.y);
1071 removeLocalScaling(copy.motion.xrel, copy.motion.yrel);
1072 applyMonitorOffset(copy.motion.windowID, copy.motion.x, copy.motion.y);
1073
1074 return SdlTouch::handleEvent(this, copy.motion);
1075}
1076
1077bool SdlContext::handleEvent(const SDL_MouseWheelEvent& ev)
1078{
1079 if (!getWindowForId(ev.windowID))
1080 return true;
1081 SDL_Event copy{};
1082 copy.wheel = ev;
1083 if (!eventToPixelCoordinates(ev.windowID, copy))
1084 return true;
1085 removeLocalScaling(copy.wheel.mouse_x, copy.wheel.mouse_y);
1086 return SdlTouch::handleEvent(this, copy.wheel);
1087}
1088
1089bool SdlContext::handleEvent(const SDL_WindowEvent& ev)
1090{
1091 if (!getDisplayChannelContext().handleEvent(ev))
1092 return false;
1093
1094 auto window = getWindowForId(ev.windowID);
1095 if (!window)
1096 return true;
1097
1098 {
1099 const auto& r = window->rect();
1100 const auto& b = window->bounds();
1101 const auto& scale = window->scale();
1102 const auto& orientation = window->orientation();
1103 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
1104 "%s: [%u] %dx%d-%dx%d {%dx%d-%dx%d}{scale=%f,orientation=%s}",
1105 sdl::utils::toString(ev.type).c_str(), ev.windowID, r.x, r.y, r.w, r.h, b.x,
1106 b.y, b.w, b.h, static_cast<double>(scale),
1107 sdl::utils::toString(orientation).c_str());
1108 }
1109
1110 switch (ev.type)
1111 {
1112 case SDL_EVENT_WINDOW_MOUSE_ENTER:
1113 return restoreCursor();
1114 case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
1115 if (!resizeToScale(window))
1116 return false;
1117 if (isConnected())
1118 {
1119 if (!window->fill())
1120 return false;
1121 if (!drawToWindow(*window))
1122 return false;
1123 if (!restoreCursor())
1124 return false;
1125 }
1126 break;
1127 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
1128 if (!resizeToScale(window))
1129 return false;
1130 if (!window->fill())
1131 return false;
1132 if (!drawToWindow(*window))
1133 return false;
1134 if (!restoreCursor())
1135 return false;
1136 break;
1137 case SDL_EVENT_WINDOW_MOVED:
1138 {
1139 auto r = window->rect();
1140 auto id = window->id();
1141 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%u: %dx%d-%dx%d", id, r.x, r.y, r.w, r.h);
1142 }
1143 break;
1144 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
1145 {
1146 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Window closed, terminating RDP session...");
1147 freerdp_abort_connect_context(context());
1148 }
1149 break;
1150 default:
1151 break;
1152 }
1153 return true;
1154}
1155
1156bool SdlContext::handleEvent(const SDL_DisplayEvent& ev)
1157{
1158 if (!getDisplayChannelContext().handleEvent(ev))
1159 return false;
1160
1161 switch (ev.type)
1162 {
1163 case SDL_EVENT_DISPLAY_REMOVED: // Can't show details for this one...
1164 break;
1165 default:
1166 {
1167 SDL_Rect r = {};
1168 if (!SDL_GetDisplayBounds(ev.displayID, &r))
1169 return false;
1170 const auto name = SDL_GetDisplayName(ev.displayID);
1171 if (!name)
1172 return false;
1173 const auto orientation = SDL_GetCurrentDisplayOrientation(ev.displayID);
1174 const auto scale = SDL_GetDisplayContentScale(ev.displayID);
1175 const auto mode = SDL_GetCurrentDisplayMode(ev.displayID);
1176 if (!mode)
1177 return false;
1178
1179 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
1180 "%s: [%u, %s] %dx%d-%dx%d {orientation=%s, scale=%f}%s",
1181 sdl::utils::toString(ev.type).c_str(), ev.displayID, name, r.x, r.y, r.w,
1182 r.h, sdl::utils::toString(orientation).c_str(), static_cast<double>(scale),
1183 sdl::utils::toString(mode).c_str());
1184 }
1185 break;
1186 }
1187 return true;
1188}
1189
1190bool SdlContext::handleEvent(const SDL_MouseButtonEvent& ev)
1191{
1192 if (!getWindowForId(ev.windowID))
1193 return true;
1194 SDL_Event copy = {};
1195 copy.button = ev;
1196 if (!eventToPixelCoordinates(ev.windowID, copy))
1197 return true;
1198 removeLocalScaling(copy.button.x, copy.button.y);
1199 applyMonitorOffset(copy.button.windowID, copy.button.x, copy.button.y);
1200 return SdlTouch::handleEvent(this, copy.button);
1201}
1202
1203bool SdlContext::handleEvent(const SDL_TouchFingerEvent& ev)
1204{
1205 if (!getWindowForId(ev.windowID))
1206 return true;
1207 SDL_Event copy{};
1208 copy.tfinger = ev;
1209 if (!eventToPixelCoordinates(ev.windowID, copy))
1210 return true;
1211 removeLocalScaling(copy.tfinger.dx, copy.tfinger.dy);
1212 removeLocalScaling(copy.tfinger.x, copy.tfinger.y);
1213 applyMonitorOffset(copy.tfinger.windowID, copy.tfinger.x, copy.tfinger.y);
1214 return SdlTouch::handleEvent(this, copy.tfinger);
1215}
1216
1217void SdlContext::addOrUpdateDisplay(SDL_DisplayID id)
1218{
1219 auto monitor = SdlWindow::query(id, false);
1220 _displays.emplace(id, monitor);
1221
1222 /* Update actual display rectangles:
1223 *
1224 * 1. Get logical display bounds
1225 * 2. Use already known pixel width and height
1226 * 3. Iterate over each display and update the x and y offsets by adding all monitor
1227 * widths/heights from the primary
1228 */
1229 _offsets.clear();
1230 for (auto& entry : _displays)
1231 {
1232 SDL_Rect bounds{};
1233 std::ignore = SDL_GetDisplayBounds(entry.first, &bounds);
1234
1235 SDL_Rect pixel{};
1236 pixel.w = entry.second.width;
1237 pixel.h = entry.second.height;
1238 _offsets.emplace(entry.first, std::pair{ bounds, pixel });
1239 }
1240
1241 /* 1. Find primary and update all neighbors
1242 * 2. For each neighbor update all neighbors
1243 * 3. repeat until all displays updated.
1244 */
1245 const auto primary = SDL_GetPrimaryDisplay();
1246 std::vector<SDL_DisplayID> handled;
1247 handled.push_back(primary);
1248
1249 auto neighbors = updateDisplayOffsetsForNeighbours(primary);
1250 while (!neighbors.empty())
1251 {
1252 auto neighbor = neighbors.front();
1253 neighbors.erase(neighbors.begin());
1254
1255 if (std::find(handled.begin(), handled.end(), neighbor) != handled.end())
1256 continue;
1257 handled.push_back(neighbor);
1258
1259 auto next = updateDisplayOffsetsForNeighbours(neighbor, handled);
1260 neighbors.insert(neighbors.end(), next.begin(), next.end());
1261 }
1262 updateMonitorDataFromOffsets();
1263}
1264
1265void SdlContext::deleteDisplay(SDL_DisplayID id)
1266{
1267 _displays.erase(id);
1268}
1269
1270bool SdlContext::eventToPixelCoordinates(SDL_WindowID id, SDL_Event& ev)
1271{
1272 auto w = getWindowForId(id);
1273 if (!w)
1274 return false;
1275
1276 /* Ignore errors here, sometimes SDL has no renderer */
1277 auto renderer = w->renderer();
1278 if (!renderer)
1279 return true;
1280 return SDL_ConvertEventToRenderCoordinates(renderer, &ev);
1281}
1282
1283SDL_FPoint SdlContext::applyLocalScaling(const SDL_FPoint& val) const
1284{
1285 if (!useLocalScale())
1286 return val;
1287
1288 auto rval = val;
1289 rval.x *= _localScale.x;
1290 rval.y *= _localScale.y;
1291 return rval;
1292}
1293
1294void SdlContext::removeLocalScaling(float& x, float& y) const
1295{
1296 if (!useLocalScale())
1297 return;
1298 x /= _localScale.x;
1299 y /= _localScale.y;
1300}
1301
1302SDL_FPoint SdlContext::screenToPixel(SDL_WindowID id, const SDL_FPoint& pos)
1303{
1304 auto w = getWindowForId(id);
1305 if (!w)
1306 return {};
1307
1308 /* Ignore errors here, sometimes SDL has no renderer */
1309 auto renderer = w->renderer();
1310 if (!renderer)
1311 return pos;
1312
1313 SDL_FPoint rpos{};
1314 if (!SDL_RenderCoordinatesFromWindow(renderer, pos.x, pos.y, &rpos.x, &rpos.y))
1315 return {};
1316 removeLocalScaling(rpos.x, rpos.y);
1317 return rpos;
1318}
1319
1320SDL_FPoint SdlContext::pixelToScreen(SDL_WindowID id, const SDL_FPoint& pos)
1321{
1322 auto w = getWindowForId(id);
1323 if (!w)
1324 return {};
1325
1326 /* Ignore errors here, sometimes SDL has no renderer */
1327 auto renderer = w->renderer();
1328 if (!renderer)
1329 return pos;
1330
1331 SDL_FPoint rpos{};
1332 if (!SDL_RenderCoordinatesToWindow(renderer, pos.x, pos.y, &rpos.x, &rpos.y))
1333 return {};
1334 return applyLocalScaling(rpos);
1335}
1336
1337SDL_FRect SdlContext::pixelToScreen(SDL_WindowID id, const SDL_FRect& pos, bool round)
1338{
1339 const auto fpos = pixelToScreen(id, SDL_FPoint{ pos.x, pos.y });
1340 const auto size = pixelToScreen(id, SDL_FPoint{ pos.w, pos.h });
1341 SDL_FRect r{ fpos.x, fpos.y, size.x, size.y };
1342 if (round)
1343 {
1344 r.w = std::ceil(r.w);
1345 r.h = std::ceil(r.h);
1346 r.x = std::floor(r.x);
1347 r.y = std::floor(r.y);
1348 }
1349 return r;
1350}
1351
1352bool SdlContext::handleEvent(const SDL_Event& ev)
1353{
1354 if ((ev.type >= SDL_EVENT_DISPLAY_FIRST) && (ev.type <= SDL_EVENT_DISPLAY_LAST))
1355 {
1356 const auto& dev = ev.display;
1357 return handleEvent(dev);
1358 }
1359 if ((ev.type >= SDL_EVENT_WINDOW_FIRST) && (ev.type <= SDL_EVENT_WINDOW_LAST))
1360 {
1361 const auto& wev = ev.window;
1362 return handleEvent(wev);
1363 }
1364 switch (ev.type)
1365 {
1366 case SDL_EVENT_RENDER_TARGETS_RESET:
1367 case SDL_EVENT_RENDER_DEVICE_RESET:
1368 case SDL_EVENT_WILL_ENTER_FOREGROUND:
1369 return redraw();
1370 default:
1371 break;
1372 }
1373
1374 if (!isConnected())
1375 return true;
1376
1377 switch (ev.type)
1378 {
1379 case SDL_EVENT_FINGER_DOWN:
1380 case SDL_EVENT_FINGER_UP:
1381 case SDL_EVENT_FINGER_MOTION:
1382 {
1383 const auto& cev = ev.tfinger;
1384 return handleEvent(cev);
1385 }
1386 case SDL_EVENT_MOUSE_MOTION:
1387
1388 {
1389 const auto& cev = ev.motion;
1390 return handleEvent(cev);
1391 }
1392 case SDL_EVENT_MOUSE_BUTTON_DOWN:
1393 case SDL_EVENT_MOUSE_BUTTON_UP:
1394 {
1395 const auto& cev = ev.button;
1396 return handleEvent(cev);
1397 }
1398 case SDL_EVENT_MOUSE_WHEEL:
1399 {
1400 const auto& cev = ev.wheel;
1401 return handleEvent(cev);
1402 }
1403 case SDL_EVENT_CLIPBOARD_UPDATE:
1404 {
1405 const auto& cev = ev.clipboard;
1406 return getClipboardChannelContext().handleEvent(cev);
1407 }
1408 case SDL_EVENT_KEY_DOWN:
1409 case SDL_EVENT_KEY_UP:
1410 {
1411 const auto& cev = ev.key;
1412 return getInputChannelContext().handleEvent(cev);
1413 }
1414 default:
1415 return true;
1416 }
1417}
1418
1419COMMAND_LINE_ARGUMENT_A* SdlContext::args()
1420{
1421 return _args.data();
1422}
1423
1424size_t SdlContext::argsCount() const
1425{
1426 if (_args.size() <= 1)
1427 return 0;
1428 return _args.size() - 1;
1429}
1430
1431int SdlContext::argumentHandler(const COMMAND_LINE_ARGUMENT_A* arg, void* custom)
1432{
1433 auto sdl = static_cast<SdlContext*>(custom);
1434 if (!sdl)
1435 return -1;
1436
1437 if (arg->Name)
1438 {
1439 if (strcmp(arg->Name, sdl_allow_screensaver) == 0)
1440 {
1441 if (arg->Value != nullptr)
1442 {
1443 if (!SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"))
1444 {
1445 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
1446 "SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER) failed with %s",
1447 SDL_GetError());
1448 return -2;
1449 }
1450 }
1451 }
1452 }
1453 return 0;
1454}
1455
1456CriticalSection& SdlContext::lock()
1457{
1458 return _critical;
1459}
1460
1461std::vector<rdpPointer*>& SdlContext::pointers()
1462{
1463 return _valid_pointers;
1464}
1465
1466bool SdlContext::contains(const rdpPointer* ptr) const
1467{
1468 for (const auto& cur : _valid_pointers)
1469 {
1470 if (cur == ptr)
1471 return true;
1472 }
1473 return false;
1474}
1475
1476bool SdlContext::credentialsRead() const
1477{
1478 return _credentialsRead;
1479}
1480
1481void SdlContext::setCredentialsRead()
1482{
1483 _credentialsRead = true;
1484}
1485
1486bool SdlContext::resizeToScale(SdlWindow* window)
1487{
1488 if (freerdp_settings_get_bool(context()->settings, FreeRDP_SmartSizing))
1489 return true;
1490 if (!useLocalScale())
1491 return true;
1492 if (!window)
1493 return false;
1494 return window->resizeToScale();
1495}
1496
1497bool SdlContext::useLocalScale() const
1498{
1499 const auto ssize = freerdp_settings_get_bool(context()->settings, FreeRDP_SmartSizing);
1500 if (ssize)
1501 return true;
1502 const auto dynResize =
1503 freerdp_settings_get_bool(context()->settings, FreeRDP_DynamicResolutionUpdate);
1504 const auto fs = freerdp_settings_get_bool(context()->settings, FreeRDP_Fullscreen);
1505 const auto multimon = freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon);
1506 return !dynResize && !fs && !multimon;
1507}
1508
1509bool SdlContext::drawToWindows(const std::vector<SDL_Rect>& rects)
1510{
1511 for (auto& window : _windows)
1512 {
1513 if (!drawToWindow(window.second, rects))
1514 return FALSE;
1515 }
1516
1517 return TRUE;
1518}
1519
1520BOOL SdlContext::desktopResize(rdpContext* context)
1521{
1522 rdpGdi* gdi = nullptr;
1523 rdpSettings* settings = nullptr;
1524 auto sdl = get_context(context);
1525
1526 WINPR_ASSERT(sdl);
1527 WINPR_ASSERT(context);
1528
1529 settings = context->settings;
1530 WINPR_ASSERT(settings);
1531
1532 std::unique_lock lock(sdl->_critical);
1533 gdi = context->gdi;
1534 if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
1535 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
1536 return FALSE;
1537 return sdl->createPrimary();
1538}
1539
1540/* This function is called to output a System BEEP */
1541BOOL SdlContext::playSound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound)
1542{
1543 /* TODO: Implement */
1544 WINPR_UNUSED(context);
1545 WINPR_UNUSED(play_sound);
1546 return TRUE;
1547}
1548
1549/* This function is called whenever a new frame starts.
1550 * It can be used to reset invalidated areas. */
1551BOOL SdlContext::beginPaint(rdpContext* context)
1552{
1553 auto gdi = context->gdi;
1554 WINPR_ASSERT(gdi);
1555 WINPR_ASSERT(gdi->primary);
1556
1557 HGDI_DC hdc = gdi->primary->hdc;
1558 WINPR_ASSERT(hdc);
1559 if (!hdc->hwnd)
1560 return TRUE;
1561
1562 HGDI_WND hwnd = hdc->hwnd;
1563 WINPR_ASSERT(hwnd->invalid);
1564 hwnd->invalid->null = TRUE;
1565 hwnd->ninvalid = 0;
1566
1567 return TRUE;
1568}
1569
1570bool SdlContext::redraw(bool suppress) const
1571{
1572 if (!_connected)
1573 return true;
1574
1575 auto gdi = context()->gdi;
1576 WINPR_ASSERT(gdi);
1577 return gdi_send_suppress_output(gdi, suppress);
1578}
1579
1580void SdlContext::setConnected(bool val)
1581{
1582 _connected = val;
1583}
1584
1585bool SdlContext::isConnected() const
1586{
1587 return _connected;
1588}
1589
1590rdpContext* SdlContext::context() const
1591{
1592 WINPR_ASSERT(_context);
1593 return _context;
1594}
1595
1596rdpClientContext* SdlContext::common() const
1597{
1598 return reinterpret_cast<rdpClientContext*>(context());
1599}
1600
1601bool SdlContext::setCursor(CursorType type)
1602{
1603 _cursorType = type;
1604 return restoreCursor();
1605}
1606
1607bool SdlContext::setCursor(const rdpPointer* cursor)
1608{
1609 std::unique_lock lock(_critical);
1610 if (!contains(cursor))
1611 return true;
1612
1613 _cursor = { sdl_Pointer_Copy(cursor), sdl_PointerFreeCopyAll };
1614 return setCursor(CURSOR_IMAGE);
1615}
1616
1617rdpPointer* SdlContext::cursor() const
1618{
1619 return _cursor.get();
1620}
1621
1622bool SdlContext::restoreCursor()
1623{
1624 WLog_Print(getWLog(), WLOG_DEBUG, "restore cursor: %d", _cursorType);
1625 switch (_cursorType)
1626 {
1627 case CURSOR_NULL:
1628 if (!SDL_HideCursor())
1629 {
1630 WLog_Print(getWLog(), WLOG_ERROR, "SDL_HideCursor failed");
1631 return false;
1632 }
1633
1634 setHasCursor(false);
1635 return true;
1636
1637 case CURSOR_DEFAULT:
1638 {
1639 auto def = SDL_GetDefaultCursor();
1640 if (!SDL_SetCursor(def))
1641 {
1642 WLog_Print(getWLog(), WLOG_ERROR, "SDL_SetCursor(default=%p) failed",
1643 static_cast<void*>(def));
1644 return false;
1645 }
1646 if (!SDL_ShowCursor())
1647 {
1648 WLog_Print(getWLog(), WLOG_ERROR, "SDL_ShowCursor failed");
1649 return false;
1650 }
1651 setHasCursor(true);
1652 return true;
1653 }
1654 case CURSOR_IMAGE:
1655 setHasCursor(true);
1656 return sdl_Pointer_Set_Process(this);
1657 default:
1658 WLog_Print(getWLog(), WLOG_ERROR, "Unknown cursorType %s",
1659 sdl::utils::toString(_cursorType).c_str());
1660 return false;
1661 }
1662}
1663
1664void SdlContext::setMonitorIds(const std::vector<SDL_DisplayID>& ids)
1665{
1666 _monitorIds.clear();
1667 for (auto id : ids)
1668 {
1669 _monitorIds.push_back(id);
1670 }
1671}
1672
1673const std::vector<SDL_DisplayID>& SdlContext::monitorIds() const
1674{
1675 return _monitorIds;
1676}
1677
1678int64_t SdlContext::monitorId(uint32_t index) const
1679{
1680 if (index >= _monitorIds.size())
1681 {
1682 return -1;
1683 }
1684 return _monitorIds.at(index);
1685}
1686
1687void SdlContext::push(std::vector<SDL_Rect>&& rects)
1688{
1689 std::unique_lock lock(_queue_mux);
1690 _queue.emplace(std::move(rects));
1691}
1692
1693std::vector<SDL_Rect> SdlContext::pop()
1694{
1695 std::unique_lock lock(_queue_mux);
1696 if (_queue.empty())
1697 {
1698 return {};
1699 }
1700 auto val = std::move(_queue.front());
1701 _queue.pop();
1702 return val;
1703}
1704
1705bool SdlContext::setFullscreen(bool enter, bool forceOriginalDisplay)
1706{
1707 for (const auto& window : _windows)
1708 {
1709 if (!sdl_push_user_event(SDL_EVENT_USER_WINDOW_FULLSCREEN, &window.second, enter,
1710 forceOriginalDisplay))
1711 return false;
1712 }
1713 _fullscreen = enter;
1714 return true;
1715}
1716
1717bool SdlContext::setMinimized()
1718{
1719 return sdl_push_user_event(SDL_EVENT_USER_WINDOW_MINIMIZE);
1720}
1721
1722bool SdlContext::grabMouse() const
1723{
1724 return _grabMouse;
1725}
1726
1727bool SdlContext::toggleGrabMouse()
1728{
1729 return setGrabMouse(!grabMouse());
1730}
1731
1732bool SdlContext::setGrabMouse(bool enter)
1733{
1734 _grabMouse = enter;
1735 return true;
1736}
1737
1738bool SdlContext::grabKeyboard() const
1739{
1740 return _grabKeyboard;
1741}
1742
1743bool SdlContext::toggleGrabKeyboard()
1744{
1745 return setGrabKeyboard(!grabKeyboard());
1746}
1747
1748bool SdlContext::setGrabKeyboard(bool enter)
1749{
1750 _grabKeyboard = enter;
1751 return true;
1752}
1753
1754bool SdlContext::setResizeable(bool enable)
1755{
1756 const auto settings = context()->settings;
1757 const bool dyn = freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate);
1758 const bool smart = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing);
1759 bool use = (dyn && enable) || smart;
1760
1761 for (const auto& window : _windows)
1762 {
1763 if (!sdl_push_user_event(SDL_EVENT_USER_WINDOW_RESIZEABLE, &window.second, use))
1764 return false;
1765 }
1766 _resizeable = use;
1767
1768 return true;
1769}
1770
1771bool SdlContext::resizeable() const
1772{
1773 return _resizeable;
1774}
1775
1776bool SdlContext::toggleResizeable()
1777{
1778 return setResizeable(!resizeable());
1779}
1780
1781bool SdlContext::fullscreen() const
1782{
1783 return _fullscreen;
1784}
1785
1786bool SdlContext::toggleFullscreen()
1787{
1788 return setFullscreen(!fullscreen());
1789}
object that handles clipboard context for the SDL3 client
Definition sdl_clip.hpp:76
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.