24#include <freerdp/config.h>
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>
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>
42#include <freerdp/log.h>
43#include <winpr/assert.h>
44#include <winpr/config.h>
46#include <winpr/synch.h>
49#if !defined(__MINGW32__)
50#include <SDL3/SDL_main.h>
52#include <SDL3/SDL_hints.h>
53#include <SDL3/SDL_oldnames.h>
54#include <SDL3/SDL_video.h>
56#include <sdl_config.hpp>
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"
70#define SDL_TAG CLIENT_TAG("SDL")
73class ErrorMsg :
public std::exception
76 ErrorMsg(
int rc, Uint32 type,
const std::string& msg,
77 const std::string& sdlError = SDL_GetError());
79 [[nodiscard]]
int rc()
const;
80 [[nodiscard]]
const char* what() const noexcept override;
86 std::
string _sdlError;
89const
char* ErrorMsg::what() const noexcept
91 return _buffer.c_str();
94int ErrorMsg::rc()
const
99ErrorMsg::ErrorMsg(
int rc, Uint32 type,
const std::string& msg,
const std::string& sdlError)
100 : _rc(rc), _type(type), _msg(msg), _sdlError(sdlError)
102 std::stringstream ss;
103 ss << _msg <<
" {" << sdl::utils::toString(_type) <<
"}{SDL:" << sdlError <<
"}";
107static void sdl_term_handler([[maybe_unused]]
int signum, [[maybe_unused]]
const char* signame,
108 [[maybe_unused]]
void* context)
110 std::ignore = sdl_push_quit();
113[[nodiscard]]
static int sdl_run(
SdlContext* sdl)
120 while (!sdl->shallAbort())
122 SDL_Event windowEvent = {};
123 while (!sdl->shallAbort() && SDL_WaitEventTimeout(
nullptr, 1000))
128 const int prc = SDL_PeepEvents(&windowEvent, 1, SDL_GETEVENT, SDL_EVENT_FIRST,
129 SDL_EVENT_USER_RETRY_DIALOG);
132 if (sdl_log_error(prc, sdl->getWLog(),
"SDL_PeepEvents"))
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);
140 if (sdl->shallAbort(
true))
143 if (sdl->getDialog().handleEvent(windowEvent))
146 if (!sdl->handleEvent(windowEvent))
147 throw ErrorMsg{ -1, windowEvent.type,
"sdl->handleEvent" };
149 switch (windowEvent.type)
152 std::ignore = freerdp_abort_connect_context(sdl->context());
154 case SDL_EVENT_USER_CERT_DIALOG:
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" };
163 case SDL_EVENT_USER_SHOW_DIALOG:
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" };
172 case SDL_EVENT_USER_SCARD_DIALOG:
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" };
181 case SDL_EVENT_USER_AUTH_DIALOG:
184 if (!sdl_auth_dialog_show(
186 throw ErrorMsg{ -1, windowEvent.type,
"sdl_auth_dialog_show" };
189 case SDL_EVENT_USER_UPDATE:
191 std::vector<SDL_Rect> rectangles;
194 rectangles = sdl->pop();
195 if (!sdl->drawToWindows(rectangles))
196 throw ErrorMsg{ -1, windowEvent.type,
"sdl->drawToWindows" };
197 }
while (!rectangles.empty());
200 case SDL_EVENT_USER_CREATE_WINDOWS:
202 auto ctx =
static_cast<SdlContext*
>(windowEvent.user.data1);
203 if (!ctx->createWindows())
204 throw ErrorMsg{ -1, windowEvent.type,
"sdl->createWindows" };
207 case SDL_EVENT_USER_WINDOW_RESIZEABLE:
209 auto window =
static_cast<SdlWindow*
>(windowEvent.user.data1);
210 const bool use = windowEvent.user.code != 0;
212 window->resizeable(use);
215 case SDL_EVENT_USER_WINDOW_FULLSCREEN:
217 auto window =
static_cast<SdlWindow*
>(windowEvent.user.data1);
218 const bool enter = windowEvent.user.code != 0;
220 window->fullscreen(enter);
223 case SDL_EVENT_USER_WINDOW_MINIMIZE:
224 if (!sdl->minimizeAllWindows())
225 throw ErrorMsg{ -1, windowEvent.type,
"sdl->minimizeAllWindows" };
227 case SDL_EVENT_USER_POINTER_NULL:
228 if (!sdl->setCursor(SdlContext::CURSOR_NULL))
229 throw ErrorMsg{ -1, windowEvent.type,
"sdl->setCursor" };
231 case SDL_EVENT_USER_POINTER_DEFAULT:
232 if (!sdl->setCursor(SdlContext::CURSOR_DEFAULT))
233 throw ErrorMsg{ -1, windowEvent.type,
"sdl->setCursor" };
235 case SDL_EVENT_USER_POINTER_POSITION:
238 static_cast<INT32
>(
reinterpret_cast<uintptr_t
>(windowEvent.user.data1));
240 static_cast<INT32
>(
reinterpret_cast<uintptr_t
>(windowEvent.user.data2));
241 if (!sdl->moveMouseTo(
242 {
static_cast<float>(x) * 1.0f,
static_cast<float>(y) * 1.0f }))
243 throw ErrorMsg{ -1, windowEvent.type,
"sdl->moveMouseTo" };
246 case SDL_EVENT_USER_POINTER_SET:
247 if (!sdl->setCursor(
static_cast<rdpPointer*
>(windowEvent.user.data1)))
248 throw ErrorMsg{ -1, windowEvent.type,
"sdl->setCursor" };
250 case SDL_EVENT_USER_QUIT:
258 catch (ErrorMsg& msg)
260 WLog_Print(sdl->getWLog(), WLOG_ERROR,
"[exception] %s", msg.what());
270[[nodiscard]]
static BOOL sdl_client_global_init()
274 const DWORD wVersionRequested = MAKEWORD(1, 1);
275 const int rc = WSAStartup(wVersionRequested, &wsaData);
278 WLog_ERR(SDL_TAG,
"WSAStartup failed with %s [%d]", gai_strerrorA(rc), rc);
283 return (freerdp_handle_signals() == 0);
287static void sdl_client_global_uninit()
294[[nodiscard]]
static BOOL sdl_client_new(freerdp* instance, rdpContext* context)
298 if (!instance || !context)
302 return sdl->sdl !=
nullptr;
305static void sdl_client_free([[maybe_unused]] freerdp* instance, rdpContext* context)
315[[nodiscard]]
static int sdl_client_start(rdpContext* context)
317 auto sdl = get_context(context);
322[[nodiscard]]
static int sdl_client_stop(rdpContext* context)
324 auto sdl = get_context(context);
329static void RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
331 WINPR_ASSERT(pEntryPoints);
333 ZeroMemory(pEntryPoints,
sizeof(RDP_CLIENT_ENTRY_POINTS));
334 pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION;
335 pEntryPoints->Size =
sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
336 pEntryPoints->GlobalInit = sdl_client_global_init;
337 pEntryPoints->GlobalUninit = sdl_client_global_uninit;
339 pEntryPoints->ClientNew = sdl_client_new;
340 pEntryPoints->ClientFree = sdl_client_free;
341 pEntryPoints->ClientStart = sdl_client_start;
342 pEntryPoints->ClientStop = sdl_client_stop;
348 freerdp_client_context_free(&sdl->common.context);
351[[nodiscard]]
static const char* category2str(
int category)
355 case SDL_LOG_CATEGORY_APPLICATION:
356 return "SDL_LOG_CATEGORY_APPLICATION";
357 case SDL_LOG_CATEGORY_ERROR:
358 return "SDL_LOG_CATEGORY_ERROR";
359 case SDL_LOG_CATEGORY_ASSERT:
360 return "SDL_LOG_CATEGORY_ASSERT";
361 case SDL_LOG_CATEGORY_SYSTEM:
362 return "SDL_LOG_CATEGORY_SYSTEM";
363 case SDL_LOG_CATEGORY_AUDIO:
364 return "SDL_LOG_CATEGORY_AUDIO";
365 case SDL_LOG_CATEGORY_VIDEO:
366 return "SDL_LOG_CATEGORY_VIDEO";
367 case SDL_LOG_CATEGORY_RENDER:
368 return "SDL_LOG_CATEGORY_RENDER";
369 case SDL_LOG_CATEGORY_INPUT:
370 return "SDL_LOG_CATEGORY_INPUT";
371 case SDL_LOG_CATEGORY_TEST:
372 return "SDL_LOG_CATEGORY_TEST";
373 case SDL_LOG_CATEGORY_GPU:
374 return "SDL_LOG_CATEGORY_GPU";
375 case SDL_LOG_CATEGORY_RESERVED2:
376 return "SDL_LOG_CATEGORY_RESERVED2";
377 case SDL_LOG_CATEGORY_RESERVED3:
378 return "SDL_LOG_CATEGORY_RESERVED3";
379 case SDL_LOG_CATEGORY_RESERVED4:
380 return "SDL_LOG_CATEGORY_RESERVED4";
381 case SDL_LOG_CATEGORY_RESERVED5:
382 return "SDL_LOG_CATEGORY_RESERVED5";
383 case SDL_LOG_CATEGORY_RESERVED6:
384 return "SDL_LOG_CATEGORY_RESERVED6";
385 case SDL_LOG_CATEGORY_RESERVED7:
386 return "SDL_LOG_CATEGORY_RESERVED7";
387 case SDL_LOG_CATEGORY_RESERVED8:
388 return "SDL_LOG_CATEGORY_RESERVED8";
389 case SDL_LOG_CATEGORY_RESERVED9:
390 return "SDL_LOG_CATEGORY_RESERVED9";
391 case SDL_LOG_CATEGORY_RESERVED10:
392 return "SDL_LOG_CATEGORY_RESERVED10";
393 case SDL_LOG_CATEGORY_CUSTOM:
395 return "SDL_LOG_CATEGORY_CUSTOM";
399[[nodiscard]]
static SDL_LogPriority wloglevel2dl(DWORD level)
404 return SDL_LOG_PRIORITY_VERBOSE;
406 return SDL_LOG_PRIORITY_DEBUG;
408 return SDL_LOG_PRIORITY_INFO;
410 return SDL_LOG_PRIORITY_WARN;
412 return SDL_LOG_PRIORITY_ERROR;
414 return SDL_LOG_PRIORITY_CRITICAL;
417 return SDL_LOG_PRIORITY_VERBOSE;
421[[nodiscard]]
static DWORD sdlpriority2wlog(SDL_LogPriority priority)
423 DWORD level = WLOG_OFF;
426 case SDL_LOG_PRIORITY_VERBOSE:
429 case SDL_LOG_PRIORITY_DEBUG:
432 case SDL_LOG_PRIORITY_INFO:
435 case SDL_LOG_PRIORITY_WARN:
438 case SDL_LOG_PRIORITY_ERROR:
441 case SDL_LOG_PRIORITY_CRITICAL:
451static void SDLCALL winpr_LogOutputFunction(
void* userdata,
int category, SDL_LogPriority priority,
454 auto sdl =
static_cast<SdlContext*
>(userdata);
457 const DWORD level = sdlpriority2wlog(priority);
458 auto log = sdl->getWLog();
459 if (!WLog_IsLevelActive(log, level))
462 WLog_PrintTextMessage(log, level, __LINE__, __FILE__, __func__,
"[%s] %s",
463 category2str(category), message);
466static void sdl_quit()
469 ev.type = SDL_EVENT_QUIT;
470 if (!SDL_PushEvent(&ev))
472 SDL_Log(
"An error occurred: %s", SDL_GetError());
476static void SDLCALL rdp_file_cb(
void* userdata,
const char*
const* filelist,
477 WINPR_ATTR_UNUSED
int filter)
479 auto rdp =
static_cast<std::string*
>(userdata);
483 SDL_Log(
"An error occurred: %s", SDL_GetError());
489 SDL_Log(
"The user did not select any file.");
490 SDL_Log(
"Most likely, the dialog was canceled.");
497 SDL_Log(
"Full path to selected file: '%s'", *filelist);
505[[nodiscard]]
static std::string getRdpFile()
507 const auto flags = SDL_INIT_VIDEO | SDL_INIT_EVENTS;
508 SDL_DialogFileFilter filters[] = { {
"RDP files",
"rdp;rdpw" } };
512 if (!SDL_Init(flags))
515 auto props = SDL_CreateProperties();
516 SDL_SetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING,
517 "SDL Freerdp - Open a RDP file");
518 SDL_SetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN,
false);
519 SDL_SetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, filters);
520 SDL_SetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, ARRAYSIZE(filters));
521 SDL_ShowFileDialogWithProperties(SDL_FILEDIALOG_OPENFILE, rdp_file_cb, &rdp, props);
522 SDL_DestroyProperties(props);
526 SDL_Event
event = {};
527 std::ignore = SDL_PollEvent(&event);
542int main(
int argc,
char* argv[])
545 RDP_CLIENT_ENTRY_POINTS clientEntryPoints = {};
548 RdpClientEntry(&clientEntryPoints);
550 reinterpret_cast<sdl_rdp_context*
>(freerdp_client_context_new(&clientEntryPoints)),
555 auto sdl = sdl_rdp->sdl;
557 auto settings = sdl->context()->settings;
558 WINPR_ASSERT(settings);
560 std::string rdp_file;
561 std::vector<char*> args;
562 args.reserve(WINPR_ASSERTING_INT_CAST(
size_t, argc));
563 for (
auto x = 0; x < argc; x++)
564 args.push_back(argv[x]);
568 rdp_file = getRdpFile();
569 if (!rdp_file.empty())
571 args.push_back(rdp_file.data());
575 auto status = freerdp_client_settings_parse_command_line(
576 settings, WINPR_ASSERTING_INT_CAST(
int, args.size()), args.data(), FALSE);
577 sdl_rdp->sdl->setMetadata();
580 rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv);
583 if (!sdl_list_monitors(sdl))
590 case COMMAND_LINE_STATUS_PRINT:
591 case COMMAND_LINE_STATUS_PRINT_VERSION:
592 case COMMAND_LINE_STATUS_PRINT_BUILDCONFIG:
594 case COMMAND_LINE_STATUS_PRINT_HELP:
596 SdlPref::print_config_file_help(3);
604 if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
608 SDL_SetLogOutputFunction(winpr_LogOutputFunction, sdl);
609 auto level = WLog_GetLogLevel(sdl->getWLog());
610 SDL_SetLogPriorities(wloglevel2dl(level));
612 auto backend = SDL_GetCurrentVideoDriver();
613 WLog_Print(sdl->getWLog(), WLOG_DEBUG,
"client is using backend '%s'", backend);
616 SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED,
"0");
617 SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR,
"0");
618 SDL_SetHint(SDL_HINT_PEN_MOUSE_EVENTS,
"0");
619 SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS,
"0");
620 SDL_SetHint(SDL_HINT_PEN_TOUCH_EVENTS,
"1");
621 SDL_SetHint(SDL_HINT_TRACKPAD_IS_TOUCH_ONLY,
"1");
628 freerdp_del_signal_cleanup_handler(sdl->context(), sdl_term_handler);
629 sdl_dialogs_uninit();
634 auto context = sdl->context();
635 WINPR_ASSERT(context);
637 if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE))
640 if (freerdp_client_start(context) != 0)
645 if (freerdp_client_stop(context) != 0)
648 if (sdl->exitCode() != 0)
649 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.