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