FreeRDP
Loading...
Searching...
No Matches
SDL3/sdl_freerdp.cpp
1
20#include <iostream>
21#include <memory>
22#include <mutex>
23
24#include <freerdp/config.h>
25
26#if defined(__has_include)
27#if __has_include(<version>)
28#include <version>
29#endif
30#endif
31
32#include <cerrno>
33#include <cstdio>
34#include <cstring>
35
36#if defined(__cpp_lib_source_location) && (__cpp_lib_source_location >= 201907L)
37#include <source_location>
38#endif
39
40#include <freerdp/constants.h>
41#include <freerdp/freerdp.h>
42#include <freerdp/gdi/gdi.h>
43#include <freerdp/streamdump.h>
44#include <freerdp/utils/signal.h>
45
46#include <freerdp/channels/channels.h>
47#include <freerdp/client/channels.h>
48#include <freerdp/client/cliprdr.h>
49#include <freerdp/client/cmdline.h>
50#include <freerdp/client/file.h>
51
52#include <freerdp/log.h>
53#include <winpr/assert.h>
54#include <winpr/config.h>
55#include <winpr/crt.h>
56#include <winpr/synch.h>
57
58#include <SDL3/SDL.h>
59#if !defined(__MINGW32__)
60#include <SDL3/SDL_main.h>
61#endif
62#include <SDL3/SDL_hints.h>
63#include <SDL3/SDL_oldnames.h>
64#include <SDL3/SDL_video.h>
65
66#include <sdl_config.hpp>
67
68#include "dialogs/sdl_connection_dialog_hider.hpp"
69#include "dialogs/sdl_dialogs.hpp"
70#include "scoped_guard.hpp"
71#include "sdl_channels.hpp"
72#include "sdl_freerdp.hpp"
73#include "sdl_context.hpp"
74#include "sdl_monitor.hpp"
75#include "sdl_pointer.hpp"
76#include "sdl_prefs.hpp"
77#include "sdl_utils.hpp"
78
79#if defined(_WIN32)
80#define SDL_TAG CLIENT_TAG("SDL")
81#endif
82
83class ErrorMsg : public std::exception
84{
85 public:
86 ErrorMsg(int rc, Uint32 type, const std::string& msg,
87 const std::string& sdlError = SDL_GetError()
88#if defined(__cpp_lib_source_location) && (__cpp_lib_source_location >= 201907L)
89 ,
90 std::source_location loc = std::source_location::current()
91#elif defined(__GNUC__)
92 ,
93 const std::string& file = __builtin_FILE(),
94 const std::string& fkt = __builtin_FUNCTION(), size_t line = __builtin_LINE(),
95#if defined(__clang__)
96 size_t column = __builtin_COLUMN()
97#else
98 size_t column = 0
99#endif
100#endif
101 );
102
103 [[nodiscard]] int rc() const;
104 [[nodiscard]] const char* what() const noexcept override;
105
106 private:
107 int _rc;
108 Uint32 _type;
109 std::string _msg;
110 std::string _sdlError;
111 std::string _buffer;
112
113 std::string _file;
114 std::string _fkt;
115 size_t _line = 0;
116 size_t _column = 0;
117};
118const char* ErrorMsg::what() const noexcept
119{
120 return _buffer.c_str();
121}
122
123int ErrorMsg::rc() const
124{
125 return _rc;
126}
127
128ErrorMsg::ErrorMsg(int rc, Uint32 type, const std::string& msg, const std::string& sdlError
129#if defined(__cpp_lib_source_location) && (__cpp_lib_source_location >= 201907L)
130 ,
131 std::source_location loc
132#elif defined(__GNUC__)
133 ,
134 const std::string& file, const std::string& fkt, size_t line, size_t column
135#endif
136 )
137 : _rc(rc), _type(type), _msg(msg), _sdlError(sdlError)
138#if defined(__cpp_lib_source_location) && (__cpp_lib_source_location >= 201907L)
139 ,
140 _file(loc.file_name()), _fkt(loc.function_name()), _line(loc.line()), _column(loc.column())
141#elif defined(__GNUC__)
142 ,
143 _file(file), _fkt(fkt), _line(line), _column(column)
144#endif
145{
146 std::stringstream ss;
147 ss << _msg << " {" << sdl::utils::toString(_type) << "}{SDL:" << sdlError << "}";
148 ss << "{" << _fkt << " [" << _file << ":" << _line << "-" << _column << "]}";
149 _buffer = ss.str();
150}
151
152static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const char* signame,
153 [[maybe_unused]] void* context)
154{
155 std::ignore = sdl_push_quit();
156}
157
158[[nodiscard]] static int sdl_run(SdlContext* sdl)
159{
160 int rc = -1;
161 WINPR_ASSERT(sdl);
162
163 try
164 {
165 while (!sdl->shallAbort())
166 {
167 SDL_Event windowEvent = {};
168 while (!sdl->shallAbort() && SDL_WaitEventTimeout(nullptr, 1000))
169 {
170 /* Only poll standard SDL events and SDL_EVENT_USERS meant to create
171 * dialogs. do not process the dialog return value events here.
172 */
173 const int prc = SDL_PeepEvents(&windowEvent, 1, SDL_GETEVENT, SDL_EVENT_FIRST,
174 SDL_EVENT_USER_RETRY_DIALOG);
175 if (prc < 0)
176 {
177 if (sdl_log_error(prc, sdl->getWLog(), "SDL_PeepEvents"))
178 continue;
179 }
180
181#if defined(WITH_DEBUG_SDL_EVENTS)
182 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "got event %s [0x%08" PRIx32 "]",
183 sdl::utils::toString(windowEvent.type).c_str(), windowEvent.type);
184#endif
185 if (sdl->shallAbort(true))
186 continue;
187
188 if (sdl->getDialog().handleEvent(windowEvent))
189 continue;
190
191 if (!sdl->handleEvent(windowEvent))
192 throw ErrorMsg{ -1, windowEvent.type, "sdl->handleEvent" };
193
194 switch (windowEvent.type)
195 {
196 case SDL_EVENT_QUIT:
197 std::ignore = freerdp_abort_connect_context(sdl->context());
198 break;
199 case SDL_EVENT_USER_CERT_DIALOG:
200 {
201 SDLConnectionDialogHider hider(sdl);
202 auto title = static_cast<const char*>(windowEvent.user.data1);
203 auto msg = static_cast<const char*>(windowEvent.user.data2);
204 if (!sdl_cert_dialog_show(title, msg))
205 throw ErrorMsg{ -1, windowEvent.type, "sdl_cert_dialog_show" };
206 }
207 break;
208 case SDL_EVENT_USER_SHOW_DIALOG:
209 {
210 SDLConnectionDialogHider hider(sdl);
211 auto title = static_cast<const char*>(windowEvent.user.data1);
212 auto msg = static_cast<const char*>(windowEvent.user.data2);
213 if (!sdl_message_dialog_show(title, msg, windowEvent.user.code))
214 throw ErrorMsg{ -1, windowEvent.type, "sdl_message_dialog_show" };
215 }
216 break;
217 case SDL_EVENT_USER_SCARD_DIALOG:
218 {
219 SDLConnectionDialogHider hider(sdl);
220 auto title = static_cast<const char*>(windowEvent.user.data1);
221 auto msg = static_cast<const char**>(windowEvent.user.data2);
222 if (!sdl_scard_dialog_show(title, windowEvent.user.code, msg))
223 throw ErrorMsg{ -1, windowEvent.type, "sdl_scard_dialog_show" };
224 }
225 break;
226 case SDL_EVENT_USER_AUTH_DIALOG:
227 {
228 SDLConnectionDialogHider hider(sdl);
229 if (!sdl_auth_dialog_show(
230 reinterpret_cast<const SDL_UserAuthArg*>(windowEvent.padding)))
231 throw ErrorMsg{ -1, windowEvent.type, "sdl_auth_dialog_show" };
232 }
233 break;
234 case SDL_EVENT_USER_UPDATE:
235 {
236 std::vector<SDL_Rect> rectangles;
237 do
238 {
239 rectangles = sdl->pop();
240 if (!sdl->drawToWindows(rectangles))
241 throw ErrorMsg{ -1, windowEvent.type, "sdl->drawToWindows" };
242 } while (!rectangles.empty());
243 }
244 break;
245 case SDL_EVENT_USER_CREATE_WINDOWS:
246 {
247 auto ctx = static_cast<SdlContext*>(windowEvent.user.data1);
248 if (!ctx->createWindows())
249 throw ErrorMsg{ -1, windowEvent.type, "sdl->createWindows" };
250 }
251 break;
252 case SDL_EVENT_USER_WINDOW_RESIZEABLE:
253 {
254 auto window = static_cast<SdlWindow*>(windowEvent.user.data1);
255 const bool use = windowEvent.user.code != 0;
256 if (window)
257 window->resizeable(use);
258 }
259 break;
260 case SDL_EVENT_USER_WINDOW_FULLSCREEN:
261 {
262 auto window = static_cast<SdlWindow*>(windowEvent.user.data1);
263 const bool enter = windowEvent.user.code != 0;
264 const bool forceOriginalDisplay = windowEvent.user.data2 != nullptr;
265 if (window)
266 window->fullscreen(enter, forceOriginalDisplay);
267 }
268 break;
269 case SDL_EVENT_USER_WINDOW_MINIMIZE:
270 if (!sdl->minimizeAllWindows())
271 throw ErrorMsg{ -1, windowEvent.type, "sdl->minimizeAllWindows" };
272 break;
273 case SDL_EVENT_USER_POINTER_NULL:
274 if (!sdl->setCursor(SdlContext::CURSOR_NULL))
275 throw ErrorMsg{ -1, windowEvent.type, "sdl->setCursor" };
276 break;
277 case SDL_EVENT_USER_POINTER_DEFAULT:
278 if (!sdl->setCursor(SdlContext::CURSOR_DEFAULT))
279 throw ErrorMsg{ -1, windowEvent.type, "sdl->setCursor" };
280 break;
281 case SDL_EVENT_USER_POINTER_POSITION:
282 {
283 const auto x =
284 static_cast<INT32>(reinterpret_cast<uintptr_t>(windowEvent.user.data1));
285 const auto y =
286 static_cast<INT32>(reinterpret_cast<uintptr_t>(windowEvent.user.data2));
287 if (!sdl->moveMouseTo(
288 { static_cast<float>(x) * 1.0f, static_cast<float>(y) * 1.0f }))
289 throw ErrorMsg{ -1, windowEvent.type, "sdl->moveMouseTo" };
290 }
291 break;
292 case SDL_EVENT_USER_POINTER_SET:
293 if (!sdl->setCursor(static_cast<rdpPointer*>(windowEvent.user.data1)))
294 throw ErrorMsg{ -1, windowEvent.type, "sdl->setCursor" };
295 break;
296 case SDL_EVENT_USER_QUIT:
297 default:
298 break;
299 }
300 }
301 }
302 rc = 1;
303 }
304 catch (ErrorMsg& msg)
305 {
306 WLog_Print(sdl->getWLog(), WLOG_ERROR, "[exception] %s", msg.what());
307 rc = msg.rc();
308 }
309
310 return rc;
311}
312
313/* Optional global initializer.
314 * Here we just register a signal handler to print out stack traces
315 * if available. */
316[[nodiscard]] static BOOL sdl_client_global_init()
317{
318#if defined(_WIN32)
319 WSADATA wsaData = {};
320 const DWORD wVersionRequested = MAKEWORD(1, 1);
321 const int rc = WSAStartup(wVersionRequested, &wsaData);
322 if (rc != 0)
323 {
324 WLog_ERR(SDL_TAG, "WSAStartup failed with %s [%d]", gai_strerrorA(rc), rc);
325 return FALSE;
326 }
327#endif
328
329 return (freerdp_handle_signals() == 0);
330}
331
332/* Optional global tear down */
333static void sdl_client_global_uninit()
334{
335#if defined(_WIN32)
336 WSACleanup();
337#endif
338}
339
340[[nodiscard]] static BOOL sdl_client_new(freerdp* instance, rdpContext* context)
341{
342 auto sdl = reinterpret_cast<sdl_rdp_context*>(context);
343
344 if (!instance || !context)
345 return FALSE;
346
347 sdl->sdl = new SdlContext(context);
348 return sdl->sdl != nullptr;
349}
350
351static void sdl_client_free([[maybe_unused]] freerdp* instance, rdpContext* context)
352{
353 auto sdl = reinterpret_cast<sdl_rdp_context*>(context);
354
355 if (!context)
356 return;
357
358 delete sdl->sdl;
359}
360
361[[nodiscard]] static int sdl_client_start(rdpContext* context)
362{
363 auto sdl = get_context(context);
364 WINPR_ASSERT(sdl);
365 return sdl->start();
366}
367
368[[nodiscard]] static int sdl_client_stop(rdpContext* context)
369{
370 auto sdl = get_context(context);
371 WINPR_ASSERT(sdl);
372 return sdl->join();
373}
374
375static void RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
376{
377 WINPR_ASSERT(pEntryPoints);
378
379 ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS));
380 pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION;
381 pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
382 pEntryPoints->GlobalInit = sdl_client_global_init;
383 pEntryPoints->GlobalUninit = sdl_client_global_uninit;
384 pEntryPoints->ContextSize = sizeof(sdl_rdp_context);
385 pEntryPoints->ClientNew = sdl_client_new;
386 pEntryPoints->ClientFree = sdl_client_free;
387 pEntryPoints->ClientStart = sdl_client_start;
388 pEntryPoints->ClientStop = sdl_client_stop;
389}
390
391static void context_free(sdl_rdp_context* sdl)
392{
393 if (sdl)
394 freerdp_client_context_free(&sdl->common.context);
395}
396
397[[nodiscard]] static const char* category2str(int category)
398{
399 switch (category)
400 {
401 case SDL_LOG_CATEGORY_APPLICATION:
402 return "SDL_LOG_CATEGORY_APPLICATION";
403 case SDL_LOG_CATEGORY_ERROR:
404 return "SDL_LOG_CATEGORY_ERROR";
405 case SDL_LOG_CATEGORY_ASSERT:
406 return "SDL_LOG_CATEGORY_ASSERT";
407 case SDL_LOG_CATEGORY_SYSTEM:
408 return "SDL_LOG_CATEGORY_SYSTEM";
409 case SDL_LOG_CATEGORY_AUDIO:
410 return "SDL_LOG_CATEGORY_AUDIO";
411 case SDL_LOG_CATEGORY_VIDEO:
412 return "SDL_LOG_CATEGORY_VIDEO";
413 case SDL_LOG_CATEGORY_RENDER:
414 return "SDL_LOG_CATEGORY_RENDER";
415 case SDL_LOG_CATEGORY_INPUT:
416 return "SDL_LOG_CATEGORY_INPUT";
417 case SDL_LOG_CATEGORY_TEST:
418 return "SDL_LOG_CATEGORY_TEST";
419 case SDL_LOG_CATEGORY_GPU:
420 return "SDL_LOG_CATEGORY_GPU";
421 case SDL_LOG_CATEGORY_RESERVED2:
422 return "SDL_LOG_CATEGORY_RESERVED2";
423 case SDL_LOG_CATEGORY_RESERVED3:
424 return "SDL_LOG_CATEGORY_RESERVED3";
425 case SDL_LOG_CATEGORY_RESERVED4:
426 return "SDL_LOG_CATEGORY_RESERVED4";
427 case SDL_LOG_CATEGORY_RESERVED5:
428 return "SDL_LOG_CATEGORY_RESERVED5";
429 case SDL_LOG_CATEGORY_RESERVED6:
430 return "SDL_LOG_CATEGORY_RESERVED6";
431 case SDL_LOG_CATEGORY_RESERVED7:
432 return "SDL_LOG_CATEGORY_RESERVED7";
433 case SDL_LOG_CATEGORY_RESERVED8:
434 return "SDL_LOG_CATEGORY_RESERVED8";
435 case SDL_LOG_CATEGORY_RESERVED9:
436 return "SDL_LOG_CATEGORY_RESERVED9";
437 case SDL_LOG_CATEGORY_RESERVED10:
438 return "SDL_LOG_CATEGORY_RESERVED10";
439 case SDL_LOG_CATEGORY_CUSTOM:
440 default:
441 return "SDL_LOG_CATEGORY_CUSTOM";
442 }
443}
444
445[[nodiscard]] static SDL_LogPriority wloglevel2dl(DWORD level)
446{
447 switch (level)
448 {
449 case WLOG_TRACE:
450 return SDL_LOG_PRIORITY_VERBOSE;
451 case WLOG_DEBUG:
452 return SDL_LOG_PRIORITY_DEBUG;
453 case WLOG_INFO:
454 return SDL_LOG_PRIORITY_INFO;
455 case WLOG_WARN:
456 return SDL_LOG_PRIORITY_WARN;
457 case WLOG_ERROR:
458 return SDL_LOG_PRIORITY_ERROR;
459 case WLOG_FATAL:
460 return SDL_LOG_PRIORITY_CRITICAL;
461 case WLOG_OFF:
462 default:
463 return SDL_LOG_PRIORITY_VERBOSE;
464 }
465}
466
467[[nodiscard]] static DWORD sdlpriority2wlog(SDL_LogPriority priority)
468{
469 DWORD level = WLOG_OFF;
470 switch (priority)
471 {
472 case SDL_LOG_PRIORITY_VERBOSE:
473 level = WLOG_TRACE;
474 break;
475 case SDL_LOG_PRIORITY_DEBUG:
476 level = WLOG_DEBUG;
477 break;
478 case SDL_LOG_PRIORITY_INFO:
479 level = WLOG_INFO;
480 break;
481 case SDL_LOG_PRIORITY_WARN:
482 level = WLOG_WARN;
483 break;
484 case SDL_LOG_PRIORITY_ERROR:
485 level = WLOG_ERROR;
486 break;
487 case SDL_LOG_PRIORITY_CRITICAL:
488 level = WLOG_FATAL;
489 break;
490 default:
491 break;
492 }
493
494 return level;
495}
496
497static void SDLCALL winpr_LogOutputFunction(void* userdata, int category, SDL_LogPriority priority,
498 const char* message)
499{
500 auto sdl = static_cast<SdlContext*>(userdata);
501 WINPR_ASSERT(sdl);
502
503 const DWORD level = sdlpriority2wlog(priority);
504 auto log = sdl->getWLog();
505 if (!WLog_IsLevelActive(log, level))
506 return;
507
508 WLog_PrintTextMessage(log, level, __LINE__, __FILE__, __func__, "[%s] %s",
509 category2str(category), message);
510}
511
512static void sdl_quit()
513{
514 const auto cat = SDL_LOG_CATEGORY_APPLICATION;
515 SDL_Event ev = {};
516 ev.type = SDL_EVENT_QUIT;
517 if (!SDL_PushEvent(&ev))
518 {
519 SDL_LogError(cat, "An error occurred: %s", SDL_GetError());
520 }
521}
522
523static void SDLCALL rdp_file_cb(void* userdata, const char* const* filelist,
524 WINPR_ATTR_UNUSED int filter)
525{
526 auto rdp = static_cast<std::string*>(userdata);
527
528 const auto cat = SDL_LOG_CATEGORY_APPLICATION;
529 if (!filelist)
530 {
531 SDL_LogError(cat, "An error occurred: %s", SDL_GetError());
532 sdl_quit();
533 return;
534 }
535 else if (!*filelist)
536 {
537 SDL_LogError(cat, "The user did not select any file.");
538 SDL_LogError(cat, "Most likely, the dialog was canceled.");
539 sdl_quit();
540 return;
541 }
542
543 while (*filelist)
544 {
545 SDL_LogError(cat, "Full path to selected file: '%s'", *filelist);
546 *rdp = *filelist;
547 filelist++;
548 }
549
550 sdl_quit();
551}
552
553[[nodiscard]] static std::string getRdpFile()
554{
555 const auto flags = SDL_INIT_VIDEO | SDL_INIT_EVENTS;
556 SDL_DialogFileFilter filters[] = { { "RDP files", "rdp;rdpw" } };
557 std::string rdp;
558
559 bool running = true;
560 if (!SDL_Init(flags))
561 return {};
562
563 auto props = SDL_CreateProperties();
564 SDL_SetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING,
565 "SDL Freerdp - Open a RDP file");
566 SDL_SetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
567 SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, filters);
568 SDL_SetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, ARRAYSIZE(filters));
569 SDL_ShowFileDialogWithProperties(SDL_FILEDIALOG_OPENFILE, rdp_file_cb, &rdp, props);
570 SDL_DestroyProperties(props);
571
572 do
573 {
574 SDL_Event event = {};
575 std::ignore = SDL_PollEvent(&event);
576
577 switch (event.type)
578 {
579 case SDL_EVENT_QUIT:
580 running = false;
581 break;
582 default:
583 break;
584 }
585 } while (running);
586 SDL_Quit();
587 return rdp;
588}
589
590int main(int argc, char* argv[])
591{
592 int rc = -1;
593 RDP_CLIENT_ENTRY_POINTS clientEntryPoints = {};
594
595 /* Allocate the RDP context first, we need to pass it to SDL */
596 RdpClientEntry(&clientEntryPoints);
597 std::unique_ptr<sdl_rdp_context, void (*)(sdl_rdp_context*)> sdl_rdp(
598 reinterpret_cast<sdl_rdp_context*>(freerdp_client_context_new(&clientEntryPoints)),
599 context_free);
600
601 if (!sdl_rdp)
602 return -1;
603 auto sdl = sdl_rdp->sdl;
604
605 auto settings = sdl->context()->settings;
606 WINPR_ASSERT(settings);
607
608 std::string rdp_file;
609 std::vector<char*> args;
610 args.reserve(WINPR_ASSERTING_INT_CAST(size_t, argc));
611 for (auto x = 0; x < argc; x++)
612 args.push_back(argv[x]);
613
614 if (argc == 1)
615 {
616 rdp_file = getRdpFile();
617 if (!rdp_file.empty())
618 {
619 args.push_back(rdp_file.data());
620 }
621 }
622
623 auto status = freerdp_client_settings_parse_command_line(
624 settings, WINPR_ASSERTING_INT_CAST(int, args.size()), args.data(), FALSE);
625 sdl_rdp->sdl->setMetadata();
626 if (status)
627 {
628 rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv);
629 if (freerdp_settings_get_bool(settings, FreeRDP_ListMonitors))
630 {
631 if (!sdl_list_monitors(sdl))
632 return -1;
633 }
634 else
635 {
636 switch (status)
637 {
638 case COMMAND_LINE_STATUS_PRINT:
639 case COMMAND_LINE_STATUS_PRINT_VERSION:
640 case COMMAND_LINE_STATUS_PRINT_BUILDCONFIG:
641 break;
642 case COMMAND_LINE_STATUS_PRINT_HELP:
643 SdlPref::print_config_file_help(3);
644 break;
645 default:
646 break;
647 }
648 }
649 return rc;
650 }
651
652 /* Basic SDL initialization */
653 if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
654 return -1;
655
656 SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0");
657 SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
658 SDL_SetHint(SDL_HINT_PEN_MOUSE_EVENTS, "0");
659 SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
660 SDL_SetHint(SDL_HINT_PEN_TOUCH_EVENTS, "1");
661 SDL_SetHint(SDL_HINT_TRACKPAD_IS_TOUCH_ONLY, "1");
662
663 /* Redirect SDL log messages to wLog */
664 SDL_SetLogOutputFunction(winpr_LogOutputFunction, sdl);
665 auto level = WLog_GetLogLevel(sdl->getWLog());
666 SDL_SetLogPriorities(wloglevel2dl(level));
667
668 auto backend = SDL_GetCurrentVideoDriver();
669 WLog_Print(sdl->getWLog(), WLOG_DEBUG, "client is using backend '%s'", backend);
670 sdl_dialogs_init();
671
672 /* SDL cleanup code if the client exits */
673 ScopeGuard guard(
674 [&]()
675 {
676 sdl->cleanup();
677 freerdp_del_signal_cleanup_handler(sdl->context(), sdl_term_handler);
678 sdl_dialogs_uninit();
679 SDL_Quit();
680 });
681 if (!sdl->detectDisplays())
682 return -1;
683
684 /* Initialize RDP */
685 auto context = sdl->context();
686 WINPR_ASSERT(context);
687
688 if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE))
689 return -1;
690
691 if (freerdp_client_start(context) != 0)
692 return -1;
693
694 rc = sdl_run(sdl);
695
696 if (freerdp_client_stop(context) != 0)
697 return -1;
698
699 if (sdl->exitCode() != 0)
700 rc = sdl->exitCode();
701
702 return rc;
703}
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.