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