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