24#include <freerdp/config.h>
26#if defined(__has_include)
27#if __has_include(<version>)
36#if defined(__cpp_lib_source_location) && (__cpp_lib_source_location >= 201907L)
37#include <source_location>
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>
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>
52#include <freerdp/log.h>
53#include <winpr/assert.h>
54#include <winpr/config.h>
56#include <winpr/synch.h>
59#if !defined(__MINGW32__)
60#include <SDL3/SDL_main.h>
62#include <SDL3/SDL_hints.h>
63#include <SDL3/SDL_oldnames.h>
64#include <SDL3/SDL_video.h>
66#include <sdl_config.hpp>
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"
80#define SDL_TAG CLIENT_TAG("SDL")
83class ErrorMsg :
public std::exception
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)
90 std::source_location loc = std::source_location::current()
91#elif defined(__GNUC__)
93 const std::string& file = __builtin_FILE(),
94 const std::string& fkt = __builtin_FUNCTION(),
size_t line = __builtin_LINE(),
96 size_t column = __builtin_COLUMN()
103 [[nodiscard]]
int rc()
const;
104 [[nodiscard]]
const char* what() const noexcept override;
110 std::
string _sdlError;
118const
char* ErrorMsg::what() const noexcept
120 return _buffer.c_str();
123int ErrorMsg::rc()
const
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)
131 std::source_location loc
132#elif defined(__GNUC__)
134 const std::string& file,
const std::string& fkt,
size_t line,
size_t column
137 : _rc(rc), _type(type), _msg(msg), _sdlError(sdlError)
138#if defined(__cpp_lib_source_location) && (__cpp_lib_source_location >= 201907L)
140 _file(loc.file_name()), _fkt(loc.function_name()), _line(loc.line()), _column(loc.column())
141#elif defined(__GNUC__)
143 _file(file), _fkt(fkt), _line(line), _column(column)
146 std::stringstream ss;
147 ss << _msg <<
" {" << sdl::utils::toString(_type) <<
"}{SDL:" << sdlError <<
"}";
148 ss <<
"{" << _fkt <<
" [" << _file <<
":" << _line <<
"-" << _column <<
"]}";
152static void sdl_term_handler([[maybe_unused]]
int signum, [[maybe_unused]]
const char* signame,
153 [[maybe_unused]]
void* context)
155 std::ignore = sdl_push_quit();
158[[nodiscard]]
static int sdl_run(
SdlContext* sdl)
165 while (!sdl->shallAbort())
167 SDL_Event windowEvent = {};
168 while (!sdl->shallAbort() && SDL_WaitEventTimeout(
nullptr, 1000))
173 const int prc = SDL_PeepEvents(&windowEvent, 1, SDL_GETEVENT, SDL_EVENT_FIRST,
174 SDL_EVENT_USER_RETRY_DIALOG);
177 if (sdl_log_error(prc, sdl->getWLog(),
"SDL_PeepEvents"))
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);
185 if (sdl->shallAbort(
true))
188 if (sdl->getDialog().handleEvent(windowEvent))
191 if (!sdl->handleEvent(windowEvent))
192 throw ErrorMsg{ -1, windowEvent.type,
"sdl->handleEvent" };
194 switch (windowEvent.type)
197 std::ignore = freerdp_abort_connect_context(sdl->context());
199 case SDL_EVENT_USER_CERT_DIALOG:
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" };
208 case SDL_EVENT_USER_SHOW_DIALOG:
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" };
217 case SDL_EVENT_USER_SCARD_DIALOG:
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" };
226 case SDL_EVENT_USER_AUTH_DIALOG:
229 if (!sdl_auth_dialog_show(
231 throw ErrorMsg{ -1, windowEvent.type,
"sdl_auth_dialog_show" };
234 case SDL_EVENT_USER_UPDATE:
236 std::vector<SDL_Rect> rectangles;
239 rectangles = sdl->pop();
240 if (!sdl->drawToWindows(rectangles))
241 throw ErrorMsg{ -1, windowEvent.type,
"sdl->drawToWindows" };
242 }
while (!rectangles.empty());
245 case SDL_EVENT_USER_CREATE_WINDOWS:
247 auto ctx =
static_cast<SdlContext*
>(windowEvent.user.data1);
248 if (!ctx->createWindows())
249 throw ErrorMsg{ -1, windowEvent.type,
"sdl->createWindows" };
252 case SDL_EVENT_USER_WINDOW_RESIZEABLE:
254 auto window =
static_cast<SdlWindow*
>(windowEvent.user.data1);
255 const bool use = windowEvent.user.code != 0;
257 window->resizeable(use);
260 case SDL_EVENT_USER_WINDOW_FULLSCREEN:
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;
266 window->fullscreen(enter, forceOriginalDisplay);
269 case SDL_EVENT_USER_WINDOW_MINIMIZE:
270 if (!sdl->minimizeAllWindows())
271 throw ErrorMsg{ -1, windowEvent.type,
"sdl->minimizeAllWindows" };
273 case SDL_EVENT_USER_POINTER_NULL:
274 if (!sdl->setCursor(SdlContext::CURSOR_NULL))
275 throw ErrorMsg{ -1, windowEvent.type,
"sdl->setCursor" };
277 case SDL_EVENT_USER_POINTER_DEFAULT:
278 if (!sdl->setCursor(SdlContext::CURSOR_DEFAULT))
279 throw ErrorMsg{ -1, windowEvent.type,
"sdl->setCursor" };
281 case SDL_EVENT_USER_POINTER_POSITION:
284 static_cast<INT32
>(
reinterpret_cast<uintptr_t
>(windowEvent.user.data1));
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" };
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" };
296 case SDL_EVENT_USER_QUIT:
304 catch (ErrorMsg& msg)
306 WLog_Print(sdl->getWLog(), WLOG_ERROR,
"[exception] %s", msg.what());
316[[nodiscard]]
static BOOL sdl_client_global_init()
320 const DWORD wVersionRequested = MAKEWORD(1, 1);
321 const int rc = WSAStartup(wVersionRequested, &wsaData);
324 WLog_ERR(SDL_TAG,
"WSAStartup failed with %s [%d]", gai_strerrorA(rc), rc);
329 return (freerdp_handle_signals() == 0);
333static void sdl_client_global_uninit()
340[[nodiscard]]
static BOOL sdl_client_new(freerdp* instance, rdpContext* context)
344 if (!instance || !context)
348 return sdl->sdl !=
nullptr;
351static void sdl_client_free([[maybe_unused]] freerdp* instance, rdpContext* context)
361[[nodiscard]]
static int sdl_client_start(rdpContext* context)
363 auto sdl = get_context(context);
368[[nodiscard]]
static int sdl_client_stop(rdpContext* context)
370 auto sdl = get_context(context);
375static void RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
377 WINPR_ASSERT(pEntryPoints);
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;
385 pEntryPoints->ClientNew = sdl_client_new;
386 pEntryPoints->ClientFree = sdl_client_free;
387 pEntryPoints->ClientStart = sdl_client_start;
388 pEntryPoints->ClientStop = sdl_client_stop;
394 freerdp_client_context_free(&sdl->common.context);
397[[nodiscard]]
static const char* category2str(
int category)
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:
441 return "SDL_LOG_CATEGORY_CUSTOM";
445[[nodiscard]]
static SDL_LogPriority wloglevel2dl(DWORD level)
450 return SDL_LOG_PRIORITY_VERBOSE;
452 return SDL_LOG_PRIORITY_DEBUG;
454 return SDL_LOG_PRIORITY_INFO;
456 return SDL_LOG_PRIORITY_WARN;
458 return SDL_LOG_PRIORITY_ERROR;
460 return SDL_LOG_PRIORITY_CRITICAL;
463 return SDL_LOG_PRIORITY_VERBOSE;
467[[nodiscard]]
static DWORD sdlpriority2wlog(SDL_LogPriority priority)
469 DWORD level = WLOG_OFF;
472 case SDL_LOG_PRIORITY_VERBOSE:
475 case SDL_LOG_PRIORITY_DEBUG:
478 case SDL_LOG_PRIORITY_INFO:
481 case SDL_LOG_PRIORITY_WARN:
484 case SDL_LOG_PRIORITY_ERROR:
487 case SDL_LOG_PRIORITY_CRITICAL:
497static void SDLCALL winpr_LogOutputFunction(
void* userdata,
int category, SDL_LogPriority priority,
500 auto sdl =
static_cast<SdlContext*
>(userdata);
503 const DWORD level = sdlpriority2wlog(priority);
504 auto log = sdl->getWLog();
505 if (!WLog_IsLevelActive(log, level))
508 WLog_PrintTextMessage(log, level, __LINE__, __FILE__, __func__,
"[%s] %s",
509 category2str(category), message);
512static void sdl_quit()
514 const auto cat = SDL_LOG_CATEGORY_APPLICATION;
516 ev.type = SDL_EVENT_QUIT;
517 if (!SDL_PushEvent(&ev))
519 SDL_LogError(cat,
"An error occurred: %s", SDL_GetError());
523static void SDLCALL rdp_file_cb(
void* userdata,
const char*
const* filelist,
524 WINPR_ATTR_UNUSED
int filter)
526 auto rdp =
static_cast<std::string*
>(userdata);
528 const auto cat = SDL_LOG_CATEGORY_APPLICATION;
531 SDL_LogError(cat,
"An error occurred: %s", SDL_GetError());
537 SDL_LogError(cat,
"The user did not select any file.");
538 SDL_LogError(cat,
"Most likely, the dialog was canceled.");
545 SDL_LogError(cat,
"Full path to selected file: '%s'", *filelist);
553[[nodiscard]]
static std::string getRdpFile()
555 const auto flags = SDL_INIT_VIDEO | SDL_INIT_EVENTS;
556 SDL_DialogFileFilter filters[] = { {
"RDP files",
"rdp;rdpw" } };
560 if (!SDL_Init(flags))
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);
574 SDL_Event
event = {};
575 std::ignore = SDL_PollEvent(&event);
590int main(
int argc,
char* argv[])
593 RDP_CLIENT_ENTRY_POINTS clientEntryPoints = {};
596 RdpClientEntry(&clientEntryPoints);
598 reinterpret_cast<sdl_rdp_context*
>(freerdp_client_context_new(&clientEntryPoints)),
603 auto sdl = sdl_rdp->sdl;
605 auto settings = sdl->context()->settings;
606 WINPR_ASSERT(settings);
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]);
616 rdp_file = getRdpFile();
617 if (!rdp_file.empty())
619 args.push_back(rdp_file.data());
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();
628 rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv);
631 if (!sdl_list_monitors(sdl))
638 case COMMAND_LINE_STATUS_PRINT:
639 case COMMAND_LINE_STATUS_PRINT_VERSION:
640 case COMMAND_LINE_STATUS_PRINT_BUILDCONFIG:
642 case COMMAND_LINE_STATUS_PRINT_HELP:
643 SdlPref::print_config_file_help(3);
653 if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
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");
664 SDL_SetLogOutputFunction(winpr_LogOutputFunction, sdl);
665 auto level = WLog_GetLogLevel(sdl->getWLog());
666 SDL_SetLogPriorities(wloglevel2dl(level));
668 auto backend = SDL_GetCurrentVideoDriver();
669 WLog_Print(sdl->getWLog(), WLOG_DEBUG,
"client is using backend '%s'", backend);
677 freerdp_del_signal_cleanup_handler(sdl->context(), sdl_term_handler);
678 sdl_dialogs_uninit();
681 if (!sdl->detectDisplays())
685 auto context = sdl->context();
686 WINPR_ASSERT(context);
688 if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE))
691 if (freerdp_client_start(context) != 0)
696 if (freerdp_client_stop(context) != 0)
699 if (sdl->exitCode() != 0)
700 rc = sdl->exitCode();
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.