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