FreeRDP
Loading...
Searching...
No Matches
sdl_context.cpp
1
19#include "sdl_context.hpp"
20#include "sdl_config.hpp"
21#include "sdl_channels.hpp"
22#include "sdl_monitor.hpp"
23#include "sdl_pointer.hpp"
24#include "sdl_touch.hpp"
25
26#include <sdl_common_utils.hpp>
27#include <scoped_guard.hpp>
28
29#include "dialogs/sdl_dialogs.hpp"
30
31#if defined(WITH_WEBVIEW)
32#include <aad/sdl_webview.hpp>
33#endif
34
35SdlContext::SdlContext(rdpContext* context)
36 : _context(context), _log(WLog_Get(CLIENT_TAG("SDL"))), _rdpThreadRunning(false),
37 _primary(nullptr, SDL_DestroySurface), _disp(this), _input(this), _clip(this), _dialog(_log)
38{
39 WINPR_ASSERT(context);
40 setMetadata();
41
42 auto instance = _context->instance;
43 WINPR_ASSERT(instance);
44
45 instance->PreConnect = preConnect;
46 instance->PostConnect = postConnect;
47 instance->PostDisconnect = postDisconnect;
48 instance->PostFinalDisconnect = postFinalDisconnect;
49 instance->AuthenticateEx = sdl_authenticate_ex;
50 instance->VerifyCertificateEx = sdl_verify_certificate_ex;
51 instance->VerifyChangedCertificateEx = sdl_verify_changed_certificate_ex;
52 instance->LogonErrorInfo = sdl_logon_error_info;
53 instance->PresentGatewayMessage = sdl_present_gateway_message;
54 instance->ChooseSmartcard = sdl_choose_smartcard;
55 instance->RetryDialog = sdl_retry_dialog;
56
57#ifdef WITH_WEBVIEW
58 instance->GetAccessToken = sdl_webview_get_access_token;
59#else
60 instance->GetAccessToken = client_cli_get_access_token;
61#endif
62 /* TODO: Client display set up */
63}
64
65void SdlContext::setHasCursor(bool val)
66{
67 this->_cursor_visible = val;
68}
69
70bool SdlContext::hasCursor() const
71{
72 return _cursor_visible;
73}
74
75void SdlContext::setMetadata()
76{
77 auto wmclass = freerdp_settings_get_string(_context->settings, FreeRDP_WmClass);
78 if (!wmclass || (strlen(wmclass) == 0))
79 wmclass = SDL_CLIENT_UUID;
80
81 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING, wmclass);
82 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING, SDL_CLIENT_NAME);
83 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_VERSION_STRING, SDL_CLIENT_VERSION);
84 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_CREATOR_STRING, SDL_CLIENT_VENDOR);
85 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_COPYRIGHT_STRING, SDL_CLIENT_COPYRIGHT);
86 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_URL_STRING, SDL_CLIENT_URL);
87 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_TYPE_STRING, SDL_CLIENT_TYPE);
88}
89
90int SdlContext::start()
91{
92 _thread = std::thread(rdpThreadRun, this);
93 return 0;
94}
95
96int SdlContext::join()
97{
98 /* We do not want to use freerdp_abort_connect_context here.
99 * It would change the exit code and we do not want that. */
100 HANDLE event = freerdp_abort_event(context());
101 if (!SetEvent(event))
102 return -1;
103
104 _thread.join();
105 return 0;
106}
107
108void SdlContext::cleanup()
109{
110 std::unique_lock lock(_critical);
111 _windows.clear();
112 _dialog.destroy();
113 _primary.reset();
114}
115
116bool SdlContext::shallAbort(bool ignoreDialogs)
117{
118 std::unique_lock lock(_critical);
119 if (freerdp_shall_disconnect_context(context()))
120 {
121 if (ignoreDialogs)
122 return true;
123 if (_rdpThreadRunning)
124 return false;
125 return !getDialog().isRunning();
126 }
127 return false;
128}
129
130/* Called before a connection is established.
131 * Set all configuration options to support and load channels here. */
132BOOL SdlContext::preConnect(freerdp* instance)
133{
134 WINPR_ASSERT(instance);
135 WINPR_ASSERT(instance->context);
136
137 auto sdl = get_context(instance->context);
138
139 auto settings = instance->context->settings;
140 WINPR_ASSERT(settings);
141
142 if (!freerdp_settings_set_bool(settings, FreeRDP_CertificateCallbackPreferPEM, TRUE))
143 return FALSE;
144
145 /* Optional OS identifier sent to server */
146 if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX))
147 return FALSE;
148 if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_SDL))
149 return FALSE;
150 /* OrderSupport is initialized at this point.
151 * Only override it if you plan to implement custom order
152 * callbacks or deactivate certain features. */
153 /* Register the channel listeners.
154 * They are required to set up / tear down channels if they are loaded. */
155 PubSub_SubscribeChannelConnected(instance->context->pubSub, sdl_OnChannelConnectedEventHandler);
156 PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
157 sdl_OnChannelDisconnectedEventHandler);
158
159 if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
160 {
161 UINT32 maxWidth = 0;
162 UINT32 maxHeight = 0;
163
164 if (!sdl_detect_monitors(sdl, &maxWidth, &maxHeight))
165 return FALSE;
166
167 if ((maxWidth != 0) && (maxHeight != 0) &&
168 !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
169 {
170 WLog_Print(sdl->getWLog(), WLOG_INFO, "Update size to %ux%u", maxWidth, maxHeight);
171 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, maxWidth))
172 return FALSE;
173 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, maxHeight))
174 return FALSE;
175 }
176 }
177 else
178 {
179 /* Check +auth-only has a username and password. */
180 if (!freerdp_settings_get_string(settings, FreeRDP_Password))
181 {
182 WLog_Print(sdl->getWLog(), WLOG_INFO,
183 "auth-only, but no password set. Please provide one.");
184 return FALSE;
185 }
186
187 if (!freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, TRUE))
188 return FALSE;
189
190 WLog_Print(sdl->getWLog(), WLOG_INFO, "Authentication only. Don't connect SDL.");
191 }
192
193 if (!sdl->getInputChannelContext().initialize())
194 return FALSE;
195
196 /* TODO: Any code your client requires */
197 return TRUE;
198}
199
200/* Called after a RDP connection was successfully established.
201 * Settings might have changed during negotiation of client / server feature
202 * support.
203 *
204 * Set up local framebuffers and paing callbacks.
205 * If required, register pointer callbacks to change the local mouse cursor
206 * when hovering over the RDP window
207 */
208BOOL SdlContext::postConnect(freerdp* instance)
209{
210 WINPR_ASSERT(instance);
211
212 auto context = instance->context;
213 WINPR_ASSERT(context);
214
215 auto sdl = get_context(context);
216
217 if (freerdp_settings_get_bool(context->settings, FreeRDP_UseMultimon))
218 {
219 const auto driver = SDL_GetCurrentVideoDriver();
220 bool buggy = false;
221 if (driver)
222 {
223 if (strcmp(driver, "wayland") == 0)
224 buggy = true;
225 else if (strcmp(driver, "x11") == 0)
226 {
227 auto env = SDL_GetEnvironment();
228 auto xdg = SDL_GetEnvironmentVariable(env, "XDG_SESSION_TYPE");
229 auto qpa = SDL_GetEnvironmentVariable(env, "QT_QPA_PLATFORM");
230 if (xdg && (strcmp(xdg, "wayland") == 0))
231 buggy = true;
232 else if (qpa && (strcmp(qpa, "wayland") == 0))
233 buggy = true;
234 }
235 }
236
237 if (buggy)
238 {
239 const auto name = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
240
241 WLog_Print(sdl->getWLog(), WLOG_WARN,
242 "%s is affected by wayland bug "
243 "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/179",
244 name);
245 WLog_Print(
246 sdl->getWLog(), WLOG_WARN,
247 "you will not be able to properly use all monitors for FreeRDP unless this is "
248 "resolved and the SDL library you are using supports this.");
249 WLog_Print(sdl->getWLog(), WLOG_WARN,
250 "For the time being run %s from an X11 session or only use single monitor "
251 "fullscreen /f",
252 name);
253 }
254 }
255
256 // Retry was successful, discard dialog
257 sdl->getDialog().show(false);
258
259 if (freerdp_settings_get_bool(context->settings, FreeRDP_AuthenticationOnly))
260 {
261 /* Check +auth-only has a username and password. */
262 if (!freerdp_settings_get_string(context->settings, FreeRDP_Password))
263 {
264 WLog_Print(sdl->getWLog(), WLOG_INFO,
265 "auth-only, but no password set. Please provide one.");
266 return FALSE;
267 }
268
269 WLog_Print(sdl->getWLog(), WLOG_INFO, "Authentication only. Don't connect to X.");
270 return TRUE;
271 }
272
273 if (!sdl->waitForWindowsCreated())
274 return FALSE;
275
276 sdl->_sdlPixelFormat = SDL_PIXELFORMAT_BGRA32;
277 if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
278 return FALSE;
279
280 if (!sdl->createPrimary())
281 return FALSE;
282
283 if (!sdl_register_pointer(instance->context->graphics))
284 return FALSE;
285
286 WINPR_ASSERT(context->update);
287
288 context->update->BeginPaint = beginPaint;
289 context->update->EndPaint = endPaint;
290 context->update->PlaySound = playSound;
291 context->update->DesktopResize = desktopResize;
292 context->update->SetKeyboardIndicators = sdlInput::keyboard_set_indicators;
293 context->update->SetKeyboardImeStatus = sdlInput::keyboard_set_ime_status;
294
295 if (!sdl->setResizeable(false))
296 return FALSE;
297 if (!sdl->setFullscreen(freerdp_settings_get_bool(context->settings, FreeRDP_Fullscreen) ||
298 freerdp_settings_get_bool(context->settings, FreeRDP_UseMultimon)))
299 return FALSE;
300 sdl->setConnected(true);
301 return TRUE;
302}
303
304/* This function is called whether a session ends by failure or success.
305 * Clean up everything allocated by pre_connect and post_connect.
306 */
307void SdlContext::postDisconnect(freerdp* instance)
308{
309 if (!instance)
310 return;
311
312 if (!instance->context)
313 return;
314
315 auto sdl = get_context(instance->context);
316 sdl->setConnected(false);
317
318 gdi_free(instance);
319}
320
321void SdlContext::postFinalDisconnect(freerdp* instance)
322{
323 if (!instance)
324 return;
325
326 if (!instance->context)
327 return;
328
329 PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
330 sdl_OnChannelConnectedEventHandler);
331 PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub,
332 sdl_OnChannelDisconnectedEventHandler);
333}
334
335/* Create a SDL surface from the GDI buffer */
336bool SdlContext::createPrimary()
337{
338 auto gdi = context()->gdi;
339 WINPR_ASSERT(gdi);
340
341 _primary = SDLSurfacePtr(
342 SDL_CreateSurfaceFrom(static_cast<int>(gdi->width), static_cast<int>(gdi->height),
343 pixelFormat(), gdi->primary_buffer, static_cast<int>(gdi->stride)),
344 SDL_DestroySurface);
345 if (!_primary)
346 return false;
347
348 SDL_SetSurfaceBlendMode(_primary.get(), SDL_BLENDMODE_NONE);
349 SDL_Rect surfaceRect = { 0, 0, gdi->width, gdi->height };
350 SDL_FillSurfaceRect(_primary.get(), &surfaceRect,
351 SDL_MapSurfaceRGBA(_primary.get(), 0, 0, 0, 0xff));
352
353 return true;
354}
355
356bool SdlContext::createWindows()
357{
358 auto settings = context()->settings;
359 const auto& title = windowTitle();
360
361 ScopeGuard guard1([&]() { _windowsCreatedEvent.set(); });
362
363 UINT32 windowCount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
364
365 Sint32 originX = 0;
366 Sint32 originY = 0;
367 for (UINT32 x = 0; x < windowCount; x++)
368 {
369 auto id = monitorId(x);
370 if (id < 0)
371 return false;
372
373 auto monitor = static_cast<rdpMonitor*>(
374 freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x));
375
376 originX = std::min<Sint32>(monitor->x, originX);
377 originY = std::min<Sint32>(monitor->y, originY);
378 }
379
380 for (UINT32 x = 0; x < windowCount; x++)
381 {
382 auto id = monitorId(x);
383 if (id < 0)
384 return false;
385
386 auto monitor = static_cast<rdpMonitor*>(
387 freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x));
388
389 auto w = WINPR_ASSERTING_INT_CAST(Uint32, monitor->width);
390 auto h = WINPR_ASSERTING_INT_CAST(Uint32, monitor->height);
391 if (!(freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) ||
392 freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)))
393 {
394 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
395 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
396 }
397
398 Uint32 flags = SDL_WINDOW_HIGH_PIXEL_DENSITY;
399
400 if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) &&
401 !freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
402 {
403 flags |= SDL_WINDOW_FULLSCREEN;
404 }
405
406 if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
407 {
408 flags |= SDL_WINDOW_BORDERLESS;
409 }
410
411 if (!freerdp_settings_get_bool(settings, FreeRDP_Decorations))
412 flags |= SDL_WINDOW_BORDERLESS;
413
414 auto did = WINPR_ASSERTING_INT_CAST(SDL_DisplayID, id);
415 auto window = SdlWindow::create(did, title, flags, w, h);
416
417 if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
418 {
419 window.setOffsetX(originX - monitor->x);
420 window.setOffsetY(originY - monitor->y);
421 }
422
423 _windows.insert({ window.id(), std::move(window) });
424 }
425
426 return true;
427}
428
429bool SdlContext::updateWindowList()
430{
431 std::vector<rdpMonitor> list;
432 list.reserve(_windows.size());
433 for (const auto& win : _windows)
434 list.push_back(win.second.monitor(_windows.size() == 1));
435
436 return freerdp_settings_set_monitor_def_array_sorted(context()->settings, list.data(),
437 list.size());
438}
439
440std::string SdlContext::windowTitle() const
441{
442 const char* prefix = "FreeRDP:";
443
444 const auto windowTitle = freerdp_settings_get_string(context()->settings, FreeRDP_WindowTitle);
445 if (windowTitle)
446 return windowTitle;
447
448 const auto name = freerdp_settings_get_server_name(context()->settings);
449 const auto port = freerdp_settings_get_uint32(context()->settings, FreeRDP_ServerPort);
450 const auto addPort = (port != 3389);
451
452 std::stringstream ss;
453 ss << prefix << " " << name;
454
455 if (addPort)
456 ss << ":" << port;
457
458 return ss.str();
459}
460
461bool SdlContext::waitForWindowsCreated()
462{
463 {
464 std::unique_lock<CriticalSection> lock(_critical);
465 _windowsCreatedEvent.clear();
466 if (!sdl_push_user_event(SDL_EVENT_USER_CREATE_WINDOWS, this))
467 return false;
468 }
469
470 HANDLE handles[] = { _windowsCreatedEvent.handle(), freerdp_abort_event(context()) };
471
472 const DWORD rc = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
473 switch (rc)
474 {
475 case WAIT_OBJECT_0:
476 return true;
477 default:
478 return false;
479 }
480}
481
482/* This function is called when the library completed composing a new
483 * frame. Read out the changed areas and blit them to your output device.
484 * The image buffer will have the format specified by gdi_init
485 */
486BOOL SdlContext::endPaint(rdpContext* context)
487{
488 auto sdl = get_context(context);
489 WINPR_ASSERT(sdl);
490
491 auto gdi = context->gdi;
492 WINPR_ASSERT(gdi);
493 WINPR_ASSERT(gdi->primary);
494
495 HGDI_DC hdc = gdi->primary->hdc;
496 WINPR_ASSERT(hdc);
497 if (!hdc->hwnd)
498 return TRUE;
499
500 HGDI_WND hwnd = hdc->hwnd;
501 WINPR_ASSERT(hwnd->invalid || (hwnd->ninvalid == 0));
502
503 if (hwnd->invalid->null)
504 return TRUE;
505
506 WINPR_ASSERT(hwnd->invalid);
507 if (gdi->suppressOutput || hwnd->invalid->null)
508 return TRUE;
509
510 const INT32 ninvalid = hwnd->ninvalid;
511 const GDI_RGN* cinvalid = hwnd->cinvalid;
512
513 if (ninvalid < 1)
514 return TRUE;
515
516 std::vector<SDL_Rect> rects;
517 for (INT32 x = 0; x < ninvalid; x++)
518 {
519 auto& rgn = cinvalid[x];
520 rects.push_back({ rgn.x, rgn.y, rgn.w, rgn.h });
521 }
522
523 sdl->push(std::move(rects));
524 return sdl_push_user_event(SDL_EVENT_USER_UPDATE);
525}
526
527void SdlContext::sdl_client_cleanup(int exit_code, const std::string& error_msg)
528{
529 rdpSettings* settings = context()->settings;
530 WINPR_ASSERT(settings);
531
532 _rdpThreadRunning = false;
533 bool showError = false;
534 if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
535 WLog_Print(getWLog(), WLOG_INFO, "Authentication only, exit status %s [%" PRId32 "]",
536 sdl::error::exitCodeToTag(exit_code), exit_code);
537 else
538 {
539 switch (exit_code)
540 {
541 case sdl::error::SUCCESS:
542 case sdl::error::DISCONNECT:
543 case sdl::error::LOGOFF:
544 case sdl::error::DISCONNECT_BY_USER:
545 case sdl::error::CONNECT_CANCELLED:
546 break;
547 default:
548 {
549 getDialog().showError(error_msg);
550 }
551 break;
552 }
553 }
554
555 if (!showError)
556 getDialog().show(false);
557
558 _exitCode = exit_code;
559 std::ignore = sdl_push_user_event(SDL_EVENT_USER_QUIT);
560 SDL_CleanupTLS();
561}
562
563int SdlContext::sdl_client_thread_connect(std::string& error_msg)
564{
565 auto instance = context()->instance;
566 WINPR_ASSERT(instance);
567
568 _rdpThreadRunning = true;
569 BOOL rc = freerdp_connect(instance);
570
571 rdpSettings* settings = context()->settings;
572 WINPR_ASSERT(settings);
573
574 int exit_code = sdl::error::SUCCESS;
575 if (!rc)
576 {
577 UINT32 error = freerdp_get_last_error(context());
578 exit_code = sdl::error::errorToExitCode(error);
579 }
580
581 if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
582 {
583 DWORD code = freerdp_get_last_error(context());
584 freerdp_abort_connect_context(context());
585 WLog_Print(getWLog(), WLOG_ERROR, "Authentication only, %s [0x%08" PRIx32 "] %s",
586 freerdp_get_last_error_name(code), code, freerdp_get_last_error_string(code));
587 return exit_code;
588 }
589
590 if (!rc)
591 {
592 DWORD code = freerdp_error_info(instance);
593 if (exit_code == sdl::error::SUCCESS)
594 {
595 char* msg = nullptr;
596 size_t len = 0;
597 exit_code = error_info_to_error(&code, &msg, &len);
598 if (msg)
599 error_msg = msg;
600 free(msg);
601 }
602
603 auto last = freerdp_get_last_error(context());
604 if (error_msg.empty())
605 {
606 char* msg = nullptr;
607 size_t len = 0;
608 winpr_asprintf(&msg, &len, "%s [0x%08" PRIx32 "]\n%s",
609 freerdp_get_last_error_name(last), last,
610 freerdp_get_last_error_string(last));
611 if (msg)
612 error_msg = msg;
613 free(msg);
614 }
615
616 if (exit_code == sdl::error::SUCCESS)
617 {
618 if (last == FREERDP_ERROR_AUTHENTICATION_FAILED)
619 exit_code = sdl::error::AUTH_FAILURE;
620 else if (code == ERRINFO_SUCCESS)
621 exit_code = sdl::error::CONN_FAILED;
622 }
623
624 getDialog().show(false);
625 }
626
627 return exit_code;
628}
629
630int SdlContext::sdl_client_thread_run(std::string& error_msg)
631{
632 auto instance = context()->instance;
633 WINPR_ASSERT(instance);
634
635 int exit_code = sdl::error::SUCCESS;
636 while (!freerdp_shall_disconnect_context(context()))
637 {
638 HANDLE handles[MAXIMUM_WAIT_OBJECTS] = {};
639 /*
640 * win8 and server 2k12 seem to have some timing issue/race condition
641 * when a initial sync request is send to sync the keyboard indicators
642 * sending the sync event twice fixed this problem
643 */
644 if (freerdp_focus_required(instance))
645 {
646 auto ctx = get_context(context());
647 WINPR_ASSERT(ctx);
648
649 auto& input = ctx->getInputChannelContext();
650 if (!input.keyboard_focus_in())
651 break;
652 if (!input.keyboard_focus_in())
653 break;
654 }
655
656 const DWORD nCount = freerdp_get_event_handles(context(), handles, ARRAYSIZE(handles));
657
658 if (nCount == 0)
659 {
660 WLog_Print(getWLog(), WLOG_ERROR, "freerdp_get_event_handles failed");
661 break;
662 }
663
664 const DWORD status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
665
666 if (status == WAIT_FAILED)
667 {
668 WLog_Print(getWLog(), WLOG_ERROR, "WaitForMultipleObjects WAIT_FAILED");
669 break;
670 }
671
672 if (!freerdp_check_event_handles(context()))
673 {
674 if (client_auto_reconnect(instance))
675 {
676 // Retry was successful, discard dialog
677 getDialog().show(false);
678 continue;
679 }
680 else
681 {
682 /*
683 * Indicate an unsuccessful connection attempt if reconnect
684 * did not succeed and no other error was specified.
685 */
686 if (freerdp_error_info(instance) == 0)
687 exit_code = sdl::error::CONN_FAILED;
688 }
689
690 if (freerdp_get_last_error(context()) == FREERDP_ERROR_SUCCESS)
691 WLog_Print(getWLog(), WLOG_ERROR, "WaitForMultipleObjects failed with %" PRIu32 "",
692 status);
693 if (freerdp_get_last_error(context()) == FREERDP_ERROR_SUCCESS)
694 WLog_Print(getWLog(), WLOG_ERROR, "Failed to check FreeRDP event handles");
695 break;
696 }
697 }
698
699 if (exit_code == sdl::error::SUCCESS)
700 {
701 DWORD code = 0;
702 {
703 char* emsg = nullptr;
704 size_t elen = 0;
705 exit_code = error_info_to_error(&code, &emsg, &elen);
706 if (emsg)
707 error_msg = emsg;
708 free(emsg);
709 }
710
711 if ((code == ERRINFO_LOGOFF_BY_USER) &&
712 (freerdp_get_disconnect_ultimatum(context()) == Disconnect_Ultimatum_user_requested))
713 {
714 const char* msg = "Error info says user did not initiate but disconnect ultimatum says "
715 "they did; treat this as a user logoff";
716
717 char* emsg = nullptr;
718 size_t elen = 0;
719 winpr_asprintf(&emsg, &elen, "%s", msg);
720 if (emsg)
721 error_msg = emsg;
722 free(emsg);
723
724 /* This situation might be limited to Windows XP. */
725 WLog_Print(getWLog(), WLOG_INFO, "%s", msg);
726 exit_code = sdl::error::LOGOFF;
727 }
728 }
729
730 freerdp_disconnect(instance);
731
732 return exit_code;
733}
734
735/* RDP main loop.
736 * Connects RDP, loops while running and handles event and dispatch, cleans up
737 * after the connection ends. */
738DWORD SdlContext::rdpThreadRun(SdlContext* sdl)
739{
740 WINPR_ASSERT(sdl);
741
742 std::string error_msg;
743 int exit_code = sdl->sdl_client_thread_connect(error_msg);
744 if (exit_code == sdl::error::SUCCESS)
745 exit_code = sdl->sdl_client_thread_run(error_msg);
746 sdl->sdl_client_cleanup(exit_code, error_msg);
747
748 return static_cast<DWORD>(exit_code);
749}
750
751int SdlContext::error_info_to_error(DWORD* pcode, char** msg, size_t* len) const
752{
753 const DWORD code = freerdp_error_info(context()->instance);
754 const char* name = freerdp_get_error_info_name(code);
755 const char* str = freerdp_get_error_info_string(code);
756 const int exit_code = sdl::error::errorToExitCode(code);
757
758 winpr_asprintf(msg, len, "Terminate with %s due to ERROR_INFO %s [0x%08" PRIx32 "]: %s",
759 sdl::error::errorToExitCodeTag(code), name, code, str);
760 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%s", *msg);
761 if (pcode)
762 *pcode = code;
763 return exit_code;
764}
765
766void SdlContext::applyMonitorOffset(SDL_WindowID window, float& x, float& y) const
767{
768 if (!freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon))
769 return;
770
771 auto w = getWindowForId(window);
772 x -= static_cast<float>(w->offsetX());
773 y -= static_cast<float>(w->offsetY());
774}
775
776bool SdlContext::drawToWindow(SdlWindow& window, const std::vector<SDL_Rect>& rects)
777{
778 if (!isConnected())
779 return true;
780
781 auto gdi = context()->gdi;
782 WINPR_ASSERT(gdi);
783
784 auto size = window.rect();
785
786 std::unique_lock lock(_critical);
787 auto surface = _primary.get();
788 if (freerdp_settings_get_bool(context()->settings, FreeRDP_SmartSizing))
789 {
790 window.setOffsetX(0);
791 window.setOffsetY(0);
792 if (gdi->width < size.w)
793 {
794 window.setOffsetX((size.w - gdi->width) / 2);
795 }
796 if (gdi->height < size.h)
797 {
798 window.setOffsetY((size.h - gdi->height) / 2);
799 }
800
801 _localScale = { static_cast<float>(size.w) / static_cast<float>(gdi->width),
802 static_cast<float>(size.h) / static_cast<float>(gdi->height) };
803 if (!window.drawScaledRects(surface, _localScale, rects))
804 return false;
805 }
806 else
807 {
808 SDL_Point offset{ 0, 0 };
809 if (freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon))
810 offset = { window.offsetX(), window.offsetY() };
811 if (!window.drawRects(surface, offset, rects))
812 return false;
813 }
814
815 window.updateSurface();
816 return true;
817}
818
819bool SdlContext::minimizeAllWindows()
820{
821 for (auto& w : _windows)
822 w.second.minimize();
823 return true;
824}
825
826int SdlContext::exitCode() const
827{
828 return _exitCode;
829}
830
831SDL_PixelFormat SdlContext::pixelFormat() const
832{
833 return _sdlPixelFormat;
834}
835
836bool SdlContext::addDisplayWindow(SDL_DisplayID id)
837{
838 const auto flags =
839 SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS;
840 auto title = sdl::utils::windowTitle(context()->settings);
841 auto w = SdlWindow::create(id, title, flags);
842 _windows.emplace(w.id(), std::move(w));
843 return true;
844}
845
846bool SdlContext::removeDisplay(SDL_DisplayID id)
847{
848 for (auto& w : _windows)
849 {
850 if (w.second.displayIndex() == id)
851 _windows.erase(w.first);
852 }
853 return true;
854}
855
856const SdlWindow* SdlContext::getWindowForId(SDL_WindowID id) const
857{
858 auto it = _windows.find(id);
859 if (it == _windows.end())
860 return nullptr;
861 return &it->second;
862}
863
864SdlWindow* SdlContext::getWindowForId(SDL_WindowID id)
865{
866 auto it = _windows.find(id);
867 if (it == _windows.end())
868 return nullptr;
869 return &it->second;
870}
871
872SdlWindow* SdlContext::getFirstWindow()
873{
874 if (_windows.empty())
875 return nullptr;
876 return &_windows.begin()->second;
877}
878
879sdlDispContext& SdlContext::getDisplayChannelContext()
880{
881 return _disp;
882}
883
884sdlInput& SdlContext::getInputChannelContext()
885{
886 return _input;
887}
888
889sdlClip& SdlContext::getClipboardChannelContext()
890{
891 return _clip;
892}
893
894SdlConnectionDialogWrapper& SdlContext::getDialog()
895{
896 return _dialog;
897}
898
899wLog* SdlContext::getWLog()
900{
901 return _log;
902}
903
904bool SdlContext::moveMouseTo(const SDL_FPoint& pos)
905{
906 auto window = SDL_GetMouseFocus();
907 if (!window)
908 return true;
909
910 const auto id = SDL_GetWindowID(window);
911 const auto spos = pixelToScreen(id, pos);
912 SDL_WarpMouseInWindow(window, spos.x, spos.y);
913 return true;
914}
915
916bool SdlContext::handleEvent(const SDL_MouseMotionEvent& ev)
917{
918 SDL_Event copy{};
919 copy.motion = ev;
920 if (!eventToPixelCoordinates(ev.windowID, copy))
921 return false;
922 removeLocalScaling(copy.motion.x, copy.motion.y);
923 removeLocalScaling(copy.motion.xrel, copy.motion.yrel);
924 applyMonitorOffset(copy.motion.windowID, copy.motion.x, copy.motion.y);
925
926 return SdlTouch::handleEvent(this, copy.motion);
927}
928
929bool SdlContext::handleEvent(const SDL_MouseWheelEvent& ev)
930{
931 SDL_Event copy{};
932 copy.wheel = ev;
933 if (!eventToPixelCoordinates(ev.windowID, copy))
934 return false;
935 removeLocalScaling(copy.wheel.mouse_x, copy.wheel.mouse_y);
936 return SdlTouch::handleEvent(this, copy.wheel);
937}
938
939bool SdlContext::handleEvent(const SDL_WindowEvent& ev)
940{
941 if (!getDisplayChannelContext().handleEvent(ev))
942 return false;
943
944 auto window = getWindowForId(ev.windowID);
945 if (!window)
946 return true;
947
948 {
949 const auto& r = window->rect();
950 const auto& b = window->bounds();
951 const auto& scale = window->scale();
952 const auto& orientation = window->orientation();
953 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
954 "%s: [%u] %dx%d-%dx%d {%dx%d-%dx%d}{scale=%f,orientation=%s}",
955 sdl::utils::toString(ev.type).c_str(), ev.windowID, r.x, r.y, r.w, r.h, b.x,
956 b.y, b.w, b.h, static_cast<double>(scale),
957 sdl::utils::toString(orientation).c_str());
958 }
959
960 switch (ev.type)
961 {
962 case SDL_EVENT_WINDOW_MOUSE_ENTER:
963 return restoreCursor();
964 case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
965 if (isConnected())
966 {
967 if (!window->fill())
968 return false;
969 if (!drawToWindow(*window))
970 return false;
971 if (!restoreCursor())
972 return false;
973 }
974 break;
975 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
976 if (!window->fill())
977 return false;
978 if (!drawToWindow(*window))
979 return false;
980 if (!restoreCursor())
981 return false;
982 break;
983 case SDL_EVENT_WINDOW_MOVED:
984 {
985 auto r = window->rect();
986 auto id = window->id();
987 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%u: %dx%d-%dx%d", id, r.x, r.y, r.w, r.h);
988 }
989 break;
990 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
991 {
992 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Window closed, terminating RDP session...");
993 freerdp_abort_connect_context(context());
994 }
995 break;
996 default:
997 break;
998 }
999 return true;
1000}
1001
1002bool SdlContext::handleEvent(const SDL_DisplayEvent& ev)
1003{
1004 if (!getDisplayChannelContext().handleEvent(ev))
1005 return false;
1006
1007 switch (ev.type)
1008 {
1009 case SDL_EVENT_DISPLAY_REMOVED: // Can't show details for this one...
1010 break;
1011 default:
1012 {
1013 SDL_Rect r = {};
1014 if (!SDL_GetDisplayBounds(ev.displayID, &r))
1015 return false;
1016 const auto name = SDL_GetDisplayName(ev.displayID);
1017 if (!name)
1018 return false;
1019 const auto orientation = SDL_GetCurrentDisplayOrientation(ev.displayID);
1020 const auto scale = SDL_GetDisplayContentScale(ev.displayID);
1021 const auto mode = SDL_GetCurrentDisplayMode(ev.displayID);
1022 if (!mode)
1023 return false;
1024
1025 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
1026 "%s: [%u, %s] %dx%d-%dx%d {orientation=%s, scale=%f}%s",
1027 sdl::utils::toString(ev.type).c_str(), ev.displayID, name, r.x, r.y, r.w,
1028 r.h, sdl::utils::toString(orientation).c_str(), static_cast<double>(scale),
1029 sdl::utils::toString(mode).c_str());
1030 }
1031 break;
1032 }
1033 return true;
1034}
1035
1036bool SdlContext::handleEvent(const SDL_MouseButtonEvent& ev)
1037{
1038 SDL_Event copy = {};
1039 copy.button = ev;
1040 if (!eventToPixelCoordinates(ev.windowID, copy))
1041 return false;
1042 removeLocalScaling(copy.button.x, copy.button.y);
1043 applyMonitorOffset(copy.button.windowID, copy.button.x, copy.button.y);
1044 return SdlTouch::handleEvent(this, copy.button);
1045}
1046
1047bool SdlContext::handleEvent(const SDL_TouchFingerEvent& ev)
1048{
1049 SDL_Event copy{};
1050 copy.tfinger = ev;
1051 if (!eventToPixelCoordinates(ev.windowID, copy))
1052 return false;
1053 removeLocalScaling(copy.tfinger.dx, copy.tfinger.dy);
1054 removeLocalScaling(copy.tfinger.x, copy.tfinger.y);
1055 applyMonitorOffset(copy.tfinger.windowID, copy.tfinger.x, copy.tfinger.y);
1056 return SdlTouch::handleEvent(this, copy.tfinger);
1057}
1058
1059bool SdlContext::eventToPixelCoordinates(SDL_WindowID id, SDL_Event& ev)
1060{
1061 auto w = getWindowForId(id);
1062 if (!w)
1063 return false;
1064
1065 /* Ignore errors here, sometimes SDL has no renderer */
1066 auto renderer = SDL_GetRenderer(w->window());
1067 if (!renderer)
1068 return true;
1069 return SDL_ConvertEventToRenderCoordinates(renderer, &ev);
1070}
1071
1072SDL_FPoint SdlContext::applyLocalScaling(const SDL_FPoint& val) const
1073{
1074 if (!freerdp_settings_get_bool(context()->settings, FreeRDP_SmartSizing))
1075 return val;
1076
1077 auto rval = val;
1078 rval.x *= _localScale.x;
1079 rval.y *= _localScale.y;
1080 return rval;
1081}
1082
1083void SdlContext::removeLocalScaling(float& x, float& y) const
1084{
1085 if (!freerdp_settings_get_bool(context()->settings, FreeRDP_SmartSizing))
1086 return;
1087 x /= _localScale.x;
1088 y /= _localScale.y;
1089}
1090
1091SDL_FPoint SdlContext::screenToPixel(SDL_WindowID id, const SDL_FPoint& pos)
1092{
1093 auto w = getWindowForId(id);
1094 if (!w)
1095 return {};
1096
1097 /* Ignore errors here, sometimes SDL has no renderer */
1098 auto renderer = SDL_GetRenderer(w->window());
1099 if (!renderer)
1100 return pos;
1101
1102 SDL_FPoint rpos{};
1103 if (!SDL_RenderCoordinatesFromWindow(renderer, pos.x, pos.y, &rpos.x, &rpos.y))
1104 return {};
1105 removeLocalScaling(rpos.x, rpos.y);
1106 return rpos;
1107}
1108
1109SDL_FPoint SdlContext::pixelToScreen(SDL_WindowID id, const SDL_FPoint& pos)
1110{
1111 auto w = getWindowForId(id);
1112 if (!w)
1113 return {};
1114
1115 /* Ignore errors here, sometimes SDL has no renderer */
1116 auto renderer = SDL_GetRenderer(w->window());
1117 if (!renderer)
1118 return pos;
1119
1120 SDL_FPoint rpos{};
1121 if (!SDL_RenderCoordinatesToWindow(renderer, pos.x, pos.y, &rpos.x, &rpos.y))
1122 return {};
1123 return applyLocalScaling(rpos);
1124}
1125
1126SDL_FRect SdlContext::pixelToScreen(SDL_WindowID id, const SDL_FRect& pos)
1127{
1128 const auto fpos = pixelToScreen(id, SDL_FPoint{ pos.x, pos.y });
1129 const auto size = pixelToScreen(id, SDL_FPoint{ pos.w, pos.h });
1130 return { fpos.x, fpos.y, size.x, size.y };
1131}
1132
1133bool SdlContext::handleEvent(const SDL_Event& ev)
1134{
1135 if ((ev.type >= SDL_EVENT_DISPLAY_FIRST) && (ev.type <= SDL_EVENT_DISPLAY_LAST))
1136 {
1137 const auto& dev = ev.display;
1138 return handleEvent(dev);
1139 }
1140 if ((ev.type >= SDL_EVENT_WINDOW_FIRST) && (ev.type <= SDL_EVENT_WINDOW_LAST))
1141 {
1142 const auto& wev = ev.window;
1143 return handleEvent(wev);
1144 }
1145 switch (ev.type)
1146 {
1147 case SDL_EVENT_FINGER_DOWN:
1148 case SDL_EVENT_FINGER_UP:
1149 case SDL_EVENT_FINGER_MOTION:
1150 {
1151 const auto& cev = ev.tfinger;
1152 return handleEvent(cev);
1153 }
1154 case SDL_EVENT_MOUSE_MOTION:
1155 {
1156 const auto& cev = ev.motion;
1157 return handleEvent(cev);
1158 }
1159 case SDL_EVENT_MOUSE_BUTTON_DOWN:
1160 case SDL_EVENT_MOUSE_BUTTON_UP:
1161 {
1162 const auto& cev = ev.button;
1163 return handleEvent(cev);
1164 }
1165 case SDL_EVENT_MOUSE_WHEEL:
1166 {
1167 const auto& cev = ev.wheel;
1168 return handleEvent(cev);
1169 }
1170 case SDL_EVENT_CLIPBOARD_UPDATE:
1171 {
1172 const auto& cev = ev.clipboard;
1173 return getClipboardChannelContext().handleEvent(cev);
1174 }
1175 case SDL_EVENT_KEY_DOWN:
1176 case SDL_EVENT_KEY_UP:
1177 {
1178 const auto& cev = ev.key;
1179 return getInputChannelContext().handleEvent(cev);
1180 }
1181 case SDL_EVENT_RENDER_TARGETS_RESET:
1182 case SDL_EVENT_RENDER_DEVICE_RESET:
1183 case SDL_EVENT_WILL_ENTER_FOREGROUND:
1184 return redraw();
1185 default:
1186 return true;
1187 }
1188}
1189
1190bool SdlContext::drawToWindows(const std::vector<SDL_Rect>& rects)
1191{
1192 for (auto& window : _windows)
1193 {
1194 if (!drawToWindow(window.second, rects))
1195 return FALSE;
1196 }
1197
1198 return TRUE;
1199}
1200
1201BOOL SdlContext::desktopResize(rdpContext* context)
1202{
1203 rdpGdi* gdi = nullptr;
1204 rdpSettings* settings = nullptr;
1205 auto sdl = get_context(context);
1206
1207 WINPR_ASSERT(sdl);
1208 WINPR_ASSERT(context);
1209
1210 settings = context->settings;
1211 WINPR_ASSERT(settings);
1212
1213 std::unique_lock lock(sdl->_critical);
1214 gdi = context->gdi;
1215 if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
1216 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
1217 return FALSE;
1218 return sdl->createPrimary();
1219}
1220
1221/* This function is called to output a System BEEP */
1222BOOL SdlContext::playSound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound)
1223{
1224 /* TODO: Implement */
1225 WINPR_UNUSED(context);
1226 WINPR_UNUSED(play_sound);
1227 return TRUE;
1228}
1229
1230/* This function is called whenever a new frame starts.
1231 * It can be used to reset invalidated areas. */
1232BOOL SdlContext::beginPaint(rdpContext* context)
1233{
1234 auto gdi = context->gdi;
1235 WINPR_ASSERT(gdi);
1236 WINPR_ASSERT(gdi->primary);
1237
1238 HGDI_DC hdc = gdi->primary->hdc;
1239 WINPR_ASSERT(hdc);
1240 if (!hdc->hwnd)
1241 return TRUE;
1242
1243 HGDI_WND hwnd = hdc->hwnd;
1244 WINPR_ASSERT(hwnd->invalid);
1245 hwnd->invalid->null = TRUE;
1246 hwnd->ninvalid = 0;
1247
1248 return TRUE;
1249}
1250
1251bool SdlContext::redraw(bool suppress) const
1252{
1253 if (!_connected)
1254 return true;
1255
1256 auto gdi = context()->gdi;
1257 WINPR_ASSERT(gdi);
1258 return gdi_send_suppress_output(gdi, suppress);
1259}
1260
1261void SdlContext::setConnected(bool val)
1262{
1263 _connected = val;
1264}
1265
1266bool SdlContext::isConnected() const
1267{
1268 return _connected;
1269}
1270
1271rdpContext* SdlContext::context() const
1272{
1273 WINPR_ASSERT(_context);
1274 return _context;
1275}
1276
1277rdpClientContext* SdlContext::common() const
1278{
1279 return reinterpret_cast<rdpClientContext*>(context());
1280}
1281
1282bool SdlContext::setCursor(CursorType type)
1283{
1284 _cursorType = type;
1285 return restoreCursor();
1286}
1287
1288bool SdlContext::setCursor(rdpPointer* cursor)
1289{
1290 _cursor = cursor;
1291 return setCursor(CURSOR_IMAGE);
1292}
1293
1294rdpPointer* SdlContext::cursor() const
1295{
1296 return _cursor;
1297}
1298
1299bool SdlContext::restoreCursor()
1300{
1301 WLog_Print(getWLog(), WLOG_DEBUG, "restore cursor: %d", _cursorType);
1302 switch (_cursorType)
1303 {
1304 case CURSOR_NULL:
1305 if (!SDL_HideCursor())
1306 {
1307 WLog_Print(getWLog(), WLOG_ERROR, "SDL_HideCursor failed");
1308 return false;
1309 }
1310
1311 setHasCursor(false);
1312 return true;
1313
1314 case CURSOR_DEFAULT:
1315 {
1316 auto def = SDL_GetDefaultCursor();
1317 if (!SDL_SetCursor(def))
1318 {
1319 WLog_Print(getWLog(), WLOG_ERROR, "SDL_SetCursor(default=%p) failed",
1320 static_cast<void*>(def));
1321 return false;
1322 }
1323 if (!SDL_ShowCursor())
1324 {
1325 WLog_Print(getWLog(), WLOG_ERROR, "SDL_ShowCursor failed");
1326 return false;
1327 }
1328 setHasCursor(true);
1329 return true;
1330 }
1331 case CURSOR_IMAGE:
1332 setHasCursor(true);
1333 return sdl_Pointer_Set_Process(this);
1334 default:
1335 WLog_Print(getWLog(), WLOG_ERROR, "Unknown cursorType %s",
1336 sdl::utils::toString(_cursorType).c_str());
1337 return false;
1338 }
1339}
1340
1341void SdlContext::setMonitorIds(const std::vector<SDL_DisplayID>& ids)
1342{
1343 _monitorIds.clear();
1344 for (auto id : ids)
1345 {
1346 _monitorIds.push_back(id);
1347 }
1348}
1349
1350const std::vector<SDL_DisplayID>& SdlContext::monitorIds() const
1351{
1352 return _monitorIds;
1353}
1354
1355int64_t SdlContext::monitorId(uint32_t index) const
1356{
1357 if (index >= _monitorIds.size())
1358 {
1359 return -1;
1360 }
1361 return _monitorIds[index];
1362}
1363
1364void SdlContext::push(std::vector<SDL_Rect>&& rects)
1365{
1366 std::unique_lock lock(_queue_mux);
1367 _queue.emplace(std::move(rects));
1368}
1369
1370std::vector<SDL_Rect> SdlContext::pop()
1371{
1372 std::unique_lock lock(_queue_mux);
1373 if (_queue.empty())
1374 {
1375 return {};
1376 }
1377 auto val = std::move(_queue.front());
1378 _queue.pop();
1379 return val;
1380}
1381
1382bool SdlContext::setFullscreen(bool enter)
1383{
1384 for (const auto& window : _windows)
1385 {
1386 if (!sdl_push_user_event(SDL_EVENT_USER_WINDOW_FULLSCREEN, &window.second, enter))
1387 return false;
1388 }
1389 _fullscreen = enter;
1390 return true;
1391}
1392
1393bool SdlContext::setMinimized()
1394{
1395 return sdl_push_user_event(SDL_EVENT_USER_WINDOW_MINIMIZE);
1396}
1397
1398bool SdlContext::grabMouse() const
1399{
1400 return _grabMouse;
1401}
1402
1403bool SdlContext::toggleGrabMouse()
1404{
1405 return setGrabMouse(!grabMouse());
1406}
1407
1408bool SdlContext::setGrabMouse(bool enter)
1409{
1410 _grabMouse = enter;
1411 return true;
1412}
1413
1414bool SdlContext::grabKeyboard() const
1415{
1416 return _grabKeyboard;
1417}
1418
1419bool SdlContext::toggleGrabKeyboard()
1420{
1421 return setGrabKeyboard(!grabKeyboard());
1422}
1423
1424bool SdlContext::setGrabKeyboard(bool enter)
1425{
1426 _grabKeyboard = enter;
1427 return true;
1428}
1429
1430bool SdlContext::setResizeable(bool enable)
1431{
1432 const auto settings = context()->settings;
1433 const bool dyn = freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate);
1434 const bool smart = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing);
1435 bool use = (dyn && enable) || smart;
1436
1437 for (const auto& window : _windows)
1438 {
1439 if (!sdl_push_user_event(SDL_EVENT_USER_WINDOW_RESIZEABLE, &window.second, use))
1440 return false;
1441 }
1442 _resizeable = use;
1443
1444 return true;
1445}
1446
1447bool SdlContext::resizeable() const
1448{
1449 return _resizeable;
1450}
1451
1452bool SdlContext::toggleResizeable()
1453{
1454 return setResizeable(!resizeable());
1455}
1456
1457bool SdlContext::fullscreen() const
1458{
1459 return _fullscreen;
1460}
1461
1462bool SdlContext::toggleFullscreen()
1463{
1464 return setFullscreen(!fullscreen());
1465}
SdlContext(rdpContext *context)
object that handles clipboard context for the SDL3 client
Definition sdl_clip.hpp:76
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_server_name(const rdpSettings *settings)
A helper function to return the correct server name.
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_monitor_def_array_sorted(rdpSettings *settings, const rdpMonitor *monitors, size_t count)
Sort monitor array according to:
FREERDP_API BOOL freerdp_settings_set_uint32(rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 param)
Sets a UINT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.
FREERDP_API BOOL freerdp_settings_set_bool(rdpSettings *settings, FreeRDP_Settings_Keys_Bool id, BOOL param)
Sets a BOOL settings value.