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