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"
78#include "sdl_win32_console.hpp"
80#include "sdl_utils.hpp"
83#define SDL_TAG CLIENT_TAG("SDL")
86class ErrorMsg :
public std::exception
89 ErrorMsg(
int rc, Uint32 type,
const std::string& msg,
90 const std::string& sdlError = SDL_GetError()
91#
if defined(__cpp_lib_source_location) && (__cpp_lib_source_location >= 201907L)
93 std::source_location loc = std::source_location::current()
94#elif defined(__GNUC__)
96 const std::string& file = __builtin_FILE(),
97 const std::string& fkt = __builtin_FUNCTION(),
size_t line = __builtin_LINE(),
99 size_t column = __builtin_COLUMN()
106 [[nodiscard]]
int rc()
const;
107 [[nodiscard]]
const char* what() const noexcept override;
113 std::
string _sdlError;
121const
char* ErrorMsg::what() const noexcept
123 return _buffer.c_str();
126int ErrorMsg::rc()
const
131ErrorMsg::ErrorMsg(
int rc, Uint32 type,
const std::string& msg,
const std::string& sdlError
132#
if defined(__cpp_lib_source_location) && (__cpp_lib_source_location >= 201907L)
134 std::source_location loc
135#elif defined(__GNUC__)
137 const std::string& file,
const std::string& fkt,
size_t line,
size_t column
140 : _rc(rc), _type(type), _msg(msg), _sdlError(sdlError)
141#if defined(__cpp_lib_source_location) && (__cpp_lib_source_location >= 201907L)
143 _file(loc.file_name()), _fkt(loc.function_name()), _line(loc.line()), _column(loc.column())
144#elif defined(__GNUC__)
146 _file(file), _fkt(fkt), _line(line), _column(column)
149 std::stringstream ss;
150 ss << _msg <<
" {" << sdl::utils::toString(_type) <<
"}{SDL:" << sdlError <<
"}";
151 ss <<
"{" << _fkt <<
" [" << _file <<
":" << _line <<
"-" << _column <<
"]}";
155static void sdl_term_handler([[maybe_unused]]
int signum, [[maybe_unused]]
const char* signame,
156 [[maybe_unused]]
void* context)
158 std::ignore = sdl_push_quit();
168 while (!
sdl->shallAbort())
170 SDL_Event windowEvent = {};
171 while (!
sdl->shallAbort() && SDL_WaitEventTimeout(
nullptr, 1000))
176 const int prc = SDL_PeepEvents(&windowEvent, 1, SDL_GETEVENT, SDL_EVENT_FIRST,
177 SDL_EVENT_USER_RETRY_DIALOG);
180 if (sdl_log_error(prc,
sdl->getWLog(),
"SDL_PeepEvents"))
184 WLog_Print(
sdl->getWLog(), WLOG_TRACE,
"got event %s [0x%08" PRIx32
"]",
185 sdl::utils::toString(windowEvent.type).c_str(), windowEvent.type);
188 if (
sdl->getDialog().handleEvent(windowEvent))
191 if (
sdl->shallAbort(
true))
194 if (!
sdl->handleEvent(windowEvent))
195 throw ErrorMsg{ -1, windowEvent.type,
"sdl->handleEvent" };
197 switch (windowEvent.type)
200 std::ignore = freerdp_abort_connect_context(
sdl->context());
202 case SDL_EVENT_USER_CERT_DIALOG:
205 auto title =
static_cast<const char*
>(windowEvent.user.data1);
206 auto msg =
static_cast<const char*
>(windowEvent.user.data2);
207 if (!sdl_cert_dialog_show(title, msg))
208 throw ErrorMsg{ -1, windowEvent.type,
"sdl_cert_dialog_show" };
211 case SDL_EVENT_USER_SHOW_DIALOG:
214 auto title =
static_cast<const char*
>(windowEvent.user.data1);
215 auto msg =
static_cast<const char*
>(windowEvent.user.data2);
216 if (!sdl_message_dialog_show(title, msg, windowEvent.user.code))
217 throw ErrorMsg{ -1, windowEvent.type,
"sdl_message_dialog_show" };
220 case SDL_EVENT_USER_SCARD_DIALOG:
223 auto title =
static_cast<const char*
>(windowEvent.user.data1);
224 auto msg =
static_cast<const char**
>(windowEvent.user.data2);
225 if (!sdl_scard_dialog_show(title, windowEvent.user.code, msg))
226 throw ErrorMsg{ -1, windowEvent.type,
"sdl_scard_dialog_show" };
229 case SDL_EVENT_USER_AUTH_DIALOG:
232 if (!sdl_auth_dialog_show(
234 throw ErrorMsg{ -1, windowEvent.type,
"sdl_auth_dialog_show" };
237 case SDL_EVENT_USER_UPDATE:
239 std::vector<SDL_Rect> rectangles;
242 rectangles =
sdl->pop();
243 if (!
sdl->drawToWindows(rectangles))
244 throw ErrorMsg{ -1, windowEvent.type,
"sdl->drawToWindows" };
245 }
while (!rectangles.empty());
248 case SDL_EVENT_USER_CREATE_WINDOWS:
250 auto ctx =
static_cast<SdlContext*
>(windowEvent.user.data1);
251 if (!ctx->createWindows())
252 throw ErrorMsg{ -1, windowEvent.type,
"sdl->createWindows" };
255 case SDL_EVENT_USER_WINDOW_RESIZEABLE:
257 auto window =
static_cast<SdlWindow*
>(windowEvent.user.data1);
258 const bool use = windowEvent.user.code != 0;
260 window->resizeable(use);
263 case SDL_EVENT_USER_WINDOW_FULLSCREEN:
265 auto window =
static_cast<SdlWindow*
>(windowEvent.user.data1);
266 const bool enter = windowEvent.user.code != 0;
267 const bool forceOriginalDisplay = windowEvent.user.data2 !=
nullptr;
269 window->fullscreen(enter, forceOriginalDisplay);
272 case SDL_EVENT_USER_WINDOW_MINIMIZE:
273 if (!
sdl->minimizeAllWindows())
274 throw ErrorMsg{ -1, windowEvent.type,
"sdl->minimizeAllWindows" };
276 case SDL_EVENT_USER_POINTER_NULL:
277 if (!
sdl->setCursor(SdlContext::CURSOR_NULL))
278 throw ErrorMsg{ -1, windowEvent.type,
"sdl->setCursor" };
280 case SDL_EVENT_USER_POINTER_DEFAULT:
281 if (!
sdl->setCursor(SdlContext::CURSOR_DEFAULT))
282 throw ErrorMsg{ -1, windowEvent.type,
"sdl->setCursor" };
284 case SDL_EVENT_USER_POINTER_POSITION:
287 static_cast<INT32
>(
reinterpret_cast<uintptr_t
>(windowEvent.user.data1));
289 static_cast<INT32
>(
reinterpret_cast<uintptr_t
>(windowEvent.user.data2));
290 if (!
sdl->moveMouseTo(
291 {
static_cast<float>(x) * 1.0f,
static_cast<float>(y) * 1.0f }))
292 throw ErrorMsg{ -1, windowEvent.type,
"sdl->moveMouseTo" };
295 case SDL_EVENT_USER_POINTER_SET:
296 if (!
sdl->setCursor(
static_cast<rdpPointer*
>(windowEvent.user.data1)))
297 throw ErrorMsg{ -1, windowEvent.type,
"sdl->setCursor" };
299 case SDL_EVENT_USER_RAIL_MOVE:
301 const auto id =
static_cast<uint32_t
>(
302 reinterpret_cast<uintptr_t
>(windowEvent.user.data1));
303 const auto moveType =
static_cast<uint16_t
>(windowEvent.user.code);
304 sdl->getRailChannelContext().handleLocalMoveRequested(
id, moveType);
307 case SDL_EVENT_USER_QUIT:
315 catch (ErrorMsg& msg)
317 WLog_Print(
sdl->getWLog(), WLOG_ERROR,
"[exception] %s", msg.what());
327[[nodiscard]]
static BOOL sdl_client_global_init()
331 const DWORD wVersionRequested = MAKEWORD(1, 1);
332 const int rc = WSAStartup(wVersionRequested, &wsaData);
335 WLog_ERR(SDL_TAG,
"WSAStartup failed with %s [%d]", gai_strerrorA(rc), rc);
340 return (freerdp_handle_signals() == 0);
344static void sdl_client_global_uninit()
351[[nodiscard]]
static BOOL sdl_client_new(freerdp* instance, rdpContext* context)
355 if (!instance || !context)
359 return sdl->sdl !=
nullptr;
362static void sdl_client_free([[maybe_unused]] freerdp* instance, rdpContext* context)
372[[nodiscard]]
static int sdl_client_start(rdpContext* context)
374 auto sdl = get_context(context);
379[[nodiscard]]
static int sdl_client_stop(rdpContext* context)
381 const int rc = freerdp_client_common_stop(context);
382 auto sdl = get_context(context);
384 const int rc2 =
sdl->join();
390static void RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
392 WINPR_ASSERT(pEntryPoints);
394 ZeroMemory(pEntryPoints,
sizeof(RDP_CLIENT_ENTRY_POINTS));
395 pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION;
396 pEntryPoints->Size =
sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
397 pEntryPoints->GlobalInit = sdl_client_global_init;
398 pEntryPoints->GlobalUninit = sdl_client_global_uninit;
400 pEntryPoints->ClientNew = sdl_client_new;
401 pEntryPoints->ClientFree = sdl_client_free;
402 pEntryPoints->ClientStart = sdl_client_start;
403 pEntryPoints->ClientStop = sdl_client_stop;
409 freerdp_client_context_free(&
sdl->common.context);
412[[nodiscard]]
static const char* category2str(
int category)
416 case SDL_LOG_CATEGORY_APPLICATION:
417 return "SDL_LOG_CATEGORY_APPLICATION";
418 case SDL_LOG_CATEGORY_ERROR:
419 return "SDL_LOG_CATEGORY_ERROR";
420 case SDL_LOG_CATEGORY_ASSERT:
421 return "SDL_LOG_CATEGORY_ASSERT";
422 case SDL_LOG_CATEGORY_SYSTEM:
423 return "SDL_LOG_CATEGORY_SYSTEM";
424 case SDL_LOG_CATEGORY_AUDIO:
425 return "SDL_LOG_CATEGORY_AUDIO";
426 case SDL_LOG_CATEGORY_VIDEO:
427 return "SDL_LOG_CATEGORY_VIDEO";
428 case SDL_LOG_CATEGORY_RENDER:
429 return "SDL_LOG_CATEGORY_RENDER";
430 case SDL_LOG_CATEGORY_INPUT:
431 return "SDL_LOG_CATEGORY_INPUT";
432 case SDL_LOG_CATEGORY_TEST:
433 return "SDL_LOG_CATEGORY_TEST";
434 case SDL_LOG_CATEGORY_GPU:
435 return "SDL_LOG_CATEGORY_GPU";
436 case SDL_LOG_CATEGORY_RESERVED2:
437 return "SDL_LOG_CATEGORY_RESERVED2";
438 case SDL_LOG_CATEGORY_RESERVED3:
439 return "SDL_LOG_CATEGORY_RESERVED3";
440 case SDL_LOG_CATEGORY_RESERVED4:
441 return "SDL_LOG_CATEGORY_RESERVED4";
442 case SDL_LOG_CATEGORY_RESERVED5:
443 return "SDL_LOG_CATEGORY_RESERVED5";
444 case SDL_LOG_CATEGORY_RESERVED6:
445 return "SDL_LOG_CATEGORY_RESERVED6";
446 case SDL_LOG_CATEGORY_RESERVED7:
447 return "SDL_LOG_CATEGORY_RESERVED7";
448 case SDL_LOG_CATEGORY_RESERVED8:
449 return "SDL_LOG_CATEGORY_RESERVED8";
450 case SDL_LOG_CATEGORY_RESERVED9:
451 return "SDL_LOG_CATEGORY_RESERVED9";
452 case SDL_LOG_CATEGORY_RESERVED10:
453 return "SDL_LOG_CATEGORY_RESERVED10";
454 case SDL_LOG_CATEGORY_CUSTOM:
456 return "SDL_LOG_CATEGORY_CUSTOM";
460[[nodiscard]]
static SDL_LogPriority wloglevel2dl(DWORD level)
465 return SDL_LOG_PRIORITY_VERBOSE;
467 return SDL_LOG_PRIORITY_DEBUG;
469 return SDL_LOG_PRIORITY_INFO;
471 return SDL_LOG_PRIORITY_WARN;
473 return SDL_LOG_PRIORITY_ERROR;
475 return SDL_LOG_PRIORITY_CRITICAL;
478 return SDL_LOG_PRIORITY_VERBOSE;
482[[nodiscard]]
static DWORD sdlpriority2wlog(SDL_LogPriority priority)
484 DWORD level = WLOG_OFF;
487 case SDL_LOG_PRIORITY_VERBOSE:
490 case SDL_LOG_PRIORITY_DEBUG:
493 case SDL_LOG_PRIORITY_INFO:
496 case SDL_LOG_PRIORITY_WARN:
499 case SDL_LOG_PRIORITY_ERROR:
502 case SDL_LOG_PRIORITY_CRITICAL:
512static void SDLCALL winpr_LogOutputFunction(
void* userdata,
int category, SDL_LogPriority priority,
518 const DWORD level = sdlpriority2wlog(priority);
519 auto log =
sdl->getWLog();
520 if (!WLog_IsLevelActive(log, level))
523 WLog_PrintTextMessage(log, level, __LINE__, __FILE__, __func__,
"[%s] %s",
524 category2str(category), message);
527static void sdl_quit()
529 const auto cat = SDL_LOG_CATEGORY_APPLICATION;
531 ev.type = SDL_EVENT_QUIT;
532 if (!SDL_PushEvent(&ev))
534 SDL_LogError(cat,
"An error occurred: %s", SDL_GetError());
538static void SDLCALL rdp_file_cb(
void* userdata,
const char*
const* filelist,
539 WINPR_ATTR_UNUSED
int filter)
541 auto rdp =
static_cast<std::string*
>(userdata);
543 const auto cat = SDL_LOG_CATEGORY_APPLICATION;
546 SDL_LogError(cat,
"An error occurred: %s", SDL_GetError());
552 SDL_LogError(cat,
"The user did not select any file.");
553 SDL_LogError(cat,
"Most likely, the dialog was canceled.");
560 SDL_LogError(cat,
"Full path to selected file: '%s'", *filelist);
568[[nodiscard]]
static std::string getRdpFile()
570 const auto flags = SDL_INIT_VIDEO | SDL_INIT_EVENTS;
571 SDL_DialogFileFilter filters[] = { {
"RDP files",
"rdp;rdpw" } };
575 if (!SDL_Init(flags))
578 auto props = SDL_CreateProperties();
579 SDL_SetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING,
580 "SDL Freerdp - Open a RDP file");
581 SDL_SetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN,
false);
582 SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, filters);
583 SDL_SetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, ARRAYSIZE(filters));
584 SDL_ShowFileDialogWithProperties(SDL_FILEDIALOG_OPENFILE, rdp_file_cb, &rdp, props);
585 SDL_DestroyProperties(props);
589 SDL_Event
event = {};
590 std::ignore = SDL_PollEvent(&event);
605int main(
int argc,
char* argv[])
608 sdl::win32::release_transient_console();
612 RDP_CLIENT_ENTRY_POINTS clientEntryPoints = {};
615 RdpClientEntry(&clientEntryPoints);
617 reinterpret_cast<sdl_rdp_context*
>(freerdp_client_context_new(&clientEntryPoints)),
622 auto sdl = sdl_rdp->sdl;
624 auto settings =
sdl->context()->settings;
625 WINPR_ASSERT(settings);
627 std::string rdp_file;
628 std::vector<char*> args;
629 args.reserve(WINPR_ASSERTING_INT_CAST(
size_t, argc));
630 for (
auto x = 0; x < argc; x++)
631 args.push_back(argv[x]);
635 rdp_file = getRdpFile();
636 if (!rdp_file.empty())
638 args.push_back(rdp_file.data());
642 auto status = freerdp_client_settings_parse_command_line_ex(
643 settings, WINPR_ASSERTING_INT_CAST(
int, args.size()), args.data(), FALSE,
sdl->args(),
644 sdl->argsCount(), &SdlContext::argumentHandler,
sdl);
645 sdl_rdp->sdl->setMetadata();
648 rc = freerdp_client_settings_command_line_status_print_ex(settings, status, argc, argv,
652 if (!sdl_list_monitors(
sdl))
659 case COMMAND_LINE_STATUS_PRINT:
660 case COMMAND_LINE_STATUS_PRINT_VERSION:
661 case COMMAND_LINE_STATUS_PRINT_BUILDCONFIG:
663 case COMMAND_LINE_STATUS_PRINT_HELP:
664 SdlPref::print_config_file_help(3);
673 if (!SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED,
"0"))
675 if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR,
"0"))
677 if (!SDL_SetHint(SDL_HINT_PEN_MOUSE_EVENTS,
"0"))
679 if (!SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS,
"0"))
681 if (!SDL_SetHint(SDL_HINT_PEN_TOUCH_EVENTS,
"1"))
683 if (!SDL_SetHint(SDL_HINT_TRACKPAD_IS_TOUCH_ONLY,
"1"))
685 if (!SDL_SetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME,
"RDP session running"))
687#if SDL_VERSION_ATLEAST(3, 4, 0)
688 if (!SDL_SetHint(SDL_HINT_MOUSE_DPI_SCALE_CURSORS,
"1"))
694 std::ignore = SDL_SetHint(SDL_HINT_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY,
"1");
699 !SDL_SetHint(SDL_HINT_RENDER_VSYNC,
"1"))
704 if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
707#if defined(__APPLE__)
710 SDL_SetModState(SDL_GetModState() | SDL_KMOD_NUM);
714 SDL_SetLogOutputFunction(winpr_LogOutputFunction,
sdl);
715 auto level = WLog_GetLogLevel(
sdl->getWLog());
716 SDL_SetLogPriorities(wloglevel2dl(level));
718 auto backend = SDL_GetCurrentVideoDriver();
719 WLog_Print(
sdl->getWLog(), WLOG_DEBUG,
"client is using backend '%s'", backend);
727 freerdp_del_signal_cleanup_handler(
sdl->context(), sdl_term_handler);
728 sdl_dialogs_uninit();
731 if (!freerdp_add_signal_cleanup_handler(
sdl->context(), sdl_term_handler))
733 if (!
sdl->detectDisplays())
737 auto context =
sdl->context();
738 WINPR_ASSERT(context);
740 if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE))
743 if (freerdp_client_start(context) != 0)
748 if (freerdp_client_stop(context) != 0)
751 if (
sdl->exitCode() != 0)
752 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.