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