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