FreeRDP
Loading...
Searching...
No Matches
sdl_context.cpp
1
20#include <algorithm>
21#include <cmath>
22
23#include "sdl_context.hpp"
24#include "sdl_config.hpp"
25#include "sdl_channels.hpp"
26#include "sdl_monitor.hpp"
27#include "sdl_pointer.hpp"
28#include "sdl_touch.hpp"
29#include "sdl_prefs.hpp"
30
31#include <sdl_common_utils.hpp>
32#include <scoped_guard.hpp>
33
34#include "dialogs/sdl_dialogs.hpp"
35#include <freerdp/client/aad_helper.h>
36
37static constexpr auto sdl_allow_screensaver = "sdl-allow-screensaver";
38static constexpr auto sdl_connection_dialog = "sdl-connection-dialog";
39
40SdlContext::SdlContext(rdpContext* context)
41 : _context(context), _log(WLog_Get(CLIENT_TAG("SDL"))), _cursor(nullptr, sdl_Pointer_FreeCopy),
42 _rdpThreadRunning(false), _primary(nullptr, SDL_DestroySurface), _disp(this), _input(this),
43 _clip(this), _rail(this), _dialog(_log)
44{
45 WINPR_ASSERT(context);
46 setMetadata();
47
48 auto instance = _context->instance;
49 WINPR_ASSERT(instance);
50
51 instance->PreConnect = preConnect;
52 instance->PostConnect = postConnect;
53 instance->PostDisconnect = postDisconnect;
54 instance->PostFinalDisconnect = postFinalDisconnect;
55 instance->AuthenticateEx = sdl_authenticate_ex;
56 instance->VerifyCertificateEx = sdl_verify_certificate_ex;
57 instance->VerifyChangedCertificateEx = sdl_verify_changed_certificate_ex;
58 instance->LogonErrorInfo = sdl_logon_error_info;
59 instance->PresentGatewayMessage = sdl_present_gateway_message;
60 instance->ChooseSmartcard = sdl_choose_smartcard;
61 instance->RetryDialog = sdl_retry_dialog;
62 instance->GetAccessToken = client_failsafe_get_access_token;
63
64 /* TODO: Client display set up */
65
66 _args.push_back({ sdl_allow_screensaver, COMMAND_LINE_VALUE_BOOL, nullptr, BoolValueFalse,
67 nullptr, -1, nullptr, "Allow local screensaver to activate" });
68 /* BoolValueTrue does not compile in C++, any non null default means on */
69 _args.push_back({ sdl_connection_dialog, COMMAND_LINE_VALUE_BOOL, nullptr, "on", nullptr, -1,
70 nullptr, "the connection dialog with progress and errors" });
71
72 /* Push a null element used as abort when iterating the array */
73 _args.push_back({ nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr });
74}
75
76void SdlContext::setHasCursor(bool val)
77{
78 this->_cursor_visible = val;
79}
80
81bool SdlContext::hasCursor() const
82{
83 return _cursor_visible;
84}
85
86void SdlContext::setMetadata()
87{
88 auto wmclass = freerdp_settings_get_string(_context->settings, FreeRDP_WmClass);
89 if (!wmclass || (strlen(wmclass) == 0))
90 wmclass = SDL_CLIENT_UUID;
91
92 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING, wmclass);
93 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING, SDL_CLIENT_NAME);
94 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_VERSION_STRING, SDL_CLIENT_VERSION);
95 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_CREATOR_STRING, SDL_CLIENT_VENDOR);
96 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_COPYRIGHT_STRING, SDL_CLIENT_COPYRIGHT);
97 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_URL_STRING, SDL_CLIENT_URL);
98 SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_TYPE_STRING, SDL_CLIENT_TYPE);
99}
100
101int SdlContext::start()
102{
103 _thread = std::thread(rdpThreadRun, this);
104 return 0;
105}
106
107int SdlContext::join()
108{
109 /* We do not want to use freerdp_abort_connect_context here.
110 * It would change the exit code and we do not want that. */
111 HANDLE event = freerdp_abort_event(context());
112 if (!SetEvent(event))
113 return -1;
114
115 _thread.join();
116 return 0;
117}
118
119void SdlContext::cleanup()
120{
121 std::unique_lock lock(_critical);
122 _windows.clear();
123 _dialog.destroy();
124 _primary.reset();
125}
126
127bool SdlContext::shallAbort(bool ignoreDialogs)
128{
129 std::unique_lock lock(_critical);
130 if (freerdp_shall_disconnect_context(context()))
131 {
132 if (ignoreDialogs)
133 return true;
134 if (_rdpThreadRunning)
135 return false;
136 return !getDialog().isRunning();
137 }
138 return false;
139}
140
141/* Called before a connection is established.
142 * Set all configuration options to support and load channels here. */
143BOOL SdlContext::preConnect(freerdp* instance)
144{
145 WINPR_ASSERT(instance);
146 WINPR_ASSERT(instance->context);
147
148 auto sdl = get_context(instance->context);
149
150 auto settings = instance->context->settings;
151 WINPR_ASSERT(settings);
152
153 if (!freerdp_settings_set_bool(settings, FreeRDP_CertificateCallbackPreferPEM, TRUE))
154 return FALSE;
155
156 /* Optional OS identifier sent to server */
157 if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX))
158 return FALSE;
159 if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_SDL))
160 return FALSE;
161 /* OrderSupport is initialized at this point.
162 * Only override it if you plan to implement custom order
163 * callbacks or deactivate certain features. */
164 /* Register the channel listeners.
165 * They are required to set up / tear down channels if they are loaded. */
166 if (PubSub_SubscribeChannelConnected(instance->context->pubSub,
167 sdl_OnChannelConnectedEventHandler) < 0)
168 return FALSE;
169 if (PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
170 sdl_OnChannelDisconnectedEventHandler) < 0)
171 return FALSE;
172 if (PubSub_SubscribeUserNotification(instance->context->pubSub,
173 sdl_OnUserNotificationEventHandler) < 0)
174 return FALSE;
175
176 if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
177 {
178 UINT32 maxWidth = 0;
179 UINT32 maxHeight = 0;
180
181 if (!sdl_detect_monitors(sdl, &maxWidth, &maxHeight))
182 return FALSE;
183
184 if ((maxWidth != 0) && (maxHeight != 0) &&
185 !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
186 {
187 WLog_Print(sdl->getWLog(), WLOG_INFO, "Update size to %ux%u", maxWidth, maxHeight);
188 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, maxWidth))
189 return FALSE;
190 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, maxHeight))
191 return FALSE;
192 }
193
199 const uint32_t sw = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth);
200 const uint32_t sh = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight);
201 const BOOL sm = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing);
202 if (sm && (sw > 0) && (sh > 0))
203 {
204 const BOOL mm = freerdp_settings_get_bool(settings, FreeRDP_UseMultimon);
205 if (mm)
206 WLog_Print(sdl->getWLog(), WLOG_WARN,
207 "/smart-sizing and /multimon are currently not supported, ignoring "
208 "/smart-sizing!");
209 else
210 {
211 sdl->_windowWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
212 sdl->_windowHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
213
214 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, sw))
215 return FALSE;
216 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, sh))
217 return FALSE;
218 }
219 }
220 }
221 else
222 {
223 /* Check +auth-only has a username and password. */
224 if (!freerdp_settings_get_string(settings, FreeRDP_Password))
225 {
226 WLog_Print(sdl->getWLog(), WLOG_INFO,
227 "auth-only, but no password set. Please provide one.");
228 return FALSE;
229 }
230
231 if (!freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, TRUE))
232 return FALSE;
233
234 WLog_Print(sdl->getWLog(), WLOG_INFO, "Authentication only. Don't connect SDL.");
235 }
236
237 if (!sdl->getInputChannelContext().initialize())
238 return FALSE;
239
240 sdl->_credentialsRead = false;
241
242 /* Settings are consistent at this point, the dialog can be created. */
243 auto prefs = SdlPref::instance();
244 auto dialog = prefs->get_bool("SDL_ConnectionDialog", true);
245 if (sdl->_connectionDialog && prefs->is_commandline_override_allowed())
246 dialog = *sdl->_connectionDialog;
247 if (dialog && !freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
248 {
249 sdl->getDialog().create(instance->context);
250 sdl->getDialog().setTitle("Connecting to '%s'", freerdp_settings_get_server_name(settings));
251 sdl->getDialog().showInfo("The connection is being established\n\nPlease wait...");
252 }
253
254 /* TODO: Any code your client requires */
255 return TRUE;
256}
257
258/* Called after a RDP connection was successfully established.
259 * Settings might have changed during negotiation of client / server feature
260 * support.
261 *
262 * Set up local framebuffers and paing callbacks.
263 * If required, register pointer callbacks to change the local mouse cursor
264 * when hovering over the RDP window
265 */
266BOOL SdlContext::postConnect(freerdp* instance)
267{
268 WINPR_ASSERT(instance);
269
270 auto context = instance->context;
271 WINPR_ASSERT(context);
272
273 auto sdl = get_context(context);
274
275 // Retry was successful, discard dialog
276 sdl->getDialog().show(false);
277
278 if (freerdp_settings_get_bool(context->settings, FreeRDP_AuthenticationOnly))
279 {
280 /* Check +auth-only has a username and password. */
281 if (!freerdp_settings_get_string(context->settings, FreeRDP_Password))
282 {
283 WLog_Print(sdl->getWLog(), WLOG_INFO,
284 "auth-only, but no password set. Please provide one.");
285 return FALSE;
286 }
287
288 WLog_Print(sdl->getWLog(), WLOG_INFO, "Authentication only. Don't connect to X.");
289 return TRUE;
290 }
291
292 if (!sdl->waitForWindowsCreated())
293 return FALSE;
294
295 sdl->_sdlPixelFormat = SDL_PIXELFORMAT_BGRA32;
296 if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
297 return FALSE;
298
299 if (!sdl->createPrimary())
300 return FALSE;
301
302 if (!sdl_register_pointer(instance->context->graphics))
303 return FALSE;
304
305 WINPR_ASSERT(context->update);
306
307 context->update->BeginPaint = beginPaint;
308 context->update->EndPaint = endPaint;
309 context->update->PlaySound = playSound;
310 context->update->DesktopResize = desktopResize;
311 context->update->SetKeyboardIndicators = sdlInput::keyboard_set_indicators;
312 context->update->SetKeyboardImeStatus = sdlInput::keyboard_set_ime_status;
313
314 if (!sdl->setResizeable(false))
315 return FALSE;
316 if (!sdl->setFullscreen(freerdp_settings_get_bool(context->settings, FreeRDP_Fullscreen) ||
317 freerdp_settings_get_bool(context->settings, FreeRDP_UseMultimon),
318 true))
319 return FALSE;
320 sdl->setConnected(true);
321 return TRUE;
322}
323
324/* This function is called whether a session ends by failure or success.
325 * Clean up everything allocated by pre_connect and post_connect.
326 */
327void SdlContext::postDisconnect(freerdp* instance)
328{
329 if (!instance)
330 return;
331
332 if (!instance->context)
333 return;
334
335 auto sdl = get_context(instance->context);
336 sdl->setConnected(false);
337
338 gdi_free(instance);
339}
340
341void SdlContext::postFinalDisconnect(freerdp* instance)
342{
343 if (!instance)
344 return;
345
346 if (!instance->context)
347 return;
348
349 PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
350 sdl_OnChannelConnectedEventHandler);
351 PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub,
352 sdl_OnChannelDisconnectedEventHandler);
353 PubSub_UnsubscribeUserNotification(instance->context->pubSub,
354 sdl_OnUserNotificationEventHandler);
355}
356
357/* Create a SDL surface from the GDI buffer */
358bool SdlContext::createPrimary()
359{
360 auto gdi = context()->gdi;
361 WINPR_ASSERT(gdi);
362
363 _primary = SDLSurfacePtr(
364 SDL_CreateSurfaceFrom(static_cast<int>(gdi->width), static_cast<int>(gdi->height),
365 pixelFormat(), gdi->primary_buffer, static_cast<int>(gdi->stride)),
366 SDL_DestroySurface);
367 if (!_primary)
368 return false;
369
370 SDL_SetSurfaceBlendMode(_primary.get(), SDL_BLENDMODE_NONE);
371 SDL_Rect surfaceRect = { 0, 0, gdi->width, gdi->height };
372 SDL_FillSurfaceRect(_primary.get(), &surfaceRect,
373 SDL_MapSurfaceRGBA(_primary.get(), 0, 0, 0, 0xff));
374
375 return true;
376}
377
378bool SdlContext::createWindows()
379{
380 auto settings = context()->settings;
381 const auto& title = windowTitle();
382
383 ScopeGuard guard1([&]() { _windowsCreatedEvent.set(); });
384
385 UINT32 windowCount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
386
387 Sint32 originX = 0;
388 Sint32 originY = 0;
389 for (UINT32 x = 0; x < windowCount; x++)
390 {
391 auto id = monitorId(x);
392 if (id < 0)
393 return false;
394
395 auto monitor = static_cast<rdpMonitor*>(
396 freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x));
397
398 originX = std::min<Sint32>(monitor->x, originX);
399 originY = std::min<Sint32>(monitor->y, originY);
400 }
401
402 for (UINT32 x = 0; x < windowCount; x++)
403 {
404 auto id = monitorId(x);
405 if (id < 0)
406 return false;
407
408 auto monitor = static_cast<rdpMonitor*>(
409 freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x));
410
411 Uint32 w = WINPR_ASSERTING_INT_CAST(Uint32, monitor->width);
412 Uint32 h = WINPR_ASSERTING_INT_CAST(Uint32, monitor->height);
413 if (!(freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) ||
414 freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)))
415 {
416 if (_windowWidth > 0)
417 w = _windowWidth;
418 else
419 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
420
421 if (_windowHeight > 0)
422 h = _windowHeight;
423 else
424 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
425 }
426
427 Uint32 flags = SDL_WINDOW_HIGH_PIXEL_DENSITY;
428
429 if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) &&
430 !freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
431 {
432 flags |= SDL_WINDOW_FULLSCREEN;
433 }
434
435 if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
436 {
437 flags |= SDL_WINDOW_BORDERLESS;
438 }
439
440 if (!freerdp_settings_get_bool(settings, FreeRDP_Decorations))
441 flags |= SDL_WINDOW_BORDERLESS;
442
443 auto did = WINPR_ASSERTING_INT_CAST(SDL_DisplayID, id);
444 auto window = SdlWindow::create(did, title, flags, w, h);
445
446 if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
447 {
448 window.setOffsetX(originX - monitor->x);
449 window.setOffsetY(originY - monitor->y);
450 }
451
452 _windows.insert({ window.id(), std::move(window) });
453 }
454
455 return true;
456}
457
458bool SdlContext::updateWindowList()
459{
460 std::vector<rdpMonitor> list;
461 list.reserve(_windows.size());
462 for (const auto& win : _windows)
463 list.push_back(win.second.monitor(_windows.size() == 1));
464
465 // /monitors: subset may exclude the SDL primary. The library requires
466 // the array to mark one monitor as primary, so promote the first when
467 // none of the kept windows cover the original primary.
468 if (!list.empty() &&
469 std::none_of(list.cbegin(), list.cend(), [](const rdpMonitor& m) { return m.is_primary; }))
470 list.at(0).is_primary = true;
471
472 return freerdp_settings_set_monitor_def_array_sorted(context()->settings, list.data(),
473 list.size());
474}
475
476bool SdlContext::updateWindow(SDL_WindowID id)
477{
478 /* In fullscreen the monitor kept the size measured with the probe window at startup and was
479 * never updated, so a session the server started at another size stayed that way. Only the
480 * window knows its real size, so ask it in fullscreen as well. */
481 if (freerdp_settings_get_bool(_context->settings, FreeRDP_UseMultimon))
482 return true;
483
484 auto& w = _windows.at(id);
485 auto m = w.monitor(true);
486 auto r = w.rect();
487 m.width = r.w;
488 m.height = r.h;
489 m.attributes.physicalWidth = static_cast<UINT32>(r.w);
490 m.attributes.physicalHeight = static_cast<UINT32>(r.h);
491 w.setMonitor(m);
492 return true;
493}
494
495std::string SdlContext::windowTitle() const
496{
497 const char* prefix = "FreeRDP:";
498
499 const auto windowTitle = freerdp_settings_get_string(context()->settings, FreeRDP_WindowTitle);
500 if (windowTitle)
501 return windowTitle;
502
503 const auto name = freerdp_settings_get_server_name(context()->settings);
504 const auto port = freerdp_settings_get_uint32(context()->settings, FreeRDP_ServerPort);
505 const auto addPort = (port != 3389);
506
507 std::stringstream ss;
508 ss << prefix << " " << name;
509
510 if (addPort)
511 ss << ":" << port;
512
513 return ss.str();
514}
515
516bool SdlContext::waitForWindowsCreated()
517{
518 {
519 std::unique_lock<CriticalSection> lock(_critical);
520 _windowsCreatedEvent.clear();
521 if (!sdl_push_user_event(SDL_EVENT_USER_CREATE_WINDOWS, this))
522 return false;
523 }
524
525 HANDLE handles[] = { _windowsCreatedEvent.handle(), freerdp_abort_event(context()) };
526
527 const DWORD rc = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
528 switch (rc)
529 {
530 case WAIT_OBJECT_0:
531 return true;
532 default:
533 return false;
534 }
535}
536
537/* This function is called when the library completed composing a new
538 * frame. Read out the changed areas and blit them to your output device.
539 * The image buffer will have the format specified by gdi_init
540 */
541BOOL SdlContext::endPaint(rdpContext* context)
542{
543 auto sdl = get_context(context);
544 WINPR_ASSERT(sdl);
545
546 auto gdi = context->gdi;
547 WINPR_ASSERT(gdi);
548 WINPR_ASSERT(gdi->primary);
549
550 HGDI_DC hdc = gdi->primary->hdc;
551 WINPR_ASSERT(hdc);
552 if (!hdc->hwnd)
553 return TRUE;
554
555 HGDI_WND hwnd = hdc->hwnd;
556 WINPR_ASSERT(hwnd->invalid || (hwnd->ninvalid == 0));
557
558 if (hwnd->invalid->null)
559 return TRUE;
560
561 WINPR_ASSERT(hwnd->invalid);
562 if (gdi->suppressOutput || hwnd->invalid->null)
563 return TRUE;
564
565 const INT32 ninvalid = hwnd->ninvalid;
566 const GDI_RGN* cinvalid = hwnd->cinvalid;
567
568 if (ninvalid < 1)
569 return TRUE;
570
571 std::vector<SDL_Rect> rects;
572 for (INT32 x = 0; x < ninvalid; x++)
573 {
574 auto& rgn = cinvalid[x];
575 rects.push_back({ rgn.x, rgn.y, rgn.w, rgn.h });
576 }
577
578 sdl->push(std::move(rects));
579 return sdl_push_user_event(SDL_EVENT_USER_UPDATE);
580}
581
582void SdlContext::sdl_client_cleanup(int exit_code, const std::string& error_msg)
583{
584 rdpSettings* settings = context()->settings;
585 WINPR_ASSERT(settings);
586
587 _rdpThreadRunning = false;
588 bool showError = false;
589 if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
590 WLog_Print(getWLog(), WLOG_INFO, "Authentication only, exit status %s [%" PRId32 "]",
591 sdl::error::exitCodeToTag(exit_code), exit_code);
592 else
593 {
594 switch (exit_code)
595 {
596 case sdl::error::SUCCESS:
597 case sdl::error::DISCONNECT:
598 case sdl::error::LOGOFF:
599 case sdl::error::DISCONNECT_BY_USER:
600 case sdl::error::CONNECT_CANCELLED:
601 break;
602 default:
603 {
604 showError = true;
605 getDialog().showError(error_msg);
606 }
607 break;
608 }
609 }
610
611 if (!showError)
612 getDialog().show(false);
613
614 _exitCode = exit_code;
615 std::ignore = sdl_push_user_event(SDL_EVENT_USER_QUIT);
616 SDL_CleanupTLS();
617}
618
619int SdlContext::sdl_client_thread_connect(std::string& error_msg)
620{
621 auto instance = context()->instance;
622 WINPR_ASSERT(instance);
623
624 _rdpThreadRunning = true;
625 BOOL rc = freerdp_connect(instance);
626
627 rdpSettings* settings = context()->settings;
628 WINPR_ASSERT(settings);
629
630 int exit_code = sdl::error::SUCCESS;
631 if (!rc)
632 {
633 UINT32 error = freerdp_get_last_error(context());
634 exit_code = sdl::error::errorToExitCode(error);
635 }
636
637 if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
638 {
639 DWORD code = freerdp_get_last_error(context());
640 freerdp_abort_connect_context(context());
641 WLog_Print(getWLog(), WLOG_ERROR, "Authentication only, %s [0x%08" PRIx32 "] %s",
642 freerdp_get_last_error_name(code), code, freerdp_get_last_error_string(code));
643 return exit_code;
644 }
645
646 if (!rc)
647 {
648 DWORD code = freerdp_error_info(instance);
649 if (exit_code == sdl::error::SUCCESS)
650 {
651 char* msg = nullptr;
652 size_t len = 0;
653 exit_code = error_info_to_error(&code, &msg, &len);
654 if (msg)
655 error_msg = msg;
656 free(msg);
657 }
658
659 auto last = freerdp_get_last_error(context());
660 if (error_msg.empty())
661 {
662 char* msg = nullptr;
663 size_t len = 0;
664 winpr_asprintf(&msg, &len, "%s [0x%08" PRIx32 "]\n%s",
665 freerdp_get_last_error_name(last), last,
666 freerdp_get_last_error_string(last));
667 if (msg)
668 error_msg = msg;
669 free(msg);
670 }
671
672 if (exit_code == sdl::error::SUCCESS)
673 {
674 if (last == FREERDP_ERROR_AUTHENTICATION_FAILED)
675 exit_code = sdl::error::AUTH_FAILURE;
676 else if (code == ERRINFO_SUCCESS)
677 exit_code = sdl::error::CONN_FAILED;
678 }
679
680 getDialog().show(false);
681 }
682
683 return exit_code;
684}
685
686int SdlContext::sdl_client_thread_run(std::string& error_msg)
687{
688 auto instance = context()->instance;
689 WINPR_ASSERT(instance);
690
691 int exit_code = sdl::error::SUCCESS;
692 while (!freerdp_shall_disconnect_context(context()))
693 {
694 HANDLE handles[MAXIMUM_WAIT_OBJECTS] = {};
695 /*
696 * win8 and server 2k12 seem to have some timing issue/race condition
697 * when a initial sync request is send to sync the keyboard indicators
698 * sending the sync event twice fixed this problem
699 */
700 if (freerdp_focus_required(instance))
701 {
702 auto ctx = get_context(context());
703 WINPR_ASSERT(ctx);
704
705 auto& input = ctx->getInputChannelContext();
706 if (!input.keyboard_focus_in())
707 break;
708 if (!input.keyboard_focus_in())
709 break;
710 }
711
712 const DWORD nCount = freerdp_get_event_handles(context(), handles, ARRAYSIZE(handles));
713
714 if (nCount == 0)
715 {
716 WLog_Print(getWLog(), WLOG_ERROR, "freerdp_get_event_handles failed");
717 break;
718 }
719
720 const DWORD status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
721
722 if (status == WAIT_FAILED)
723 {
724 WLog_Print(getWLog(), WLOG_ERROR, "WaitForMultipleObjects WAIT_FAILED");
725 break;
726 }
727
728 if (!freerdp_check_event_handles(context()))
729 {
730 if (client_auto_reconnect(instance))
731 {
732 // Retry was successful, discard dialog
733 getDialog().show(false);
734 continue;
735 }
736 else
737 {
738 /*
739 * Indicate an unsuccessful connection attempt if reconnect
740 * did not succeed and no other error was specified.
741 */
742 if (freerdp_error_info(instance) == 0)
743 exit_code = sdl::error::errorToExitCode(freerdp_get_last_error(context()));
744 }
745
746 if (freerdp_get_last_error(context()) == FREERDP_ERROR_SUCCESS)
747 WLog_Print(getWLog(), WLOG_ERROR, "WaitForMultipleObjects failed with %" PRIu32 "",
748 status);
749 if (freerdp_get_last_error(context()) == FREERDP_ERROR_SUCCESS)
750 WLog_Print(getWLog(), WLOG_ERROR, "Failed to check FreeRDP event handles");
751 break;
752 }
753 }
754
755 if (exit_code == sdl::error::SUCCESS)
756 {
757 DWORD code = 0;
758 {
759 char* emsg = nullptr;
760 size_t elen = 0;
761 exit_code = error_info_to_error(&code, &emsg, &elen);
762 if (emsg)
763 error_msg = emsg;
764 free(emsg);
765 }
766
767 if ((code == ERRINFO_LOGOFF_BY_USER) &&
768 (freerdp_get_disconnect_ultimatum(context()) == Disconnect_Ultimatum_user_requested))
769 {
770 const char* msg = "Error info says user did not initiate but disconnect ultimatum says "
771 "they did; treat this as a user logoff";
772
773 char* emsg = nullptr;
774 size_t elen = 0;
775 winpr_asprintf(&emsg, &elen, "%s", msg);
776 if (emsg)
777 error_msg = emsg;
778 free(emsg);
779
780 /* This situation might be limited to Windows XP. */
781 WLog_Print(getWLog(), WLOG_INFO, "%s", msg);
782 exit_code = sdl::error::LOGOFF;
783 }
784 }
785
786 if ((exit_code != sdl::error::SUCCESS) && error_msg.empty())
787 {
788 const auto last = freerdp_get_last_error(context());
789 char* msg = nullptr;
790 size_t len = 0;
791 winpr_asprintf(&msg, &len, "%s [0x%08" PRIx32 "]\n%s", freerdp_get_last_error_name(last),
792 last, freerdp_get_last_error_string(last));
793 if (msg)
794 error_msg = msg;
795 free(msg);
796 }
797
798 freerdp_disconnect(instance);
799
800 return exit_code;
801}
802
803/* RDP main loop.
804 * Connects RDP, loops while running and handles event and dispatch, cleans up
805 * after the connection ends. */
806DWORD SdlContext::rdpThreadRun(SdlContext* sdl)
807{
808 WINPR_ASSERT(sdl);
809
810 std::string error_msg;
811 int exit_code = sdl->sdl_client_thread_connect(error_msg);
812 if (exit_code == sdl::error::SUCCESS)
813 exit_code = sdl->sdl_client_thread_run(error_msg);
814 sdl->sdl_client_cleanup(exit_code, error_msg);
815
816 return static_cast<DWORD>(exit_code);
817}
818
819int SdlContext::error_info_to_error(DWORD* pcode, char** msg, size_t* len) const
820{
821 const DWORD code = freerdp_error_info(context()->instance);
822 const char* name = freerdp_get_error_info_name(code);
823 const char* str = freerdp_get_error_info_string(code);
824 const int exit_code = sdl::error::errorToExitCode(code);
825
826 winpr_asprintf(msg, len, "Terminate with %s due to ERROR_INFO %s [0x%08" PRIx32 "]: %s",
827 sdl::error::errorToExitCodeTag(code), name, code, str);
828 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%s", *msg);
829 if (pcode)
830 *pcode = code;
831 return exit_code;
832}
833
834void SdlContext::applyMonitorOffset(SDL_WindowID window, float& x, float& y) const
835{
836 if (!freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon))
837 return;
838
839 auto w = getWindowForId(window);
840 x -= static_cast<float>(w->offsetX());
841 y -= static_cast<float>(w->offsetY());
842}
843
844static bool alignX(const SDL_Rect& a, const SDL_Rect& b)
845{
846 if (a.x + a.w == b.x)
847 return true;
848 if (b.x + b.w == a.x)
849 return true;
850 return false;
851}
852
853static bool alignY(const SDL_Rect& a, const SDL_Rect& b)
854{
855 if (a.y + a.h == b.y)
856 return true;
857 if (b.y + b.h == a.y)
858 return true;
859 return false;
860}
861
862std::vector<SDL_DisplayID>
863SdlContext::updateDisplayOffsetsForNeighbours(SDL_DisplayID id,
864 const std::vector<SDL_DisplayID>& ignore)
865{
866 auto first = _offsets.at(id);
867 std::vector<SDL_DisplayID> neighbours;
868
869 for (auto& entry : _offsets)
870 {
871 if (entry.first == id)
872 continue;
873 if (std::find(ignore.begin(), ignore.end(), entry.first) != ignore.end())
874 continue;
875
876 bool neighbor = false;
877 const bool ax = alignX(entry.second.first, first.first);
878 const bool ay = alignY(entry.second.first, first.first);
879
880 /* Offset along the axis that does not touch, converted from logical to pixel
881 * units of the reference display. Without this, a monitor that is adjacent on
882 * one axis but shifted on the other (e.g. a wider monitor above another, or a
883 * rotated monitor that is not top aligned) keeps offset 0 on that axis, the
884 * layout sent to the server no longer matches the local one and pointer input
885 * lands offset by that difference. */
886 auto crossOffset = [](int ePos, int fPos, int fLogical, int fPixel) -> int
887 {
888 if (fLogical <= 0)
889 return ePos - fPos;
890 return static_cast<int>(
891 std::lround(static_cast<double>(ePos - fPos) * fPixel / fLogical));
892 };
893
894 if (ax)
895 {
896 if (entry.second.first.x < first.first.x)
897 entry.second.second.x = first.second.x - entry.second.second.w;
898 else
899 entry.second.second.x = first.second.x + first.second.w;
900 if (!ay)
901 entry.second.second.y =
902 first.second.y +
903 crossOffset(entry.second.first.y, first.first.y, first.first.h, first.second.h);
904 neighbor = true;
905 }
906 if (ay)
907 {
908 if (entry.second.first.y < first.first.y)
909 entry.second.second.y = first.second.y - entry.second.second.h;
910 else
911 entry.second.second.y = first.second.y + first.second.h;
912 if (!ax)
913 entry.second.second.x =
914 first.second.x +
915 crossOffset(entry.second.first.x, first.first.x, first.first.w, first.second.w);
916 neighbor = true;
917 }
918
919 if (neighbor)
920 neighbours.push_back(entry.first);
921 }
922 return neighbours;
923}
924
925void SdlContext::updateMonitorDataFromOffsets()
926{
927 for (auto& entry : _displays)
928 {
929 auto offsets = _offsets.at(entry.first);
930 entry.second.x = offsets.second.x;
931 entry.second.y = offsets.second.y;
932 }
933
934 for (auto& entry : _windows)
935 {
936 const auto& monitor = _displays.at(entry.first);
937 entry.second.setMonitor(monitor);
938 }
939}
940
941bool SdlContext::drawToWindow(SdlWindow& window, const std::vector<SDL_Rect>& rects)
942{
943 if (!isConnected())
944 return true;
945
946 auto gdi = context()->gdi;
947 WINPR_ASSERT(gdi);
948
949 auto size = window.rect();
950
951 std::unique_lock lock(_critical);
952 auto surface = _primary.get();
953
954 if (useLocalScale())
955 {
956 window.setOffsetX(0);
957 window.setOffsetY(0);
958 if (gdi->width < size.w)
959 {
960 window.setOffsetX((size.w - gdi->width) / 2);
961 }
962 if (gdi->height < size.h)
963 {
964 window.setOffsetY((size.h - gdi->height) / 2);
965 }
966
967 _localScale = { static_cast<float>(size.w) / static_cast<float>(gdi->width),
968 static_cast<float>(size.h) / static_cast<float>(gdi->height) };
969 if (!window.drawScaledRects(surface, _localScale, rects))
970 return false;
971 }
972 else
973 {
974 SDL_Point offset{ 0, 0 };
975 if (freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon))
976 offset = { window.offsetX(), window.offsetY() };
977 if (!window.drawRects(surface, offset, rects))
978 return false;
979 }
980
981 window.updateSurface();
982 return true;
983}
984
985bool SdlContext::minimizeAllWindows()
986{
987 for (auto& w : _windows)
988 w.second.minimize();
989 return true;
990}
991
992int SdlContext::exitCode() const
993{
994 return _exitCode;
995}
996
997SDL_PixelFormat SdlContext::pixelFormat() const
998{
999 return _sdlPixelFormat;
1000}
1001
1002bool SdlContext::addDisplayWindow(SDL_DisplayID id)
1003{
1004 const auto flags =
1005 SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS;
1006 auto title = sdl::utils::windowTitle(context()->settings);
1007 auto w = SdlWindow::create(id, title, flags);
1008 _windows.emplace(w.id(), std::move(w));
1009 return true;
1010}
1011
1012bool SdlContext::removeDisplayWindow(SDL_DisplayID id)
1013{
1014 for (auto& w : _windows)
1015 {
1016 if (w.second.displayIndex() == id)
1017 _windows.erase(w.first);
1018 }
1019 return true;
1020}
1021
1022bool SdlContext::detectDisplays()
1023{
1024 int count = 0;
1025 auto display = SDL_GetDisplays(&count);
1026 if (!display)
1027 return false;
1028 for (int x = 0; x < count; x++)
1029 {
1030 const auto id = display[x];
1031 addOrUpdateDisplay(id);
1032 }
1033 SDL_free(display);
1034 return true;
1035}
1036
1037rdpMonitor SdlContext::getDisplay(SDL_DisplayID id) const
1038{
1039 return _displays.at(id);
1040}
1041
1042std::vector<SDL_DisplayID> SdlContext::getDisplayIds() const
1043{
1044 std::vector<SDL_DisplayID> keys;
1045 keys.reserve(_displays.size());
1046 for (const auto& entry : _displays)
1047 {
1048 keys.push_back(entry.first);
1049 }
1050 return keys;
1051}
1052
1053const SdlWindow* SdlContext::getWindowForId(SDL_WindowID id) const
1054{
1055 auto it = _windows.find(id);
1056 if (it == _windows.end())
1057 return nullptr;
1058 return &it->second;
1059}
1060
1061SdlWindow* SdlContext::getWindowForId(SDL_WindowID id)
1062{
1063 auto it = _windows.find(id);
1064 if (it == _windows.end())
1065 return nullptr;
1066 return &it->second;
1067}
1068
1069SdlWindow* SdlContext::getFirstWindow()
1070{
1071 if (_windows.empty())
1072 return nullptr;
1073 return &_windows.begin()->second;
1074}
1075
1076sdlDispContext& SdlContext::getDisplayChannelContext()
1077{
1078 return _disp;
1079}
1080
1081sdlInput& SdlContext::getInputChannelContext()
1082{
1083 return _input;
1084}
1085
1086sdlClip& SdlContext::getClipboardChannelContext()
1087{
1088 return _clip;
1089}
1090
1091SdlRail& SdlContext::getRailChannelContext()
1092{
1093 return _rail;
1094}
1095
1096SdlConnectionDialogWrapper& SdlContext::getDialog()
1097{
1098 return _dialog;
1099}
1100
1101wLog* SdlContext::getWLog()
1102{
1103 return _log;
1104}
1105
1106bool SdlContext::moveMouseTo(const SDL_FPoint& pos)
1107{
1108 auto window = SDL_GetMouseFocus();
1109 if (!window)
1110 return true;
1111
1112 const auto id = SDL_GetWindowID(window);
1113 const auto spos = pixelToScreen(id, pos);
1114 SDL_WarpMouseInWindow(window, spos.x, spos.y);
1115 return true;
1116}
1117
1118bool SdlContext::handleEvent(const SDL_MouseMotionEvent& ev)
1119{
1120 SDL_Event copy{};
1121 copy.motion = ev;
1122 /* WM owns the drag (#12447); backstop: button released but button-up swallowed by grab. */
1123 if (_rail.enabled() && _rail.suppressServerMotion(ev.windowID))
1124 {
1125 if (!(SDL_GetGlobalMouseState(nullptr, nullptr) & SDL_BUTTON_LMASK))
1126 _rail.completeLocalMoveIfPending();
1127 return true;
1128 }
1129 if (_rail.enabled() && _rail.translateToServer(ev.windowID, copy.motion.x, copy.motion.y))
1130 {
1131 _rail.noteServerPointer(copy.motion.x, copy.motion.y);
1132 return SdlTouch::handleEvent(this, copy.motion);
1133 }
1134
1135 if (!getWindowForId(ev.windowID))
1136 return true; /* Event for an untracked window (e.g. closed dialog) */
1137 if (!eventToPixelCoordinates(ev.windowID, copy))
1138 return true;
1139 removeLocalScaling(copy.motion.x, copy.motion.y);
1140 removeLocalScaling(copy.motion.xrel, copy.motion.yrel);
1141 applyMonitorOffset(copy.motion.windowID, copy.motion.x, copy.motion.y);
1142
1143 return SdlTouch::handleEvent(this, copy.motion);
1144}
1145
1146bool SdlContext::handleEvent(const SDL_MouseWheelEvent& ev)
1147{
1148 SDL_Event copy{};
1149 copy.wheel = ev;
1150 if (_rail.enabled() &&
1151 _rail.translateToServer(ev.windowID, copy.wheel.mouse_x, copy.wheel.mouse_y))
1152 return SdlTouch::handleEvent(this, copy.wheel);
1153
1154 if (!getWindowForId(ev.windowID))
1155 return true;
1156 if (!eventToPixelCoordinates(ev.windowID, copy))
1157 return true;
1158 removeLocalScaling(copy.wheel.mouse_x, copy.wheel.mouse_y);
1159 return SdlTouch::handleEvent(this, copy.wheel);
1160}
1161
1162bool SdlContext::handleEvent(const SDL_WindowEvent& ev)
1163{
1164 if (!getDisplayChannelContext().handleEvent(ev))
1165 return false;
1166
1167 auto window = getWindowForId(ev.windowID);
1168 if (!window)
1169 {
1170 /* RAIL windows aren't in _windows; handle their events here. */
1171 if (_rail.enabled() && _rail.ownsWindow(ev.windowID))
1172 {
1173 switch (ev.type)
1174 {
1175 case SDL_EVENT_WINDOW_MOUSE_ENTER:
1176 /* Re-enter fires on move-grab end. */
1177 if (!(SDL_GetGlobalMouseState(nullptr, nullptr) & SDL_BUTTON_LMASK))
1178 _rail.completeLocalMoveIfPending(); /* X11 */
1179 _rail.completeWaylandResize(); /* Wayland: compositor grab ended */
1180 /* Restore the cursor or the pointer stays hidden over RemoteApp windows. */
1181 return restoreCursor();
1182 case SDL_EVENT_WINDOW_MOUSE_LEAVE:
1183 /* Compositor took the pointer for the pending Wayland resize grab. */
1184 _rail.noteResizeGrab(ev.windowID);
1185 return true;
1186 case SDL_EVENT_WINDOW_EXPOSED:
1187 /* Force repaint for exposed windows; skip during local drag to avoid redundant
1188 * frames. */
1189 if (!_rail.suppressServerInput(ev.windowID))
1190 _rail.invalidateWindow(ev.windowID);
1191 return true;
1192 case SDL_EVENT_WINDOW_MAXIMIZED:
1193 /* Maximize must complete move first. */
1194 _rail.completeLocalMoveIfPending();
1195 _rail.handleMaximized(ev.windowID);
1196 return true;
1197 case SDL_EVENT_WINDOW_MINIMIZED:
1198 _rail.handleMinimized(ev.windowID);
1199 return true;
1200 case SDL_EVENT_WINDOW_RESTORED:
1201 _rail.handleRestored(ev.windowID);
1202 return true;
1203 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
1204 /* Send SC_CLOSE, don't abort the session. */
1205 _rail.handleClose(ev.windowID);
1206 return true;
1207 case SDL_EVENT_WINDOW_FOCUS_GAINED:
1208 _rail.handleFocus(ev.windowID, true);
1209 return true;
1210 case SDL_EVENT_WINDOW_FOCUS_LOST:
1211 _rail.handleFocus(ev.windowID, false);
1212 return true;
1213 case SDL_EVENT_WINDOW_MOVED:
1214 if (!_rail.suppressServerInput(ev.windowID))
1215 _rail.syncGeometry(ev.windowID);
1216 return true;
1217 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
1218 /* No server frame during the drag: repaint synchronously each configure step;
1219 * also report the new size to catch a compositor snap/tile. */
1220 _rail.noteDragResize(ev.windowID, ev.data1, ev.data2);
1221 _rail.syncGeometry(ev.windowID);
1222 _rail.handleWaylandResize(ev.windowID);
1223 std::ignore = drawToWindows({});
1224 return true;
1225 default:
1226 break;
1227 }
1228 }
1229 return true;
1230 }
1231
1232 {
1233 const auto& r = window->rect();
1234 const auto& b = window->bounds();
1235 const auto& scale = window->scale();
1236 const auto& orientation = window->orientation();
1237 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
1238 "%s: [%u] %dx%d-%dx%d {%dx%d-%dx%d}{scale=%f,orientation=%s}",
1239 sdl::utils::toString(ev.type).c_str(), ev.windowID, r.x, r.y, r.w, r.h, b.x,
1240 b.y, b.w, b.h, static_cast<double>(scale),
1241 sdl::utils::toString(orientation).c_str());
1242 }
1243
1244 switch (ev.type)
1245 {
1246 case SDL_EVENT_WINDOW_MOUSE_ENTER:
1247 return restoreCursor();
1248 case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
1249 if (!resizeToScale(window))
1250 return false;
1251 if (isConnected())
1252 {
1253 if (!window->fill())
1254 return false;
1255 if (!drawToWindow(*window))
1256 return false;
1257 if (!restoreCursor())
1258 return false;
1259 }
1260 break;
1261 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
1262 if (!resizeToScale(window))
1263 return false;
1264 if (!window->fill())
1265 return false;
1266 if (!drawToWindow(*window))
1267 return false;
1268 if (!restoreCursor())
1269 return false;
1270 break;
1271 case SDL_EVENT_WINDOW_MOVED:
1272 {
1273 auto r = window->rect();
1274 auto id = window->id();
1275 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%u: %dx%d-%dx%d", id, r.x, r.y, r.w, r.h);
1276 }
1277 break;
1278 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
1279 {
1280 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Window closed, terminating RDP session...");
1281 freerdp_abort_connect_context(context());
1282 }
1283 break;
1284 default:
1285 break;
1286 }
1287 return true;
1288}
1289
1290bool SdlContext::handleEvent(const SDL_DisplayEvent& ev)
1291{
1292 if (!getDisplayChannelContext().handleEvent(ev))
1293 return false;
1294
1295 switch (ev.type)
1296 {
1297 case SDL_EVENT_DISPLAY_REMOVED: // Can't show details for this one...
1298 break;
1299 default:
1300 {
1301 SDL_Rect r = {};
1302 if (!SDL_GetDisplayBounds(ev.displayID, &r))
1303 return false;
1304 const auto name = SDL_GetDisplayName(ev.displayID);
1305 if (!name)
1306 return false;
1307 const auto orientation = SDL_GetCurrentDisplayOrientation(ev.displayID);
1308 const auto scale = SDL_GetDisplayContentScale(ev.displayID);
1309 const auto mode = SDL_GetCurrentDisplayMode(ev.displayID);
1310 if (!mode)
1311 return false;
1312
1313 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
1314 "%s: [%u, %s] %dx%d-%dx%d {orientation=%s, scale=%f}%s",
1315 sdl::utils::toString(ev.type).c_str(), ev.displayID, name, r.x, r.y, r.w,
1316 r.h, sdl::utils::toString(orientation).c_str(), static_cast<double>(scale),
1317 sdl::utils::toString(mode).c_str());
1318 }
1319 break;
1320 }
1321 return true;
1322}
1323
1324bool SdlContext::handleEvent(const SDL_MouseButtonEvent& ev)
1325{
1326 SDL_Event copy = {};
1327 copy.button = ev;
1328 if (_rail.enabled())
1329 {
1330 /* Suppress X11 raw button during WM op. */
1331 const bool suppress = _rail.suppressServerInput(ev.windowID);
1332 if (ev.type == SDL_EVENT_MOUSE_BUTTON_UP)
1333 _rail.completeLocalMoveIfPending();
1334 /* Backstop for a Wayland resize whose MOUSE_ENTER completion never arrived; a fresh press
1335 * proves the old grab is over, so a no-op click grab is cancelled here too. */
1336 if (ev.type == SDL_EVENT_MOUSE_BUTTON_DOWN)
1337 _rail.completeWaylandResize(true);
1338 /* activate the clicked window before forwarding, so the click routes to it. */
1339 if (ev.type == SDL_EVENT_MOUSE_BUTTON_DOWN)
1340 _rail.ensureActive(ev.windowID);
1341 if (suppress)
1342 return true;
1343 if (_rail.translateToServer(ev.windowID, copy.button.x, copy.button.y))
1344 {
1345 /* Record the forwarded left press: it is the anchor of any server modal move/size
1346 * loop this press starts (the loop moves the window by release - anchor). */
1347 if ((ev.type == SDL_EVENT_MOUSE_BUTTON_DOWN) && (ev.button == SDL_BUTTON_LEFT))
1348 _rail.noteLeftPress(copy.button.x, copy.button.y);
1349 return SdlTouch::handleEvent(this, copy.button);
1350 }
1351 }
1352
1353 if (!getWindowForId(ev.windowID))
1354 return true;
1355 if (!eventToPixelCoordinates(ev.windowID, copy))
1356 return true;
1357 removeLocalScaling(copy.button.x, copy.button.y);
1358 applyMonitorOffset(copy.button.windowID, copy.button.x, copy.button.y);
1359 return SdlTouch::handleEvent(this, copy.button);
1360}
1361
1362bool SdlContext::handleEvent(const SDL_TouchFingerEvent& ev)
1363{
1364 if (!getWindowForId(ev.windowID))
1365 return true;
1366 SDL_Event copy{};
1367 copy.tfinger = ev;
1368 if (!eventToPixelCoordinates(ev.windowID, copy))
1369 return true;
1370 removeLocalScaling(copy.tfinger.dx, copy.tfinger.dy);
1371 removeLocalScaling(copy.tfinger.x, copy.tfinger.y);
1372 applyMonitorOffset(copy.tfinger.windowID, copy.tfinger.x, copy.tfinger.y);
1373 return SdlTouch::handleEvent(this, copy.tfinger);
1374}
1375
1376void SdlContext::addOrUpdateDisplay(SDL_DisplayID id)
1377{
1378 auto monitor = SdlWindow::query(id, false);
1379 _displays.emplace(id, monitor);
1380
1381 /* Update actual display rectangles:
1382 *
1383 * 1. Get logical display bounds
1384 * 2. Use already known pixel width and height
1385 * 3. Iterate over each display and update the x and y offsets by adding all monitor
1386 * widths/heights from the primary
1387 */
1388 _offsets.clear();
1389 for (auto& entry : _displays)
1390 {
1391 SDL_Rect bounds{};
1392 std::ignore = SDL_GetDisplayBounds(entry.first, &bounds);
1393
1394 SDL_Rect pixel{};
1395 pixel.w = entry.second.width;
1396 pixel.h = entry.second.height;
1397 _offsets.emplace(entry.first, std::pair{ bounds, pixel });
1398 }
1399
1400 /* 1. Find primary and update all neighbors
1401 * 2. For each neighbor update all neighbors
1402 * 3. repeat until all displays updated.
1403 */
1404 const auto primary = SDL_GetPrimaryDisplay();
1405 std::vector<SDL_DisplayID> handled;
1406 handled.push_back(primary);
1407
1408 auto neighbors = updateDisplayOffsetsForNeighbours(primary);
1409 while (!neighbors.empty())
1410 {
1411 auto neighbor = neighbors.front();
1412 neighbors.erase(neighbors.begin());
1413
1414 if (std::find(handled.begin(), handled.end(), neighbor) != handled.end())
1415 continue;
1416 handled.push_back(neighbor);
1417
1418 auto next = updateDisplayOffsetsForNeighbours(neighbor, handled);
1419 neighbors.insert(neighbors.end(), next.begin(), next.end());
1420 }
1421 updateMonitorDataFromOffsets();
1422}
1423
1424void SdlContext::deleteDisplay(SDL_DisplayID id)
1425{
1426 _displays.erase(id);
1427}
1428
1429bool SdlContext::eventToPixelCoordinates(SDL_WindowID id, SDL_Event& ev)
1430{
1431 auto w = getWindowForId(id);
1432 if (!w)
1433 return false;
1434
1435 /* Ignore errors here, sometimes SDL has no renderer */
1436 auto renderer = w->renderer();
1437 if (!renderer)
1438 return true;
1439 return SDL_ConvertEventToRenderCoordinates(renderer, &ev);
1440}
1441
1442SDL_FPoint SdlContext::applyLocalScaling(const SDL_FPoint& val) const
1443{
1444 if (!useLocalScale())
1445 return val;
1446
1447 auto rval = val;
1448 rval.x *= _localScale.x;
1449 rval.y *= _localScale.y;
1450 return rval;
1451}
1452
1453void SdlContext::removeLocalScaling(float& x, float& y) const
1454{
1455 if (!useLocalScale())
1456 return;
1457 x /= _localScale.x;
1458 y /= _localScale.y;
1459}
1460
1461SDL_FPoint SdlContext::screenToPixel(SDL_WindowID id, const SDL_FPoint& pos)
1462{
1463 auto w = getWindowForId(id);
1464 if (!w)
1465 {
1466 /* RAIL windows are server-authoritative 1:1 (no local scaling) and not in _windows. */
1467 if (_rail.enabled() && _rail.ownsWindow(id))
1468 return pos;
1469 return {};
1470 }
1471
1472 /* Ignore errors here, sometimes SDL has no renderer */
1473 auto renderer = w->renderer();
1474 if (!renderer)
1475 return pos;
1476
1477 SDL_FPoint rpos{};
1478 if (!SDL_RenderCoordinatesFromWindow(renderer, pos.x, pos.y, &rpos.x, &rpos.y))
1479 return {};
1480 removeLocalScaling(rpos.x, rpos.y);
1481 return rpos;
1482}
1483
1484SDL_FPoint SdlContext::pixelToScreen(SDL_WindowID id, const SDL_FPoint& pos)
1485{
1486 auto w = getWindowForId(id);
1487 if (!w)
1488 {
1489 if (_rail.enabled() && _rail.ownsWindow(id))
1490 return pos;
1491 return {};
1492 }
1493
1494 /* Ignore errors here, sometimes SDL has no renderer */
1495 auto renderer = w->renderer();
1496 if (!renderer)
1497 return pos;
1498
1499 SDL_FPoint rpos{};
1500 if (!SDL_RenderCoordinatesToWindow(renderer, pos.x, pos.y, &rpos.x, &rpos.y))
1501 return {};
1502 return applyLocalScaling(rpos);
1503}
1504
1505SDL_FRect SdlContext::pixelToScreen(SDL_WindowID id, const SDL_FRect& pos, bool round)
1506{
1507 const auto fpos = pixelToScreen(id, SDL_FPoint{ pos.x, pos.y });
1508 const auto size = pixelToScreen(id, SDL_FPoint{ pos.w, pos.h });
1509 SDL_FRect r{ fpos.x, fpos.y, size.x, size.y };
1510 if (round)
1511 {
1512 r.w = std::ceil(r.w);
1513 r.h = std::ceil(r.h);
1514 r.x = std::floor(r.x);
1515 r.y = std::floor(r.y);
1516 }
1517 return r;
1518}
1519
1520bool SdlContext::handleEvent(const SDL_Event& ev)
1521{
1522 if ((ev.type >= SDL_EVENT_DISPLAY_FIRST) && (ev.type <= SDL_EVENT_DISPLAY_LAST))
1523 {
1524 const auto& dev = ev.display;
1525 return handleEvent(dev);
1526 }
1527 if ((ev.type >= SDL_EVENT_WINDOW_FIRST) && (ev.type <= SDL_EVENT_WINDOW_LAST))
1528 {
1529 const auto& wev = ev.window;
1530 return handleEvent(wev);
1531 }
1532 switch (ev.type)
1533 {
1534 case SDL_EVENT_RENDER_TARGETS_RESET:
1535 case SDL_EVENT_RENDER_DEVICE_RESET:
1536 case SDL_EVENT_WILL_ENTER_FOREGROUND:
1537 return redraw();
1538 default:
1539 break;
1540 }
1541
1542 if (!isConnected())
1543 return true;
1544
1545 switch (ev.type)
1546 {
1547 case SDL_EVENT_FINGER_DOWN:
1548 case SDL_EVENT_FINGER_UP:
1549 case SDL_EVENT_FINGER_MOTION:
1550 {
1551 const auto& cev = ev.tfinger;
1552 return handleEvent(cev);
1553 }
1554 case SDL_EVENT_MOUSE_MOTION:
1555
1556 {
1557 const auto& cev = ev.motion;
1558 return handleEvent(cev);
1559 }
1560 case SDL_EVENT_MOUSE_BUTTON_DOWN:
1561 case SDL_EVENT_MOUSE_BUTTON_UP:
1562 {
1563 const auto& cev = ev.button;
1564 return handleEvent(cev);
1565 }
1566 case SDL_EVENT_MOUSE_WHEEL:
1567 {
1568 const auto& cev = ev.wheel;
1569 return handleEvent(cev);
1570 }
1571 case SDL_EVENT_CLIPBOARD_UPDATE:
1572 {
1573 const auto& cev = ev.clipboard;
1574 return getClipboardChannelContext().handleEvent(cev);
1575 }
1576 case SDL_EVENT_KEY_DOWN:
1577 case SDL_EVENT_KEY_UP:
1578 {
1579 const auto& cev = ev.key;
1580 return getInputChannelContext().handleEvent(cev);
1581 }
1582 default:
1583 return true;
1584 }
1585}
1586
1587COMMAND_LINE_ARGUMENT_A* SdlContext::args()
1588{
1589 return _args.data();
1590}
1591
1592size_t SdlContext::argsCount() const
1593{
1594 if (_args.size() <= 1)
1595 return 0;
1596 return _args.size() - 1;
1597}
1598
1599int SdlContext::argumentHandler(const COMMAND_LINE_ARGUMENT_A* arg, void* custom)
1600{
1601 auto sdl = static_cast<SdlContext*>(custom);
1602 if (!sdl)
1603 return -1;
1604
1605 if (arg->Name)
1606 {
1607 if (strcmp(arg->Name, sdl_allow_screensaver) == 0)
1608 {
1609 if (arg->Value != nullptr)
1610 {
1611 if (!SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"))
1612 {
1613 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
1614 "SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER) failed with %s",
1615 SDL_GetError());
1616 return -2;
1617 }
1618 }
1619 }
1620 else if (strcmp(arg->Name, sdl_connection_dialog) == 0)
1621 sdl->_connectionDialog = (arg->Value != nullptr);
1622 }
1623 return 0;
1624}
1625
1626CriticalSection& SdlContext::lock()
1627{
1628 return _critical;
1629}
1630
1631std::vector<rdpPointer*>& SdlContext::pointers()
1632{
1633 return _valid_pointers;
1634}
1635
1636bool SdlContext::contains(const rdpPointer* ptr) const
1637{
1638 for (const auto& cur : _valid_pointers)
1639 {
1640 if (cur == ptr)
1641 return true;
1642 }
1643 return false;
1644}
1645
1646bool SdlContext::credentialsRead() const
1647{
1648 return _credentialsRead;
1649}
1650
1651void SdlContext::setCredentialsRead()
1652{
1653 _credentialsRead = true;
1654}
1655
1656bool SdlContext::resizeToScale(SdlWindow* window)
1657{
1658 if (freerdp_settings_get_bool(context()->settings, FreeRDP_SmartSizing))
1659 return true;
1660 if (!useLocalScale())
1661 return true;
1662 if (!window)
1663 return false;
1664 return window->resizeToScale();
1665}
1666
1667bool SdlContext::useLocalScale() const
1668{
1669 const auto ssize = freerdp_settings_get_bool(context()->settings, FreeRDP_SmartSizing);
1670 if (ssize)
1671 return true;
1672 const auto dynResize =
1673 freerdp_settings_get_bool(context()->settings, FreeRDP_DynamicResolutionUpdate);
1674 const auto fs = freerdp_settings_get_bool(context()->settings, FreeRDP_Fullscreen);
1675 const auto multimon = freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon);
1676 return !dynResize && !fs && !multimon;
1677}
1678
1679bool SdlContext::drawToWindows(const std::vector<SDL_Rect>& rects)
1680{
1681 /* RAIL damage is per-window (_gfxDamage), not in the rects queue: repaint every tick. */
1682 if (_rail.enabled())
1683 {
1684 for (auto& window : _windows)
1685 {
1686 if ((SDL_GetWindowFlags(window.second.window()) & SDL_WINDOW_HIDDEN) == 0)
1687 SDL_HideWindow(window.second.window());
1688 }
1689
1690 std::unique_lock lock(_critical);
1691 _rail.paint(_primary.get(), pixelFormat(), rects);
1692 return true;
1693 }
1694
1695 /* Non-RAIL mode (e.g. Non-Monitored Desktop during UAC / Consent UI): show the desktop window.
1696 */
1697 bool firstShow = false;
1698 for (auto& window : _windows)
1699 {
1700 if (SDL_GetWindowFlags(window.second.window()) & SDL_WINDOW_HIDDEN)
1701 {
1702 SDL_ShowWindow(window.second.window());
1703 SDL_RaiseWindow(window.second.window());
1704 firstShow = true;
1705 }
1706 }
1707
1708 if (rects.empty() && !firstShow)
1709 return true;
1710
1711 std::vector<SDL_Rect> drawRects = rects;
1712 {
1713 std::unique_lock lock(_critical);
1714 if ((drawRects.empty() || firstShow) && _primary)
1715 drawRects = { { 0, 0, _primary->w, _primary->h } };
1716 }
1717
1718 for (auto& window : _windows)
1719 {
1720 if (!drawToWindow(window.second, drawRects))
1721 return false;
1722 }
1723
1724 return true;
1725}
1726
1727BOOL SdlContext::desktopResize(rdpContext* context)
1728{
1729 rdpGdi* gdi = nullptr;
1730 rdpSettings* settings = nullptr;
1731 auto sdl = get_context(context);
1732
1733 WINPR_ASSERT(sdl);
1734 WINPR_ASSERT(context);
1735
1736 settings = context->settings;
1737 WINPR_ASSERT(settings);
1738
1739 std::unique_lock lock(sdl->_critical);
1740 gdi = context->gdi;
1741 if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
1742 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
1743 return FALSE;
1744 return sdl->createPrimary();
1745}
1746
1747/* This function is called to output a System BEEP */
1748BOOL SdlContext::playSound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound)
1749{
1750 /* TODO: Implement */
1751 WINPR_UNUSED(context);
1752 WINPR_UNUSED(play_sound);
1753 return TRUE;
1754}
1755
1756/* This function is called whenever a new frame starts.
1757 * It can be used to reset invalidated areas. */
1758BOOL SdlContext::beginPaint(rdpContext* context)
1759{
1760 auto gdi = context->gdi;
1761 WINPR_ASSERT(gdi);
1762 WINPR_ASSERT(gdi->primary);
1763
1764 HGDI_DC hdc = gdi->primary->hdc;
1765 WINPR_ASSERT(hdc);
1766 if (!hdc->hwnd)
1767 return TRUE;
1768
1769 HGDI_WND hwnd = hdc->hwnd;
1770 WINPR_ASSERT(hwnd->invalid);
1771 hwnd->invalid->null = TRUE;
1772 hwnd->ninvalid = 0;
1773
1774 return TRUE;
1775}
1776
1777bool SdlContext::redraw(bool suppress) const
1778{
1779 if (!_connected)
1780 return true;
1781
1782 /* In RAIL mode, hiding the desktop window must not suppress server output. */
1783 if (suppress && _rail.enabled())
1784 return true;
1785
1786 auto gdi = context()->gdi;
1787 WINPR_ASSERT(gdi);
1788 return gdi_send_suppress_output(gdi, suppress);
1789}
1790
1791void SdlContext::setConnected(bool val)
1792{
1793 _connected = val;
1794}
1795
1796bool SdlContext::isConnected() const
1797{
1798 return _connected;
1799}
1800
1801rdpContext* SdlContext::context() const
1802{
1803 WINPR_ASSERT(_context);
1804 return _context;
1805}
1806
1807rdpClientContext* SdlContext::common() const
1808{
1809 return reinterpret_cast<rdpClientContext*>(context());
1810}
1811
1812bool SdlContext::setCursor(CursorType type)
1813{
1814 _cursorType = type;
1815 return restoreCursor();
1816}
1817
1818bool SdlContext::setCursor(const rdpPointer* cursor)
1819{
1820 std::unique_lock lock(_critical);
1821 if (!contains(cursor))
1822 return true;
1823
1824 _cursor = { sdl_Pointer_Copy(cursor), sdl_PointerFreeCopyAll };
1825 return setCursor(CURSOR_IMAGE);
1826}
1827
1828rdpPointer* SdlContext::cursor() const
1829{
1830 return _cursor.get();
1831}
1832
1833bool SdlContext::restoreCursor()
1834{
1835 WLog_Print(getWLog(), WLOG_TRACE, "restore cursor: %d", _cursorType);
1836 switch (_cursorType)
1837 {
1838 case CURSOR_NULL:
1839 if (!SDL_HideCursor())
1840 {
1841 WLog_Print(getWLog(), WLOG_ERROR, "SDL_HideCursor failed");
1842 return false;
1843 }
1844
1845 setHasCursor(false);
1846 return true;
1847
1848 case CURSOR_DEFAULT:
1849 {
1850 auto def = SDL_GetDefaultCursor();
1851 if (!SDL_SetCursor(def))
1852 {
1853 WLog_Print(getWLog(), WLOG_ERROR, "SDL_SetCursor(default=%p) failed",
1854 static_cast<void*>(def));
1855 return false;
1856 }
1857 if (!SDL_ShowCursor())
1858 {
1859 WLog_Print(getWLog(), WLOG_ERROR, "SDL_ShowCursor failed");
1860 return false;
1861 }
1862 setHasCursor(true);
1863 return true;
1864 }
1865 case CURSOR_IMAGE:
1866 setHasCursor(true);
1867 return sdl_Pointer_Set_Process(this);
1868 default:
1869 WLog_Print(getWLog(), WLOG_ERROR, "Unknown cursorType %s",
1870 sdl::utils::toString(_cursorType).c_str());
1871 return false;
1872 }
1873}
1874
1875void SdlContext::setMonitorIds(const std::vector<SDL_DisplayID>& ids)
1876{
1877 _monitorIds.clear();
1878 for (auto id : ids)
1879 {
1880 _monitorIds.push_back(id);
1881 }
1882}
1883
1884const std::vector<SDL_DisplayID>& SdlContext::monitorIds() const
1885{
1886 return _monitorIds;
1887}
1888
1889int64_t SdlContext::monitorId(uint32_t index) const
1890{
1891 if (index >= _monitorIds.size())
1892 {
1893 return -1;
1894 }
1895 return _monitorIds.at(index);
1896}
1897
1898void SdlContext::push(std::vector<SDL_Rect>&& rects)
1899{
1900 std::unique_lock lock(_queue_mux);
1901 _queue.emplace(std::move(rects));
1902}
1903
1904std::vector<SDL_Rect> SdlContext::pop()
1905{
1906 std::unique_lock lock(_queue_mux);
1907 if (_queue.empty())
1908 {
1909 return {};
1910 }
1911 auto val = std::move(_queue.front());
1912 _queue.pop();
1913 return val;
1914}
1915
1916bool SdlContext::setFullscreen(bool enter, bool forceOriginalDisplay)
1917{
1918 for (const auto& window : _windows)
1919 {
1920 if (!sdl_push_user_event(SDL_EVENT_USER_WINDOW_FULLSCREEN, &window.second, enter,
1921 forceOriginalDisplay))
1922 return false;
1923 }
1924 _fullscreen = enter;
1925 return true;
1926}
1927
1928bool SdlContext::setMinimized()
1929{
1930 return sdl_push_user_event(SDL_EVENT_USER_WINDOW_MINIMIZE);
1931}
1932
1933bool SdlContext::grabMouse() const
1934{
1935 return _grabMouse;
1936}
1937
1938bool SdlContext::toggleGrabMouse()
1939{
1940 return setGrabMouse(!grabMouse());
1941}
1942
1943bool SdlContext::setGrabMouse(bool enter)
1944{
1945 _grabMouse = enter;
1946 return true;
1947}
1948
1949bool SdlContext::grabKeyboard() const
1950{
1951 return _grabKeyboard;
1952}
1953
1954bool SdlContext::toggleGrabKeyboard()
1955{
1956 return setGrabKeyboard(!grabKeyboard());
1957}
1958
1959bool SdlContext::setGrabKeyboard(bool enter)
1960{
1961 _grabKeyboard = enter;
1962 return true;
1963}
1964
1965bool SdlContext::setResizeable(bool enable)
1966{
1967 const auto settings = context()->settings;
1968 const bool dyn = freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate);
1969 const bool smart = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing);
1970 bool use = (dyn && enable) || smart;
1971
1972 for (const auto& window : _windows)
1973 {
1974 if (!sdl_push_user_event(SDL_EVENT_USER_WINDOW_RESIZEABLE, &window.second, use))
1975 return false;
1976 }
1977 _resizeable = use;
1978
1979 return true;
1980}
1981
1982bool SdlContext::resizeable() const
1983{
1984 return _resizeable;
1985}
1986
1987bool SdlContext::toggleResizeable()
1988{
1989 return setResizeable(!resizeable());
1990}
1991
1992bool SdlContext::fullscreen() const
1993{
1994 return _fullscreen;
1995}
1996
1997bool SdlContext::toggleFullscreen()
1998{
1999 return setFullscreen(!fullscreen());
2000}
object that handles clipboard context for the SDL3 client
Definition sdl_clip.hpp:77
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_server_name(const rdpSettings *settings)
A helper function to return the correct server name.
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_bool(rdpSettings *settings, FreeRDP_Settings_Keys_Bool id, BOOL val)
Sets a BOOL settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_uint32(rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 val)
Sets a UINT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_monitor_def_array_sorted(rdpSettings *settings, const rdpMonitor *monitors, size_t count)
Sort monitor array according to:
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.