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