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