FreeRDP
Loading...
Searching...
No Matches
SDL3/sdl_freerdp.cpp
1
20#include <memory>
21#include <mutex>
22#include <iostream>
23
24#include <freerdp/config.h>
25
26#include <cerrno>
27#include <cstdio>
28#include <cstring>
29
30#include <freerdp/freerdp.h>
31#include <freerdp/constants.h>
32#include <freerdp/gdi/gdi.h>
33#include <freerdp/streamdump.h>
34#include <freerdp/utils/signal.h>
35
36#include <freerdp/client/file.h>
37#include <freerdp/client/cmdline.h>
38#include <freerdp/client/cliprdr.h>
39#include <freerdp/client/channels.h>
40#include <freerdp/channels/channels.h>
41
42#include <winpr/crt.h>
43#include <winpr/config.h>
44#include <winpr/assert.h>
45#include <winpr/synch.h>
46#include <freerdp/log.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_video.h>
54#include <SDL3/SDL_oldnames.h>
55
56#include "sdl_channels.hpp"
57#include "sdl_freerdp.hpp"
58#include "sdl_utils.hpp"
59#include "sdl_disp.hpp"
60#include "sdl_monitor.hpp"
61#include "sdl_kbd.hpp"
62#include "sdl_touch.hpp"
63#include "sdl_pointer.hpp"
64#include "sdl_prefs.hpp"
65#include "dialogs/sdl_dialogs.hpp"
66#include "scoped_guard.hpp"
67
68#include <aad/sdl_webview.hpp>
69
70#define SDL_TAG CLIENT_TAG("SDL")
71
72enum SDL_EXIT_CODE
73{
74 /* section 0-15: protocol-independent codes */
75 SDL_EXIT_SUCCESS = 0,
76 SDL_EXIT_DISCONNECT = 1,
77 SDL_EXIT_LOGOFF = 2,
78 SDL_EXIT_IDLE_TIMEOUT = 3,
79 SDL_EXIT_LOGON_TIMEOUT = 4,
80 SDL_EXIT_CONN_REPLACED = 5,
81 SDL_EXIT_OUT_OF_MEMORY = 6,
82 SDL_EXIT_CONN_DENIED = 7,
83 SDL_EXIT_CONN_DENIED_FIPS = 8,
84 SDL_EXIT_USER_PRIVILEGES = 9,
85 SDL_EXIT_FRESH_CREDENTIALS_REQUIRED = 10,
86 SDL_EXIT_DISCONNECT_BY_USER = 11,
87
88 /* section 16-31: license error set */
89 SDL_EXIT_LICENSE_INTERNAL = 16,
90 SDL_EXIT_LICENSE_NO_LICENSE_SERVER = 17,
91 SDL_EXIT_LICENSE_NO_LICENSE = 18,
92 SDL_EXIT_LICENSE_BAD_CLIENT_MSG = 19,
93 SDL_EXIT_LICENSE_HWID_DOESNT_MATCH = 20,
94 SDL_EXIT_LICENSE_BAD_CLIENT = 21,
95 SDL_EXIT_LICENSE_CANT_FINISH_PROTOCOL = 22,
96 SDL_EXIT_LICENSE_CLIENT_ENDED_PROTOCOL = 23,
97 SDL_EXIT_LICENSE_BAD_CLIENT_ENCRYPTION = 24,
98 SDL_EXIT_LICENSE_CANT_UPGRADE = 25,
99 SDL_EXIT_LICENSE_NO_REMOTE_CONNECTIONS = 26,
100
101 /* section 32-127: RDP protocol error set */
102 SDL_EXIT_RDP = 32,
103
104 /* section 128-254: xfreerdp specific exit codes */
105 SDL_EXIT_PARSE_ARGUMENTS = 128,
106 SDL_EXIT_MEMORY = 129,
107 SDL_EXIT_PROTOCOL = 130,
108 SDL_EXIT_CONN_FAILED = 131,
109 SDL_EXIT_AUTH_FAILURE = 132,
110 SDL_EXIT_NEGO_FAILURE = 133,
111 SDL_EXIT_LOGON_FAILURE = 134,
112 SDL_EXIT_ACCOUNT_LOCKED_OUT = 135,
113 SDL_EXIT_PRE_CONNECT_FAILED = 136,
114 SDL_EXIT_CONNECT_UNDEFINED = 137,
115 SDL_EXIT_POST_CONNECT_FAILED = 138,
116 SDL_EXIT_DNS_ERROR = 139,
117 SDL_EXIT_DNS_NAME_NOT_FOUND = 140,
118 SDL_EXIT_CONNECT_FAILED = 141,
119 SDL_EXIT_MCS_CONNECT_INITIAL_ERROR = 142,
120 SDL_EXIT_TLS_CONNECT_FAILED = 143,
121 SDL_EXIT_INSUFFICIENT_PRIVILEGES = 144,
122 SDL_EXIT_CONNECT_CANCELLED = 145,
123
124 SDL_EXIT_CONNECT_TRANSPORT_FAILED = 147,
125 SDL_EXIT_CONNECT_PASSWORD_EXPIRED = 148,
126 SDL_EXIT_CONNECT_PASSWORD_MUST_CHANGE = 149,
127 SDL_EXIT_CONNECT_KDC_UNREACHABLE = 150,
128 SDL_EXIT_CONNECT_ACCOUNT_DISABLED = 151,
129 SDL_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED = 152,
130 SDL_EXIT_CONNECT_CLIENT_REVOKED = 153,
131 SDL_EXIT_CONNECT_WRONG_PASSWORD = 154,
132 SDL_EXIT_CONNECT_ACCESS_DENIED = 155,
133 SDL_EXIT_CONNECT_ACCOUNT_RESTRICTION = 156,
134 SDL_EXIT_CONNECT_ACCOUNT_EXPIRED = 157,
135 SDL_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED = 158,
136 SDL_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS = 159,
137
138 SDL_EXIT_UNKNOWN = 255,
139};
140
141struct sdl_exit_code_map_t
142{
143 DWORD error;
144 int code;
145 const char* code_tag;
146};
147
148#define ENTRY(x, y) \
149 { \
150 x, y, #y \
151 }
152static const struct sdl_exit_code_map_t sdl_exit_code_map[] = {
153 ENTRY(FREERDP_ERROR_SUCCESS, SDL_EXIT_SUCCESS), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_DISCONNECT),
154 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LOGOFF), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_IDLE_TIMEOUT),
155 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LOGON_TIMEOUT),
156 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_REPLACED),
157 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_OUT_OF_MEMORY),
158 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_DENIED),
159 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_DENIED_FIPS),
160 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_USER_PRIVILEGES),
161 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_FRESH_CREDENTIALS_REQUIRED),
162 ENTRY(ERRINFO_LOGOFF_BY_USER, SDL_EXIT_DISCONNECT_BY_USER),
163 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_UNKNOWN),
164
165 /* section 16-31: license error set */
166 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_INTERNAL),
167 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_LICENSE_SERVER),
168 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_LICENSE),
169 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT_MSG),
170 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_HWID_DOESNT_MATCH),
171 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT),
172 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_FINISH_PROTOCOL),
173 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CLIENT_ENDED_PROTOCOL),
174 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT_ENCRYPTION),
175 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_UPGRADE),
176 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_REMOTE_CONNECTIONS),
177 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_UPGRADE),
178
179 /* section 32-127: RDP protocol error set */
180 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_RDP),
181
182 /* section 128-254: xfreerdp specific exit codes */
183 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_PARSE_ARGUMENTS), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_MEMORY),
184 ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_PROTOCOL), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_FAILED),
185
186 ENTRY(FREERDP_ERROR_AUTHENTICATION_FAILED, SDL_EXIT_AUTH_FAILURE),
187 ENTRY(FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED, SDL_EXIT_NEGO_FAILURE),
188 ENTRY(FREERDP_ERROR_CONNECT_LOGON_FAILURE, SDL_EXIT_LOGON_FAILURE),
189 ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT, SDL_EXIT_ACCOUNT_LOCKED_OUT),
190 ENTRY(FREERDP_ERROR_PRE_CONNECT_FAILED, SDL_EXIT_PRE_CONNECT_FAILED),
191 ENTRY(FREERDP_ERROR_CONNECT_UNDEFINED, SDL_EXIT_CONNECT_UNDEFINED),
192 ENTRY(FREERDP_ERROR_POST_CONNECT_FAILED, SDL_EXIT_POST_CONNECT_FAILED),
193 ENTRY(FREERDP_ERROR_DNS_ERROR, SDL_EXIT_DNS_ERROR),
194 ENTRY(FREERDP_ERROR_DNS_NAME_NOT_FOUND, SDL_EXIT_DNS_NAME_NOT_FOUND),
195 ENTRY(FREERDP_ERROR_CONNECT_FAILED, SDL_EXIT_CONNECT_FAILED),
196 ENTRY(FREERDP_ERROR_MCS_CONNECT_INITIAL_ERROR, SDL_EXIT_MCS_CONNECT_INITIAL_ERROR),
197 ENTRY(FREERDP_ERROR_TLS_CONNECT_FAILED, SDL_EXIT_TLS_CONNECT_FAILED),
198 ENTRY(FREERDP_ERROR_INSUFFICIENT_PRIVILEGES, SDL_EXIT_INSUFFICIENT_PRIVILEGES),
199 ENTRY(FREERDP_ERROR_CONNECT_CANCELLED, SDL_EXIT_CONNECT_CANCELLED),
200 ENTRY(FREERDP_ERROR_CONNECT_TRANSPORT_FAILED, SDL_EXIT_CONNECT_TRANSPORT_FAILED),
201 ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED, SDL_EXIT_CONNECT_PASSWORD_EXPIRED),
202 ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE, SDL_EXIT_CONNECT_PASSWORD_MUST_CHANGE),
203 ENTRY(FREERDP_ERROR_CONNECT_KDC_UNREACHABLE, SDL_EXIT_CONNECT_KDC_UNREACHABLE),
204 ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED, SDL_EXIT_CONNECT_ACCOUNT_DISABLED),
205 ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_CERTAINLY_EXPIRED,
206 SDL_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED),
207 ENTRY(FREERDP_ERROR_CONNECT_CLIENT_REVOKED, SDL_EXIT_CONNECT_CLIENT_REVOKED),
208 ENTRY(FREERDP_ERROR_CONNECT_WRONG_PASSWORD, SDL_EXIT_CONNECT_WRONG_PASSWORD),
209 ENTRY(FREERDP_ERROR_CONNECT_ACCESS_DENIED, SDL_EXIT_CONNECT_ACCESS_DENIED),
210 ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION, SDL_EXIT_CONNECT_ACCOUNT_RESTRICTION),
211 ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED, SDL_EXIT_CONNECT_ACCOUNT_EXPIRED),
212 ENTRY(FREERDP_ERROR_CONNECT_LOGON_TYPE_NOT_GRANTED, SDL_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED),
213 ENTRY(FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS,
214 SDL_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS)
215};
216
217static const struct sdl_exit_code_map_t* sdl_map_entry_by_code(int exit_code)
218{
219 for (const auto& x : sdl_exit_code_map)
220 {
221 const struct sdl_exit_code_map_t* cur = &x;
222 if (cur->code == exit_code)
223 return cur;
224 }
225 return nullptr;
226}
227
228static const struct sdl_exit_code_map_t* sdl_map_entry_by_error(UINT32 error)
229{
230 for (const auto& x : sdl_exit_code_map)
231 {
232 const struct sdl_exit_code_map_t* cur = &x;
233 if (cur->error == error)
234 return cur;
235 }
236 return nullptr;
237}
238
239static int sdl_map_error_to_exit_code(DWORD error)
240{
241 const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_error(error);
242 if (entry)
243 return entry->code;
244
245 return SDL_EXIT_CONN_FAILED;
246}
247
248static const char* sdl_map_error_to_code_tag(UINT32 error)
249{
250 const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_error(error);
251 if (entry)
252 return entry->code_tag;
253 return nullptr;
254}
255
256static const char* sdl_map_to_code_tag(int code)
257{
258 const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_code(code);
259 if (entry)
260 return entry->code_tag;
261 return nullptr;
262}
263
264static int error_info_to_error(freerdp* instance, DWORD* pcode, char** msg, size_t* len)
265{
266 const DWORD code = freerdp_error_info(instance);
267 const char* name = freerdp_get_error_info_name(code);
268 const char* str = freerdp_get_error_info_string(code);
269 const int exit_code = sdl_map_error_to_exit_code(code);
270
271 winpr_asprintf(msg, len, "Terminate with %s due to ERROR_INFO %s [0x%08" PRIx32 "]: %s",
272 sdl_map_error_to_code_tag(code), name, code, str);
273 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%s", *msg);
274 if (pcode)
275 *pcode = code;
276 return exit_code;
277}
278
279/* This function is called whenever a new frame starts.
280 * It can be used to reset invalidated areas. */
281static BOOL sdl_begin_paint(rdpContext* context)
282{
283 auto gdi = context->gdi;
284 WINPR_ASSERT(gdi);
285 WINPR_ASSERT(gdi->primary);
286 WINPR_ASSERT(gdi->primary->hdc);
287 WINPR_ASSERT(gdi->primary->hdc->hwnd);
288 WINPR_ASSERT(gdi->primary->hdc->hwnd->invalid);
289 gdi->primary->hdc->hwnd->invalid->null = TRUE;
290 gdi->primary->hdc->hwnd->ninvalid = 0;
291
292 return TRUE;
293}
294
295static bool sdl_draw_to_window_rect([[maybe_unused]] SdlContext* sdl, SdlWindow& window,
296 SDL_Surface* surface, SDL_Point offset, const SDL_Rect& srcRect)
297{
298 WINPR_ASSERT(surface);
299 SDL_Rect dstRect = { offset.x + srcRect.x, offset.y + srcRect.y, srcRect.w, srcRect.h };
300 return window.blit(surface, srcRect, dstRect);
301}
302
303static bool sdl_draw_to_window_rect(SdlContext* sdl, SdlWindow& window, SDL_Surface* surface,
304 SDL_Point offset, const std::vector<SDL_Rect>& rects = {})
305{
306 if (rects.empty())
307 {
308 return sdl_draw_to_window_rect(sdl, window, surface, offset,
309 { 0, 0, surface->w, surface->h });
310 }
311 for (auto& srcRect : rects)
312 {
313 if (!sdl_draw_to_window_rect(sdl, window, surface, offset, srcRect))
314 return false;
315 }
316 return true;
317}
318
319static bool sdl_draw_to_window_scaled_rect(SdlContext* sdl, SdlWindow& window, SDL_Surface* surface,
320 const SDL_Rect& srcRect)
321{
322 SDL_Rect dstRect = srcRect;
323 sdl_scale_coordinates(sdl, window.id(), &dstRect.x, &dstRect.y, FALSE, TRUE);
324 sdl_scale_coordinates(sdl, window.id(), &dstRect.w, &dstRect.h, FALSE, TRUE);
325 return window.blit(surface, srcRect, dstRect);
326}
327
328static BOOL sdl_draw_to_window_scaled_rect(SdlContext* sdl, SdlWindow& window, SDL_Surface* surface,
329 const std::vector<SDL_Rect>& rects = {})
330{
331 if (rects.empty())
332 {
333 return sdl_draw_to_window_scaled_rect(sdl, window, surface,
334 { 0, 0, surface->w, surface->h });
335 }
336 for (const auto& srcRect : rects)
337 {
338 if (!sdl_draw_to_window_scaled_rect(sdl, window, surface, srcRect))
339 return FALSE;
340 }
341 return TRUE;
342}
343
344static BOOL sdl_draw_to_window(SdlContext* sdl, SdlWindow& window,
345 const std::vector<SDL_Rect>& rects = {})
346{
347 WINPR_ASSERT(sdl);
348
349 if (!sdl->isConnected())
350 return TRUE;
351
352 auto context = sdl->context();
353 auto gdi = context->gdi;
354 WINPR_ASSERT(gdi);
355
356 auto size = window.rect();
357
358 if (!freerdp_settings_get_bool(context->settings, FreeRDP_SmartSizing))
359 {
360 window.setOffsetX(0);
361 window.setOffsetY(0);
362 if (gdi->width < size.w)
363 {
364 window.setOffsetX((size.w - gdi->width) / 2);
365 }
366 if (gdi->height < size.h)
367 {
368 window.setOffsetY((size.h - gdi->height) / 2);
369 }
370
371 auto surface = sdl->primary.get();
372 if (!sdl_draw_to_window_rect(sdl, window, surface, { window.offsetX(), window.offsetY() },
373 rects))
374 return FALSE;
375 }
376 else
377 {
378 if (!sdl_draw_to_window_scaled_rect(sdl, window, sdl->primary.get(), rects))
379 return FALSE;
380 }
381 window.updateSurface();
382 return TRUE;
383}
384
385static BOOL sdl_draw_to_window(SdlContext* sdl, std::map<Uint32, SdlWindow>& windows,
386 const std::vector<SDL_Rect>& rects = {})
387{
388 for (auto& window : windows)
389 {
390 if (!sdl_draw_to_window(sdl, window.second, rects))
391 return FALSE;
392 }
393
394 return TRUE;
395}
396
397/* This function is called when the library completed composing a new
398 * frame. Read out the changed areas and blit them to your output device.
399 * The image buffer will have the format specified by gdi_init
400 */
401static BOOL sdl_end_paint(rdpContext* context)
402{
403 auto sdl = get_context(context);
404 WINPR_ASSERT(sdl);
405
406 auto gdi = context->gdi;
407 WINPR_ASSERT(gdi);
408 WINPR_ASSERT(gdi->primary);
409 WINPR_ASSERT(gdi->primary->hdc);
410 WINPR_ASSERT(gdi->primary->hdc->hwnd);
411 WINPR_ASSERT(gdi->primary->hdc->hwnd->invalid);
412 if (gdi->suppressOutput || gdi->primary->hdc->hwnd->invalid->null)
413 return TRUE;
414
415 const INT32 ninvalid = gdi->primary->hdc->hwnd->ninvalid;
416 const GDI_RGN* cinvalid = gdi->primary->hdc->hwnd->cinvalid;
417
418 if (ninvalid < 1)
419 return TRUE;
420
421 std::vector<SDL_Rect> rects;
422 for (INT32 x = 0; x < ninvalid; x++)
423 {
424 auto& rgn = cinvalid[x];
425 rects.push_back({ rgn.x, rgn.y, rgn.w, rgn.h });
426 }
427
428 sdl->push(std::move(rects));
429 return sdl_push_user_event(SDL_EVENT_USER_UPDATE);
430}
431
432static void sdl_destroy_primary(SdlContext* sdl)
433{
434 if (!sdl)
435 return;
436 sdl->primary.reset();
437}
438
439/* Create a SDL surface from the GDI buffer */
440static BOOL sdl_create_primary(SdlContext* sdl)
441{
442 rdpGdi* gdi = nullptr;
443
444 WINPR_ASSERT(sdl);
445
446 gdi = sdl->context()->gdi;
447 WINPR_ASSERT(gdi);
448
449 sdl_destroy_primary(sdl);
450 sdl->primary =
451 SDLSurfacePtr(SDL_CreateSurfaceFrom(static_cast<int>(gdi->width),
452 static_cast<int>(gdi->height), sdl->sdl_pixel_format,
453 gdi->primary_buffer, static_cast<int>(gdi->stride)),
454 SDL_DestroySurface);
455 if (!sdl->primary)
456 return FALSE;
457
458 SDL_SetSurfaceBlendMode(sdl->primary.get(), SDL_BLENDMODE_NONE);
459 SDL_Rect surfaceRect = { 0, 0, gdi->width, gdi->height };
460 SDL_FillSurfaceRect(sdl->primary.get(), &surfaceRect,
461 SDL_MapSurfaceRGBA(sdl->primary.get(), 0, 0, 0, 0xff));
462
463 return TRUE;
464}
465
466static BOOL sdl_desktop_resize(rdpContext* context)
467{
468 rdpGdi* gdi = nullptr;
469 rdpSettings* settings = nullptr;
470 auto sdl = get_context(context);
471
472 WINPR_ASSERT(sdl);
473 WINPR_ASSERT(context);
474
475 settings = context->settings;
476 WINPR_ASSERT(settings);
477
478 std::lock_guard<CriticalSection> lock(sdl->critical);
479 gdi = context->gdi;
480 if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
481 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
482 return FALSE;
483 return sdl_create_primary(sdl);
484}
485
486/* This function is called to output a System BEEP */
487static BOOL sdl_play_sound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound)
488{
489 /* TODO: Implement */
490 WINPR_UNUSED(context);
491 WINPR_UNUSED(play_sound);
492 return TRUE;
493}
494
495static BOOL sdl_wait_for_init(SdlContext* sdl)
496{
497 WINPR_ASSERT(sdl);
498 sdl->initialize.set();
499
500 HANDLE handles[] = { sdl->initialized.handle(), freerdp_abort_event(sdl->context()) };
501
502 const DWORD rc = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
503 switch (rc)
504 {
505 case WAIT_OBJECT_0:
506 return TRUE;
507 default:
508 return FALSE;
509 }
510}
511
512/* Called before a connection is established.
513 * Set all configuration options to support and load channels here. */
514static BOOL sdl_pre_connect(freerdp* instance)
515{
516 WINPR_ASSERT(instance);
517 WINPR_ASSERT(instance->context);
518
519 auto sdl = get_context(instance->context);
520
521 auto settings = instance->context->settings;
522 WINPR_ASSERT(settings);
523
524 if (!freerdp_settings_set_bool(settings, FreeRDP_CertificateCallbackPreferPEM, TRUE))
525 return FALSE;
526
527 /* Optional OS identifier sent to server */
528 if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX))
529 return FALSE;
530 if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_SDL))
531 return FALSE;
532 /* OrderSupport is initialized at this point.
533 * Only override it if you plan to implement custom order
534 * callbacks or deactivate certain features. */
535 /* Register the channel listeners.
536 * They are required to set up / tear down channels if they are loaded. */
537 PubSub_SubscribeChannelConnected(instance->context->pubSub, sdl_OnChannelConnectedEventHandler);
538 PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
539 sdl_OnChannelDisconnectedEventHandler);
540
541 if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
542 {
543 UINT32 maxWidth = 0;
544 UINT32 maxHeight = 0;
545
546 if (!sdl_wait_for_init(sdl))
547 return FALSE;
548
549 if (!freerdp_settings_get_bool(settings, FreeRDP_UseCommonStdioCallbacks))
550 {
551 sdl->dialog.create(sdl->context());
552 }
553
554 sdl->dialog.setTitle("Connecting to '%s'", freerdp_settings_get_server_name(settings));
555 sdl->dialog.showInfo("The connection is being established\n\nPlease wait...");
556
557 if (!sdl_detect_monitors(sdl, &maxWidth, &maxHeight))
558 return FALSE;
559
560 if ((maxWidth != 0) && (maxHeight != 0) &&
561 !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
562 {
563 WLog_Print(sdl->log, WLOG_INFO, "Update size to %ux%u", maxWidth, maxHeight);
564 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, maxWidth))
565 return FALSE;
566 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, maxHeight))
567 return FALSE;
568 }
569 }
570 else
571 {
572 /* Check +auth-only has a username and password. */
573 if (!freerdp_settings_get_string(settings, FreeRDP_Password))
574 {
575 WLog_Print(sdl->log, WLOG_INFO, "auth-only, but no password set. Please provide one.");
576 return FALSE;
577 }
578
579 if (!freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, TRUE))
580 return FALSE;
581
582 WLog_Print(sdl->log, WLOG_INFO, "Authentication only. Don't connect SDL.");
583 }
584
585 if (!sdl->input.initialize())
586 return FALSE;
587
588 /* TODO: Any code your client requires */
589 return TRUE;
590}
591
592static const char* sdl_window_get_title(rdpSettings* settings)
593{
594 const char* windowTitle = nullptr;
595 UINT32 port = 0;
596 BOOL addPort = 0;
597 const char* name = nullptr;
598 const char* prefix = "FreeRDP:";
599
600 if (!settings)
601 return nullptr;
602
603 windowTitle = freerdp_settings_get_string(settings, FreeRDP_WindowTitle);
604 if (windowTitle)
605 return windowTitle;
606
607 name = freerdp_settings_get_server_name(settings);
608 port = freerdp_settings_get_uint32(settings, FreeRDP_ServerPort);
609
610 addPort = (port != 3389);
611
612 char buffer[MAX_PATH + 64] = {};
613
614 if (!addPort)
615 (void)sprintf_s(buffer, sizeof(buffer), "%s %s", prefix, name);
616 else
617 (void)sprintf_s(buffer, sizeof(buffer), "%s %s:%" PRIu32, prefix, name, port);
618
619 if (!freerdp_settings_set_string(settings, FreeRDP_WindowTitle, buffer))
620 return nullptr;
621 return freerdp_settings_get_string(settings, FreeRDP_WindowTitle);
622}
623
624static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const char* signame,
625 [[maybe_unused]] void* context)
626{
627 sdl_push_quit();
628}
629
630static void sdl_cleanup_sdl(SdlContext* sdl)
631{
632 if (!sdl)
633 return;
634
635 std::lock_guard<CriticalSection> lock(sdl->critical);
636 sdl->windows.clear();
637 sdl->dialog.destroy();
638
639 sdl_destroy_primary(sdl);
640
641 freerdp_del_signal_cleanup_handler(sdl->context(), sdl_term_handler);
642 sdl_dialogs_uninit();
643 SDL_Quit();
644}
645
646static BOOL sdl_create_windows(SdlContext* sdl)
647{
648 WINPR_ASSERT(sdl);
649
650 auto settings = sdl->context()->settings;
651 auto title = sdl_window_get_title(settings);
652
653 UINT32 windowCount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
654
655 for (UINT32 x = 0; x < windowCount; x++)
656 {
657 auto id = sdl->monitorId(x);
658 if (id < 0)
659 return FALSE;
660
661 auto monitor = static_cast<rdpMonitor*>(
662 freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x));
663
664 auto w = WINPR_ASSERTING_INT_CAST(Uint32, monitor->width);
665 auto h = WINPR_ASSERTING_INT_CAST(Uint32, monitor->height);
666 if (!(freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) ||
667 freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)))
668 {
669 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
670 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
671 }
672
673 Uint32 flags = SDL_WINDOW_HIGH_PIXEL_DENSITY;
674 auto startupX = SDL_WINDOWPOS_CENTERED_DISPLAY(id);
675 auto startupY = SDL_WINDOWPOS_CENTERED_DISPLAY(id);
676
677 if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) &&
678 !freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
679 {
680 flags |= SDL_WINDOW_FULLSCREEN;
681 }
682
683 if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
684 {
685 flags |= SDL_WINDOW_BORDERLESS;
686 }
687
688 if (!freerdp_settings_get_bool(settings, FreeRDP_Decorations))
689 flags |= SDL_WINDOW_BORDERLESS;
690
691 SdlWindow window{ title,
692 static_cast<int>(startupX),
693 static_cast<int>(startupY),
694 static_cast<int>(w),
695 static_cast<int>(h),
696 flags };
697 ScopeGuard guard1([&]() { sdl->windows_created.set(); });
698 if (!window.window())
699 return FALSE;
700
701 if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
702 {
703 auto r = window.rect();
704 window.setOffsetX(0 - r.x);
705 window.setOffsetY(0 - r.y);
706 }
707
708 sdl->windows.insert({ window.id(), std::move(window) });
709 }
710
711 return TRUE;
712}
713
714static BOOL sdl_wait_create_windows(SdlContext* sdl)
715{
716 {
717 std::unique_lock<CriticalSection> lock(sdl->critical);
718 sdl->windows_created.clear();
719 if (!sdl_push_user_event(SDL_EVENT_USER_CREATE_WINDOWS, sdl))
720 return FALSE;
721 }
722
723 HANDLE handles[] = { sdl->windows_created.handle(), freerdp_abort_event(sdl->context()) };
724
725 const DWORD rc = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
726 switch (rc)
727 {
728 case WAIT_OBJECT_0:
729 return TRUE;
730 default:
731 return FALSE;
732 }
733}
734
735static bool shall_abort(SdlContext* sdl)
736{
737 std::lock_guard<CriticalSection> lock(sdl->critical);
738 if (freerdp_shall_disconnect_context(sdl->context()))
739 {
740 if (sdl->rdp_thread_running)
741 return false;
742 return !sdl->dialog.isRunning();
743 }
744 return false;
745}
746
747static int sdl_run(SdlContext* sdl)
748{
749 int rc = -1;
750 WINPR_ASSERT(sdl);
751
752 HANDLE handles[] = { sdl->initialize.handle(), freerdp_abort_event(sdl->context()) };
753 const DWORD status = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
754 switch (status)
755 {
756 case WAIT_OBJECT_0:
757 break;
758 default:
759 return 0;
760 }
761
762 SDL_Init(SDL_INIT_VIDEO);
763 sdl_dialogs_init();
764
765 SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0");
766 SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
767
768 freerdp_add_signal_cleanup_handler(sdl->context(), sdl_term_handler);
769
770 sdl->initialized.set();
771
772 while (!shall_abort(sdl))
773 {
774 SDL_Event windowEvent = {};
775 while (!shall_abort(sdl) && SDL_WaitEventTimeout(nullptr, 1000))
776 {
777 /* Only poll standard SDL events and SDL_EVENT_USERS meant to create dialogs.
778 * do not process the dialog return value events here.
779 */
780 const int prc = SDL_PeepEvents(&windowEvent, 1, SDL_GETEVENT, SDL_EVENT_FIRST,
781 SDL_EVENT_USER_RETRY_DIALOG);
782 if (prc < 0)
783 {
784 if (sdl_log_error(prc, sdl->log, "SDL_PeepEvents"))
785 continue;
786 }
787
788#if defined(WITH_DEBUG_SDL_EVENTS)
789 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "got event %s [0x%08" PRIx32 "]",
790 sdl_event_type_str(windowEvent.type), windowEvent.type);
791#endif
792 {
793 std::lock_guard<CriticalSection> lock(sdl->critical);
794 /* The session might have been disconnected while we were waiting for a new SDL
795 * event. In that case ignore the SDL event and terminate. */
796 if (freerdp_shall_disconnect_context(sdl->context()))
797 continue;
798 }
799
800 if (sdl->dialog.handleEvent(windowEvent))
801 {
802 continue;
803 }
804
805 auto point2pix = [](Uint32 win_id, float& x, float& y)
806 {
807 auto win = SDL_GetWindowFromID(win_id);
808 assert(win);
809 auto scale = SDL_GetWindowDisplayScale(win);
810 assert(scale);
811 x *= scale;
812 y *= scale;
813 };
814
815 switch (windowEvent.type)
816 {
817 case SDL_EVENT_QUIT:
818 freerdp_abort_connect_context(sdl->context());
819 break;
820 case SDL_EVENT_KEY_DOWN:
821 case SDL_EVENT_KEY_UP:
822 {
823 const SDL_KeyboardEvent* ev = &windowEvent.key;
824 sdl->input.keyboard_handle_event(ev);
825 }
826 break;
827 case SDL_EVENT_KEYMAP_CHANGED:
828 {
829 }
830 break; // TODO: Switch keyboard layout
831 case SDL_EVENT_MOUSE_MOTION:
832 {
833 SDL_MouseMotionEvent& ev = windowEvent.motion;
834 point2pix(ev.windowID, ev.x, ev.y);
835 point2pix(ev.windowID, ev.xrel, ev.yrel);
836 sdl_handle_mouse_motion(sdl, &ev);
837 }
838 break;
839 case SDL_EVENT_MOUSE_BUTTON_DOWN:
840 case SDL_EVENT_MOUSE_BUTTON_UP:
841 {
842 SDL_MouseButtonEvent& ev = windowEvent.button;
843 point2pix(ev.windowID, ev.x, ev.y);
844 sdl_handle_mouse_button(sdl, &ev);
845 }
846 break;
847 case SDL_EVENT_MOUSE_WHEEL:
848 {
849 const SDL_MouseWheelEvent* ev = &windowEvent.wheel;
850 sdl_handle_mouse_wheel(sdl, ev);
851 }
852 break;
853 case SDL_EVENT_FINGER_DOWN:
854 {
855 const SDL_TouchFingerEvent* ev = &windowEvent.tfinger;
856 sdl_handle_touch_down(sdl, ev);
857 }
858 break;
859 case SDL_EVENT_FINGER_UP:
860 {
861 const SDL_TouchFingerEvent* ev = &windowEvent.tfinger;
862 sdl_handle_touch_up(sdl, ev);
863 }
864 break;
865 case SDL_EVENT_FINGER_MOTION:
866 {
867 const SDL_TouchFingerEvent* ev = &windowEvent.tfinger;
868 sdl_handle_touch_motion(sdl, ev);
869 }
870 break;
871
872 case SDL_EVENT_RENDER_TARGETS_RESET:
873 (void)sdl->redraw();
874 break;
875 case SDL_EVENT_RENDER_DEVICE_RESET:
876 (void)sdl->redraw();
877 break;
878 case SDL_EVENT_WILL_ENTER_FOREGROUND:
879 (void)sdl->redraw();
880 break;
881 case SDL_EVENT_USER_CERT_DIALOG:
882 {
883 auto title = static_cast<const char*>(windowEvent.user.data1);
884 auto msg = static_cast<const char*>(windowEvent.user.data2);
885 sdl_cert_dialog_show(title, msg);
886 }
887 break;
888 case SDL_EVENT_USER_SHOW_DIALOG:
889 {
890 auto title = static_cast<const char*>(windowEvent.user.data1);
891 auto msg = static_cast<const char*>(windowEvent.user.data2);
892 sdl_message_dialog_show(title, msg, windowEvent.user.code);
893 }
894 break;
895 case SDL_EVENT_USER_SCARD_DIALOG:
896 {
897 auto title = static_cast<const char*>(windowEvent.user.data1);
898 auto msg = static_cast<const char**>(windowEvent.user.data2);
899 sdl_scard_dialog_show(title, windowEvent.user.code, msg);
900 }
901 break;
902 case SDL_EVENT_USER_AUTH_DIALOG:
903 sdl_auth_dialog_show(
904 reinterpret_cast<const SDL_UserAuthArg*>(windowEvent.padding));
905 break;
906 case SDL_EVENT_USER_UPDATE:
907 {
908 std::vector<SDL_Rect> rectangles;
909 do
910 {
911 rectangles = sdl->pop();
912 sdl_draw_to_window(sdl, sdl->windows, rectangles);
913 } while (!rectangles.empty());
914 }
915 break;
916 case SDL_EVENT_USER_CREATE_WINDOWS:
917 {
918 auto ctx = static_cast<SdlContext*>(windowEvent.user.data1);
919 sdl_create_windows(ctx);
920 }
921 break;
922 case SDL_EVENT_USER_WINDOW_RESIZEABLE:
923 {
924 auto window = static_cast<SdlWindow*>(windowEvent.user.data1);
925 const bool use = windowEvent.user.code != 0;
926 if (window)
927 window->resizeable(use);
928 }
929 break;
930 case SDL_EVENT_USER_WINDOW_FULLSCREEN:
931 {
932 auto window = static_cast<SdlWindow*>(windowEvent.user.data1);
933 const bool enter = windowEvent.user.code != 0;
934 if (window)
935 window->fullscreen(enter);
936 }
937 break;
938 case SDL_EVENT_USER_WINDOW_MINIMIZE:
939 for (auto& window : sdl->windows)
940 {
941 window.second.minimize();
942 }
943 break;
944 case SDL_EVENT_USER_POINTER_NULL:
945 SDL_HideCursor();
946 sdl->setCursor(nullptr);
947 sdl->setHasCursor(false);
948 break;
949 case SDL_EVENT_USER_POINTER_DEFAULT:
950 {
951 SDL_Cursor* def = SDL_GetDefaultCursor();
952 SDL_SetCursor(def);
953 SDL_ShowCursor();
954 sdl->setCursor(nullptr);
955 sdl->setHasCursor(true);
956 }
957 break;
958 case SDL_EVENT_USER_POINTER_POSITION:
959 {
960 const auto x =
961 static_cast<INT32>(reinterpret_cast<uintptr_t>(windowEvent.user.data1));
962 const auto y =
963 static_cast<INT32>(reinterpret_cast<uintptr_t>(windowEvent.user.data2));
964
965 SDL_Window* window = SDL_GetMouseFocus();
966 if (window)
967 {
968 const Uint32 id = SDL_GetWindowID(window);
969
970 INT32 sx = x;
971 INT32 sy = y;
972 if (sdl_scale_coordinates(sdl, id, &sx, &sy, FALSE, FALSE))
973 SDL_WarpMouseInWindow(window, static_cast<float>(sx),
974 static_cast<float>(sy));
975 }
976 }
977 break;
978 case SDL_EVENT_USER_POINTER_SET:
979 sdl->setCursor(static_cast<rdpPointer*>(windowEvent.user.data1));
980 sdl_Pointer_Set_Process(sdl);
981 break;
982 case SDL_EVENT_CLIPBOARD_UPDATE:
983 sdl->clip.handle_update(windowEvent.clipboard);
984 break;
985 case SDL_EVENT_USER_UPDATE_CONNECT_DIALOG:
986 sdl->dialog.handleShow();
987 break;
988 case SDL_EVENT_USER_QUIT:
989 default:
990 if ((windowEvent.type >= SDL_EVENT_DISPLAY_FIRST) &&
991 (windowEvent.type <= SDL_EVENT_DISPLAY_LAST))
992 {
993 const SDL_DisplayEvent* ev = &windowEvent.display;
994 (void)sdl->disp.handle_display_event(ev);
995 }
996 else if ((windowEvent.type >= SDL_EVENT_WINDOW_FIRST) &&
997 (windowEvent.type <= SDL_EVENT_WINDOW_LAST))
998 {
999 const SDL_WindowEvent* ev = &windowEvent.window;
1000 auto window = sdl->windows.find(ev->windowID);
1001 if (window != sdl->windows.end())
1002 {
1003 (void)sdl->disp.handle_window_event(ev);
1004
1005 switch (ev->type)
1006 {
1007 case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
1008 {
1009 sdl_Pointer_Set_Process(sdl);
1010 if (freerdp_settings_get_bool(sdl->context()->settings,
1011 FreeRDP_DynamicResolutionUpdate))
1012 {
1013 break;
1014 }
1015 auto win = window->second.window();
1016 int w_pix{};
1017 int h_pix{};
1018 [[maybe_unused]] auto rcpix =
1019 SDL_GetWindowSizeInPixels(win, &w_pix, &h_pix);
1020 assert(rcpix);
1021 auto scale = SDL_GetWindowDisplayScale(win);
1022 assert(SDL_isnanf(scale) == 0);
1023 assert(SDL_isinff(scale) == 0);
1024 assert(scale > SDL_FLT_EPSILON);
1025 auto w_gdi = sdl->context()->gdi->width;
1026 auto h_gdi = sdl->context()->gdi->height;
1027 auto pix2point = [=](int pix)
1028 { return static_cast<int>(static_cast<float>(pix) / scale); };
1029 if (w_pix != w_gdi || h_pix != h_gdi)
1030 {
1031 [[maybe_unused]] auto ssws = SDL_SetWindowSize(
1032 win, pix2point(w_gdi), pix2point(h_gdi));
1033 assert(ssws);
1034 }
1035 }
1036 break;
1037 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
1038 window->second.fill();
1039 sdl_draw_to_window(sdl, window->second);
1040 sdl_Pointer_Set_Process(sdl);
1041 break;
1042 case SDL_EVENT_WINDOW_MOVED:
1043 {
1044 auto r = window->second.rect();
1045 auto id = window->second.id();
1046 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%u: %dx%d-%dx%d",
1047 id, r.x, r.y, r.w, r.h);
1048 }
1049 break;
1050 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
1051 {
1052 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
1053 "Window closed, terminating RDP session...");
1054 freerdp_abort_connect_context(sdl->context());
1055 }
1056 break;
1057 default:
1058 break;
1059 }
1060 }
1061 }
1062 break;
1063 }
1064 }
1065 }
1066
1067 rc = 1;
1068
1069 sdl_cleanup_sdl(sdl);
1070 return rc;
1071}
1072
1073/* Called after a RDP connection was successfully established.
1074 * Settings might have changed during negotiation of client / server feature
1075 * support.
1076 *
1077 * Set up local framebuffers and paing callbacks.
1078 * If required, register pointer callbacks to change the local mouse cursor
1079 * when hovering over the RDP window
1080 */
1081static BOOL sdl_post_connect(freerdp* instance)
1082{
1083 WINPR_ASSERT(instance);
1084
1085 auto context = instance->context;
1086 WINPR_ASSERT(context);
1087
1088 auto sdl = get_context(context);
1089
1090 // Retry was successful, discard dialog
1091 sdl->dialog.show(false);
1092
1093 if (freerdp_settings_get_bool(context->settings, FreeRDP_AuthenticationOnly))
1094 {
1095 /* Check +auth-only has a username and password. */
1096 if (!freerdp_settings_get_string(context->settings, FreeRDP_Password))
1097 {
1098 WLog_Print(sdl->log, WLOG_INFO, "auth-only, but no password set. Please provide one.");
1099 return FALSE;
1100 }
1101
1102 WLog_Print(sdl->log, WLOG_INFO, "Authentication only. Don't connect to X.");
1103 return TRUE;
1104 }
1105
1106 if (!sdl_wait_create_windows(sdl))
1107 return FALSE;
1108
1109 sdl->sdl_pixel_format = SDL_PIXELFORMAT_BGRA32;
1110 if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
1111 return FALSE;
1112
1113 if (!sdl_create_primary(sdl))
1114 return FALSE;
1115
1116 if (!sdl_register_pointer(instance->context->graphics))
1117 return FALSE;
1118
1119 WINPR_ASSERT(context->update);
1120
1121 context->update->BeginPaint = sdl_begin_paint;
1122 context->update->EndPaint = sdl_end_paint;
1123 context->update->PlaySound = sdl_play_sound;
1124 context->update->DesktopResize = sdl_desktop_resize;
1125 context->update->SetKeyboardIndicators = sdlInput::keyboard_set_indicators;
1126 context->update->SetKeyboardImeStatus = sdlInput::keyboard_set_ime_status;
1127
1128 if (!sdl->update_resizeable(false))
1129 return FALSE;
1130 if (!sdl->update_fullscreen(freerdp_settings_get_bool(context->settings, FreeRDP_Fullscreen) ||
1131 freerdp_settings_get_bool(context->settings, FreeRDP_UseMultimon)))
1132 return FALSE;
1133 sdl->setConnected(true);
1134 return TRUE;
1135}
1136
1137/* This function is called whether a session ends by failure or success.
1138 * Clean up everything allocated by pre_connect and post_connect.
1139 */
1140static void sdl_post_disconnect(freerdp* instance)
1141{
1142 if (!instance)
1143 return;
1144
1145 if (!instance->context)
1146 return;
1147
1148 auto sdl = get_context(instance->context);
1149 sdl->setConnected(false);
1150
1151 PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
1152 sdl_OnChannelConnectedEventHandler);
1153 PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub,
1154 sdl_OnChannelDisconnectedEventHandler);
1155 gdi_free(instance);
1156}
1157
1158static void sdl_post_final_disconnect(freerdp* instance)
1159{
1160 if (!instance)
1161 return;
1162
1163 if (!instance->context)
1164 return;
1165}
1166
1167static void sdl_client_cleanup(SdlContext* sdl, int exit_code, const std::string& error_msg)
1168{
1169 WINPR_ASSERT(sdl);
1170
1171 rdpContext* context = sdl->context();
1172 WINPR_ASSERT(context);
1173 rdpSettings* settings = context->settings;
1174 WINPR_ASSERT(settings);
1175
1176 sdl->rdp_thread_running = false;
1177 bool showError = false;
1178 if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
1179 WLog_Print(sdl->log, WLOG_INFO, "Authentication only, exit status %s [%" PRId32 "]",
1180 sdl_map_to_code_tag(exit_code), exit_code);
1181 else
1182 {
1183 switch (exit_code)
1184 {
1185 case SDL_EXIT_SUCCESS:
1186 case SDL_EXIT_DISCONNECT:
1187 case SDL_EXIT_LOGOFF:
1188 case SDL_EXIT_DISCONNECT_BY_USER:
1189 case SDL_EXIT_CONNECT_CANCELLED:
1190 break;
1191 default:
1192 {
1193 sdl->dialog.showError(error_msg);
1194 }
1195 break;
1196 }
1197 }
1198
1199 if (!showError)
1200 sdl->dialog.show(false);
1201
1202 sdl->exit_code = exit_code;
1203 sdl_push_user_event(SDL_EVENT_USER_QUIT);
1204 SDL_CleanupTLS();
1205}
1206
1207static int sdl_client_thread_connect(SdlContext* sdl, std::string& error_msg)
1208{
1209 WINPR_ASSERT(sdl);
1210
1211 auto instance = sdl->context()->instance;
1212 WINPR_ASSERT(instance);
1213
1214 sdl->rdp_thread_running = true;
1215 BOOL rc = freerdp_connect(instance);
1216
1217 rdpContext* context = sdl->context();
1218 rdpSettings* settings = context->settings;
1219 WINPR_ASSERT(settings);
1220
1221 int exit_code = SDL_EXIT_SUCCESS;
1222 if (!rc)
1223 {
1224 UINT32 error = freerdp_get_last_error(context);
1225 exit_code = sdl_map_error_to_exit_code(error);
1226 }
1227
1228 if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
1229 {
1230 DWORD code = freerdp_get_last_error(context);
1231 freerdp_abort_connect_context(context);
1232 WLog_Print(sdl->log, WLOG_ERROR, "Authentication only, %s [0x%08" PRIx32 "] %s",
1233 freerdp_get_last_error_name(code), code, freerdp_get_last_error_string(code));
1234 return exit_code;
1235 }
1236
1237 if (!rc)
1238 {
1239 DWORD code = freerdp_error_info(instance);
1240 if (exit_code == SDL_EXIT_SUCCESS)
1241 {
1242 char* msg = nullptr;
1243 size_t len = 0;
1244 exit_code = error_info_to_error(instance, &code, &msg, &len);
1245 if (msg)
1246 error_msg = msg;
1247 free(msg);
1248 }
1249
1250 auto last = freerdp_get_last_error(context);
1251 if (error_msg.empty())
1252 {
1253 char* msg = nullptr;
1254 size_t len = 0;
1255 winpr_asprintf(&msg, &len, "%s [0x%08" PRIx32 "]\n%s",
1256 freerdp_get_last_error_name(last), last,
1257 freerdp_get_last_error_string(last));
1258 if (msg)
1259 error_msg = msg;
1260 free(msg);
1261 }
1262
1263 if (exit_code == SDL_EXIT_SUCCESS)
1264 {
1265 if (last == FREERDP_ERROR_AUTHENTICATION_FAILED)
1266 exit_code = SDL_EXIT_AUTH_FAILURE;
1267 else if (code == ERRINFO_SUCCESS)
1268 exit_code = SDL_EXIT_CONN_FAILED;
1269 }
1270
1271 sdl->dialog.show(false);
1272 }
1273
1274 return exit_code;
1275}
1276
1277static int sdl_client_thread_run(SdlContext* sdl, std::string& error_msg)
1278{
1279 WINPR_ASSERT(sdl);
1280
1281 auto context = sdl->context();
1282 WINPR_ASSERT(context);
1283
1284 auto instance = context->instance;
1285 WINPR_ASSERT(instance);
1286
1287 int exit_code = SDL_EXIT_SUCCESS;
1288 while (!freerdp_shall_disconnect_context(context))
1289 {
1290 HANDLE handles[MAXIMUM_WAIT_OBJECTS] = {};
1291 /*
1292 * win8 and server 2k12 seem to have some timing issue/race condition
1293 * when a initial sync request is send to sync the keyboard indicators
1294 * sending the sync event twice fixed this problem
1295 */
1296 if (freerdp_focus_required(instance))
1297 {
1298 auto ctx = get_context(context);
1299 WINPR_ASSERT(ctx);
1300 if (!ctx->input.keyboard_focus_in())
1301 break;
1302 if (!ctx->input.keyboard_focus_in())
1303 break;
1304 }
1305
1306 const DWORD nCount = freerdp_get_event_handles(context, handles, ARRAYSIZE(handles));
1307
1308 if (nCount == 0)
1309 {
1310 WLog_Print(sdl->log, WLOG_ERROR, "freerdp_get_event_handles failed");
1311 break;
1312 }
1313
1314 const DWORD status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
1315
1316 if (status == WAIT_FAILED)
1317 break;
1318
1319 if (!freerdp_check_event_handles(context))
1320 {
1321 if (client_auto_reconnect(instance))
1322 {
1323 // Retry was successful, discard dialog
1324 sdl->dialog.show(false);
1325 continue;
1326 }
1327 else
1328 {
1329 /*
1330 * Indicate an unsuccessful connection attempt if reconnect
1331 * did not succeed and no other error was specified.
1332 */
1333 if (freerdp_error_info(instance) == 0)
1334 exit_code = SDL_EXIT_CONN_FAILED;
1335 }
1336
1337 if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS)
1338 WLog_Print(sdl->log, WLOG_ERROR, "WaitForMultipleObjects failed with %" PRIu32 "",
1339 status);
1340 if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS)
1341 WLog_Print(sdl->log, WLOG_ERROR, "Failed to check FreeRDP event handles");
1342 break;
1343 }
1344 }
1345
1346 if (exit_code == SDL_EXIT_SUCCESS)
1347 {
1348 DWORD code = 0;
1349 {
1350 char* emsg = nullptr;
1351 size_t elen = 0;
1352 exit_code = error_info_to_error(instance, &code, &emsg, &elen);
1353 if (emsg)
1354 error_msg = emsg;
1355 free(emsg);
1356 }
1357
1358 if ((code == ERRINFO_LOGOFF_BY_USER) &&
1359 (freerdp_get_disconnect_ultimatum(context) == Disconnect_Ultimatum_user_requested))
1360 {
1361 const char* msg = "Error info says user did not initiate but disconnect ultimatum says "
1362 "they did; treat this as a user logoff";
1363
1364 char* emsg = nullptr;
1365 size_t elen = 0;
1366 winpr_asprintf(&emsg, &elen, "%s", msg);
1367 if (emsg)
1368 error_msg = emsg;
1369 free(emsg);
1370
1371 /* This situation might be limited to Windows XP. */
1372 WLog_Print(sdl->log, WLOG_INFO, "%s", msg);
1373 exit_code = SDL_EXIT_LOGOFF;
1374 }
1375 }
1376
1377 freerdp_disconnect(instance);
1378
1379 return exit_code;
1380}
1381
1382/* RDP main loop.
1383 * Connects RDP, loops while running and handles event and dispatch, cleans up
1384 * after the connection ends. */
1385static DWORD WINAPI sdl_client_thread_proc(SdlContext* sdl)
1386{
1387 WINPR_ASSERT(sdl);
1388
1389 std::string error_msg;
1390 int exit_code = sdl_client_thread_connect(sdl, error_msg);
1391 if (exit_code == SDL_EXIT_SUCCESS)
1392 exit_code = sdl_client_thread_run(sdl, error_msg);
1393 sdl_client_cleanup(sdl, exit_code, error_msg);
1394
1395 return static_cast<DWORD>(exit_code);
1396}
1397
1398/* Optional global initializer.
1399 * Here we just register a signal handler to print out stack traces
1400 * if available. */
1401static BOOL sdl_client_global_init()
1402{
1403#if defined(_WIN32)
1404 WSADATA wsaData = {};
1405 const DWORD wVersionRequested = MAKEWORD(1, 1);
1406 const int rc = WSAStartup(wVersionRequested, &wsaData);
1407 if (rc != 0)
1408 {
1409 WLog_ERR(SDL_TAG, "WSAStartup failed with %s [%d]", gai_strerrorA(rc), rc);
1410 return FALSE;
1411 }
1412#endif
1413
1414 return (freerdp_handle_signals() == 0);
1415}
1416
1417/* Optional global tear down */
1418static void sdl_client_global_uninit()
1419{
1420#if defined(_WIN32)
1421 WSACleanup();
1422#endif
1423}
1424
1425static BOOL sdl_client_new(freerdp* instance, rdpContext* context)
1426{
1427 auto sdl = reinterpret_cast<sdl_rdp_context*>(context);
1428
1429 if (!instance || !context)
1430 return FALSE;
1431
1432 sdl->sdl = new SdlContext(context);
1433 if (!sdl->sdl)
1434 return FALSE;
1435
1436 instance->PreConnect = sdl_pre_connect;
1437 instance->PostConnect = sdl_post_connect;
1438 instance->PostDisconnect = sdl_post_disconnect;
1439 instance->PostFinalDisconnect = sdl_post_final_disconnect;
1440 instance->AuthenticateEx = sdl_authenticate_ex;
1441 instance->VerifyCertificateEx = sdl_verify_certificate_ex;
1442 instance->VerifyChangedCertificateEx = sdl_verify_changed_certificate_ex;
1443 instance->LogonErrorInfo = sdl_logon_error_info;
1444 instance->PresentGatewayMessage = sdl_present_gateway_message;
1445 instance->ChooseSmartcard = sdl_choose_smartcard;
1446 instance->RetryDialog = sdl_retry_dialog;
1447
1448#ifdef WITH_WEBVIEW
1449 instance->GetAccessToken = sdl_webview_get_access_token;
1450#else
1451 instance->GetAccessToken = client_cli_get_access_token;
1452#endif
1453 /* TODO: Client display set up */
1454
1455 return TRUE;
1456}
1457
1458static void sdl_client_free([[maybe_unused]] freerdp* instance, rdpContext* context)
1459{
1460 auto sdl = reinterpret_cast<sdl_rdp_context*>(context);
1461
1462 if (!context)
1463 return;
1464
1465 delete sdl->sdl;
1466}
1467
1468static int sdl_client_start(rdpContext* context)
1469{
1470 auto sdl = get_context(context);
1471 WINPR_ASSERT(sdl);
1472
1473 sdl->thread = std::thread(sdl_client_thread_proc, sdl);
1474 return 0;
1475}
1476
1477static int sdl_client_stop(rdpContext* context)
1478{
1479 auto sdl = get_context(context);
1480 WINPR_ASSERT(sdl);
1481
1482 /* We do not want to use freerdp_abort_connect_context here.
1483 * It would change the exit code and we do not want that. */
1484 HANDLE event = freerdp_abort_event(context);
1485 if (!SetEvent(event))
1486 return -1;
1487
1488 sdl->thread.join();
1489 return 0;
1490}
1491
1492static int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
1493{
1494 WINPR_ASSERT(pEntryPoints);
1495
1496 ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS));
1497 pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION;
1498 pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
1499 pEntryPoints->GlobalInit = sdl_client_global_init;
1500 pEntryPoints->GlobalUninit = sdl_client_global_uninit;
1501 pEntryPoints->ContextSize = sizeof(sdl_rdp_context);
1502 pEntryPoints->ClientNew = sdl_client_new;
1503 pEntryPoints->ClientFree = sdl_client_free;
1504 pEntryPoints->ClientStart = sdl_client_start;
1505 pEntryPoints->ClientStop = sdl_client_stop;
1506 return 0;
1507}
1508
1509static void context_free(sdl_rdp_context* sdl)
1510{
1511 if (sdl)
1512 freerdp_client_context_free(&sdl->common.context);
1513}
1514
1515static const char* category2str(int category)
1516{
1517 switch (category)
1518 {
1519 case SDL_LOG_CATEGORY_APPLICATION:
1520 return "SDL_LOG_CATEGORY_APPLICATION";
1521 case SDL_LOG_CATEGORY_ERROR:
1522 return "SDL_LOG_CATEGORY_ERROR";
1523 case SDL_LOG_CATEGORY_ASSERT:
1524 return "SDL_LOG_CATEGORY_ASSERT";
1525 case SDL_LOG_CATEGORY_SYSTEM:
1526 return "SDL_LOG_CATEGORY_SYSTEM";
1527 case SDL_LOG_CATEGORY_AUDIO:
1528 return "SDL_LOG_CATEGORY_AUDIO";
1529 case SDL_LOG_CATEGORY_VIDEO:
1530 return "SDL_LOG_CATEGORY_VIDEO";
1531 case SDL_LOG_CATEGORY_RENDER:
1532 return "SDL_LOG_CATEGORY_RENDER";
1533 case SDL_LOG_CATEGORY_INPUT:
1534 return "SDL_LOG_CATEGORY_INPUT";
1535 case SDL_LOG_CATEGORY_TEST:
1536 return "SDL_LOG_CATEGORY_TEST";
1537 case SDL_LOG_CATEGORY_GPU:
1538 return "SDL_LOG_CATEGORY_GPU";
1539 case SDL_LOG_CATEGORY_RESERVED2:
1540 return "SDL_LOG_CATEGORY_RESERVED2";
1541 case SDL_LOG_CATEGORY_RESERVED3:
1542 return "SDL_LOG_CATEGORY_RESERVED3";
1543 case SDL_LOG_CATEGORY_RESERVED4:
1544 return "SDL_LOG_CATEGORY_RESERVED4";
1545 case SDL_LOG_CATEGORY_RESERVED5:
1546 return "SDL_LOG_CATEGORY_RESERVED5";
1547 case SDL_LOG_CATEGORY_RESERVED6:
1548 return "SDL_LOG_CATEGORY_RESERVED6";
1549 case SDL_LOG_CATEGORY_RESERVED7:
1550 return "SDL_LOG_CATEGORY_RESERVED7";
1551 case SDL_LOG_CATEGORY_RESERVED8:
1552 return "SDL_LOG_CATEGORY_RESERVED8";
1553 case SDL_LOG_CATEGORY_RESERVED9:
1554 return "SDL_LOG_CATEGORY_RESERVED9";
1555 case SDL_LOG_CATEGORY_RESERVED10:
1556 return "SDL_LOG_CATEGORY_RESERVED10";
1557 case SDL_LOG_CATEGORY_CUSTOM:
1558 default:
1559 return "SDL_LOG_CATEGORY_CUSTOM";
1560 }
1561}
1562
1563static SDL_LogPriority wloglevel2dl(DWORD level)
1564{
1565 switch (level)
1566 {
1567 case WLOG_TRACE:
1568 return SDL_LOG_PRIORITY_VERBOSE;
1569 case WLOG_DEBUG:
1570 return SDL_LOG_PRIORITY_DEBUG;
1571 case WLOG_INFO:
1572 return SDL_LOG_PRIORITY_INFO;
1573 case WLOG_WARN:
1574 return SDL_LOG_PRIORITY_WARN;
1575 case WLOG_ERROR:
1576 return SDL_LOG_PRIORITY_ERROR;
1577 case WLOG_FATAL:
1578 return SDL_LOG_PRIORITY_CRITICAL;
1579 case WLOG_OFF:
1580 default:
1581 return SDL_LOG_PRIORITY_VERBOSE;
1582 }
1583}
1584
1585static DWORD sdlpriority2wlog(SDL_LogPriority priority)
1586{
1587 DWORD level = WLOG_OFF;
1588 switch (priority)
1589 {
1590 case SDL_LOG_PRIORITY_VERBOSE:
1591 level = WLOG_TRACE;
1592 break;
1593 case SDL_LOG_PRIORITY_DEBUG:
1594 level = WLOG_DEBUG;
1595 break;
1596 case SDL_LOG_PRIORITY_INFO:
1597 level = WLOG_INFO;
1598 break;
1599 case SDL_LOG_PRIORITY_WARN:
1600 level = WLOG_WARN;
1601 break;
1602 case SDL_LOG_PRIORITY_ERROR:
1603 level = WLOG_ERROR;
1604 break;
1605 case SDL_LOG_PRIORITY_CRITICAL:
1606 level = WLOG_FATAL;
1607 break;
1608 default:
1609 break;
1610 }
1611
1612 return level;
1613}
1614
1615static void SDLCALL winpr_LogOutputFunction(void* userdata, int category, SDL_LogPriority priority,
1616 const char* message)
1617{
1618 auto sdl = static_cast<SdlContext*>(userdata);
1619 WINPR_ASSERT(sdl);
1620
1621 const DWORD level = sdlpriority2wlog(priority);
1622 auto log = sdl->log;
1623 if (!WLog_IsLevelActive(log, level))
1624 return;
1625
1626 WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, __LINE__, __FILE__, __func__, "[%s] %s",
1627 category2str(category), message);
1628}
1629
1630int main(int argc, char* argv[])
1631{
1632 int rc = -1;
1633 int status = 0;
1634 RDP_CLIENT_ENTRY_POINTS clientEntryPoints = {};
1635
1636 freerdp_client_warn_experimental(argc, argv);
1637
1638 RdpClientEntry(&clientEntryPoints);
1639 std::unique_ptr<sdl_rdp_context, void (*)(sdl_rdp_context*)> sdl_rdp(
1640 reinterpret_cast<sdl_rdp_context*>(freerdp_client_context_new(&clientEntryPoints)),
1641 context_free);
1642
1643 if (!sdl_rdp)
1644 return -1;
1645 auto sdl = sdl_rdp->sdl;
1646
1647 auto settings = sdl->context()->settings;
1648 WINPR_ASSERT(settings);
1649
1650 status = freerdp_client_settings_parse_command_line(settings, argc, argv, FALSE);
1651 if (status)
1652 {
1653 rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv);
1654 if (freerdp_settings_get_bool(settings, FreeRDP_ListMonitors))
1655 sdl_list_monitors(sdl);
1656 else
1657 {
1658 switch (status)
1659 {
1660 case COMMAND_LINE_STATUS_PRINT:
1661 case COMMAND_LINE_STATUS_PRINT_VERSION:
1662 case COMMAND_LINE_STATUS_PRINT_BUILDCONFIG:
1663 break;
1664 case COMMAND_LINE_STATUS_PRINT_HELP:
1665 default:
1666 SdlPref::print_config_file_help(3);
1667 break;
1668 }
1669 }
1670 return rc;
1671 }
1672
1673 SDL_SetLogOutputFunction(winpr_LogOutputFunction, sdl);
1674 auto level = WLog_GetLogLevel(sdl->log);
1675 SDL_SetLogPriorities(wloglevel2dl(level));
1676
1677 auto context = sdl->context();
1678 WINPR_ASSERT(context);
1679
1680 if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE))
1681 return -1;
1682
1683 if (freerdp_client_start(context) != 0)
1684 return -1;
1685
1686 rc = sdl_run(sdl);
1687
1688 if (freerdp_client_stop(context) != 0)
1689 return -1;
1690
1691 if (sdl->exit_code != 0)
1692 rc = sdl->exit_code;
1693
1694 return rc;
1695}
1696
1697bool SdlContext::update_fullscreen(bool enter)
1698{
1699 for (const auto& window : windows)
1700 {
1701 if (!sdl_push_user_event(SDL_EVENT_USER_WINDOW_FULLSCREEN, &window.second, enter))
1702 return false;
1703 }
1704 fullscreen = enter;
1705 return true;
1706}
1707
1708bool SdlContext::update_minimize()
1709{
1710 return sdl_push_user_event(SDL_EVENT_USER_WINDOW_MINIMIZE);
1711}
1712
1713bool SdlContext::update_resizeable(bool enable)
1714{
1715 const auto settings = context()->settings;
1716 const bool dyn = freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate);
1717 const bool smart = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing);
1718 bool use = (dyn && enable) || smart;
1719
1720 for (const auto& window : windows)
1721 {
1722 if (!sdl_push_user_event(SDL_EVENT_USER_WINDOW_RESIZEABLE, &window.second, use))
1723 return false;
1724 }
1725 resizeable = use;
1726
1727 return true;
1728}
1729
1730SdlContext::SdlContext(rdpContext* context)
1731 : _context(context), log(WLog_Get(SDL_TAG)), disp(this), input(this), clip(this),
1732 primary(nullptr, SDL_DestroySurface), rdp_thread_running(false)
1733{
1734 WINPR_ASSERT(context);
1735}
1736
1737void SdlContext::setHasCursor(bool val)
1738{
1739 this->_cursor_visible = val;
1740}
1741
1742bool SdlContext::hasCursor() const
1743{
1744 return _cursor_visible;
1745}
1746
1747bool SdlContext::redraw(bool suppress) const
1748{
1749 if (!_connected)
1750 return true;
1751
1752 auto gdi = context()->gdi;
1753 WINPR_ASSERT(gdi);
1754 return gdi_send_suppress_output(gdi, suppress);
1755}
1756
1757void SdlContext::setConnected(bool val)
1758{
1759 _connected = val;
1760}
1761
1762bool SdlContext::isConnected() const
1763{
1764 return _connected;
1765}
1766
1767rdpContext* SdlContext::context() const
1768{
1769 WINPR_ASSERT(_context);
1770 return _context;
1771}
1772
1773rdpClientContext* SdlContext::common() const
1774{
1775 return reinterpret_cast<rdpClientContext*>(context());
1776}
1777
1778void SdlContext::setCursor(rdpPointer* cursor)
1779{
1780 _cursor = cursor;
1781}
1782
1783rdpPointer* SdlContext::cursor() const
1784{
1785 return _cursor;
1786}
1787
1788void SdlContext::setMonitorIds(const std::vector<SDL_DisplayID>& ids)
1789{
1790 _monitorIds.clear();
1791 for (auto id : ids)
1792 {
1793 _monitorIds.push_back(id);
1794 }
1795}
1796
1797const std::vector<SDL_DisplayID>& SdlContext::monitorIds() const
1798{
1799 return _monitorIds;
1800}
1801
1802int64_t SdlContext::monitorId(uint32_t index) const
1803{
1804 if (index >= _monitorIds.size())
1805 {
1806 return -1;
1807 }
1808 return _monitorIds[index];
1809}
1810
1811void SdlContext::push(std::vector<SDL_Rect>&& rects)
1812{
1813 std::unique_lock lock(_queue_mux);
1814 _queue.emplace(std::move(rects));
1815}
1816
1817std::vector<SDL_Rect> SdlContext::pop()
1818{
1819 std::unique_lock lock(_queue_mux);
1820 if (_queue.empty())
1821 {
1822 return {};
1823 }
1824 auto val = std::move(_queue.front());
1825 _queue.pop();
1826 return val;
1827}
FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
FREERDP_API BOOL freerdp_settings_set_string(rdpSettings *settings, FreeRDP_Settings_Keys_String id, const char *param)
Sets a string settings value. The param is copied.
FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.
FREERDP_API const char * freerdp_settings_get_server_name(const rdpSettings *settings)
A helper function to return the correct server name.
FREERDP_API BOOL freerdp_settings_set_uint32(rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 param)
Sets a UINT32 settings value.
FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
FREERDP_API BOOL freerdp_settings_set_bool(rdpSettings *settings, FreeRDP_Settings_Keys_Bool id, BOOL param)
Sets a BOOL settings value.