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