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 const bool ax = alignX(entry.second.first, first.first);
845 const bool ay = alignY(entry.second.first, first.first);
846
847 /* Offset along the axis that does not touch, converted from logical to pixel
848 * units of the reference display. Without this, a monitor that is adjacent on
849 * one axis but shifted on the other (e.g. a wider monitor above another, or a
850 * rotated monitor that is not top aligned) keeps offset 0 on that axis, the
851 * layout sent to the server no longer matches the local one and pointer input
852 * lands offset by that difference. */
853 auto crossOffset = [](int ePos, int fPos, int fLogical, int fPixel) -> int
854 {
855 if (fLogical <= 0)
856 return ePos - fPos;
857 return static_cast<int>(
858 std::lround(static_cast<double>(ePos - fPos) * fPixel / fLogical));
859 };
860
861 if (ax)
862 {
863 if (entry.second.first.x < first.first.x)
864 entry.second.second.x = first.second.x - entry.second.second.w;
865 else
866 entry.second.second.x = first.second.x + first.second.w;
867 if (!ay)
868 entry.second.second.y =
869 first.second.y +
870 crossOffset(entry.second.first.y, first.first.y, first.first.h, first.second.h);
871 neighbor = true;
872 }
873 if (ay)
874 {
875 if (entry.second.first.y < first.first.y)
876 entry.second.second.y = first.second.y - entry.second.second.h;
877 else
878 entry.second.second.y = first.second.y + first.second.h;
879 if (!ax)
880 entry.second.second.x =
881 first.second.x +
882 crossOffset(entry.second.first.x, first.first.x, first.first.w, first.second.w);
883 neighbor = true;
884 }
885
886 if (neighbor)
887 neighbours.push_back(entry.first);
888 }
889 return neighbours;
890}
891
892void SdlContext::updateMonitorDataFromOffsets()
893{
894 for (auto& entry : _displays)
895 {
896 auto offsets = _offsets.at(entry.first);
897 entry.second.x = offsets.second.x;
898 entry.second.y = offsets.second.y;
899 }
900
901 for (auto& entry : _windows)
902 {
903 const auto& monitor = _displays.at(entry.first);
904 entry.second.setMonitor(monitor);
905 }
906}
907
908bool SdlContext::drawToWindow(SdlWindow& window, const std::vector<SDL_Rect>& rects)
909{
910 if (!isConnected())
911 return true;
912
913 auto gdi = context()->gdi;
914 WINPR_ASSERT(gdi);
915
916 auto size = window.rect();
917
918 std::unique_lock lock(_critical);
919 auto surface = _primary.get();
920
921 if (useLocalScale())
922 {
923 window.setOffsetX(0);
924 window.setOffsetY(0);
925 if (gdi->width < size.w)
926 {
927 window.setOffsetX((size.w - gdi->width) / 2);
928 }
929 if (gdi->height < size.h)
930 {
931 window.setOffsetY((size.h - gdi->height) / 2);
932 }
933
934 _localScale = { static_cast<float>(size.w) / static_cast<float>(gdi->width),
935 static_cast<float>(size.h) / static_cast<float>(gdi->height) };
936 if (!window.drawScaledRects(surface, _localScale, rects))
937 return false;
938 }
939 else
940 {
941 SDL_Point offset{ 0, 0 };
942 if (freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon))
943 offset = { window.offsetX(), window.offsetY() };
944 if (!window.drawRects(surface, offset, rects))
945 return false;
946 }
947
948 window.updateSurface();
949 return true;
950}
951
952bool SdlContext::minimizeAllWindows()
953{
954 for (auto& w : _windows)
955 w.second.minimize();
956 return true;
957}
958
959int SdlContext::exitCode() const
960{
961 return _exitCode;
962}
963
964SDL_PixelFormat SdlContext::pixelFormat() const
965{
966 return _sdlPixelFormat;
967}
968
969bool SdlContext::addDisplayWindow(SDL_DisplayID id)
970{
971 const auto flags =
972 SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS;
973 auto title = sdl::utils::windowTitle(context()->settings);
974 auto w = SdlWindow::create(id, title, flags);
975 _windows.emplace(w.id(), std::move(w));
976 return true;
977}
978
979bool SdlContext::removeDisplayWindow(SDL_DisplayID id)
980{
981 for (auto& w : _windows)
982 {
983 if (w.second.displayIndex() == id)
984 _windows.erase(w.first);
985 }
986 return true;
987}
988
989bool SdlContext::detectDisplays()
990{
991 int count = 0;
992 auto display = SDL_GetDisplays(&count);
993 if (!display)
994 return false;
995 for (int x = 0; x < count; x++)
996 {
997 const auto id = display[x];
998 addOrUpdateDisplay(id);
999 }
1000 SDL_free(display);
1001 return true;
1002}
1003
1004rdpMonitor SdlContext::getDisplay(SDL_DisplayID id) const
1005{
1006 return _displays.at(id);
1007}
1008
1009std::vector<SDL_DisplayID> SdlContext::getDisplayIds() const
1010{
1011 std::vector<SDL_DisplayID> keys;
1012 keys.reserve(_displays.size());
1013 for (const auto& entry : _displays)
1014 {
1015 keys.push_back(entry.first);
1016 }
1017 return keys;
1018}
1019
1020const SdlWindow* SdlContext::getWindowForId(SDL_WindowID id) const
1021{
1022 auto it = _windows.find(id);
1023 if (it == _windows.end())
1024 return nullptr;
1025 return &it->second;
1026}
1027
1028SdlWindow* SdlContext::getWindowForId(SDL_WindowID id)
1029{
1030 auto it = _windows.find(id);
1031 if (it == _windows.end())
1032 return nullptr;
1033 return &it->second;
1034}
1035
1036SdlWindow* SdlContext::getFirstWindow()
1037{
1038 if (_windows.empty())
1039 return nullptr;
1040 return &_windows.begin()->second;
1041}
1042
1043sdlDispContext& SdlContext::getDisplayChannelContext()
1044{
1045 return _disp;
1046}
1047
1048sdlInput& SdlContext::getInputChannelContext()
1049{
1050 return _input;
1051}
1052
1053sdlClip& SdlContext::getClipboardChannelContext()
1054{
1055 return _clip;
1056}
1057
1058SdlRail& SdlContext::getRailChannelContext()
1059{
1060 return _rail;
1061}
1062
1063SdlConnectionDialogWrapper& SdlContext::getDialog()
1064{
1065 return _dialog;
1066}
1067
1068wLog* SdlContext::getWLog()
1069{
1070 return _log;
1071}
1072
1073bool SdlContext::moveMouseTo(const SDL_FPoint& pos)
1074{
1075 auto window = SDL_GetMouseFocus();
1076 if (!window)
1077 return true;
1078
1079 const auto id = SDL_GetWindowID(window);
1080 const auto spos = pixelToScreen(id, pos);
1081 SDL_WarpMouseInWindow(window, spos.x, spos.y);
1082 return true;
1083}
1084
1085bool SdlContext::handleEvent(const SDL_MouseMotionEvent& ev)
1086{
1087 SDL_Event copy{};
1088 copy.motion = ev;
1089 /* WM owns the drag (#12447); backstop: button released but button-up swallowed by grab. */
1090 if (_rail.enabled() && _rail.suppressServerMotion(ev.windowID))
1091 {
1092 if (!(SDL_GetGlobalMouseState(nullptr, nullptr) & SDL_BUTTON_LMASK))
1093 _rail.completeLocalMoveIfPending();
1094 return true;
1095 }
1096 if (_rail.enabled() && _rail.translateToServer(ev.windowID, copy.motion.x, copy.motion.y))
1097 {
1098 _rail.noteServerPointer(copy.motion.x, copy.motion.y);
1099 return SdlTouch::handleEvent(this, copy.motion);
1100 }
1101
1102 if (!getWindowForId(ev.windowID))
1103 return true; /* Event for an untracked window (e.g. closed dialog) */
1104 if (!eventToPixelCoordinates(ev.windowID, copy))
1105 return true;
1106 removeLocalScaling(copy.motion.x, copy.motion.y);
1107 removeLocalScaling(copy.motion.xrel, copy.motion.yrel);
1108 applyMonitorOffset(copy.motion.windowID, copy.motion.x, copy.motion.y);
1109
1110 return SdlTouch::handleEvent(this, copy.motion);
1111}
1112
1113bool SdlContext::handleEvent(const SDL_MouseWheelEvent& ev)
1114{
1115 SDL_Event copy{};
1116 copy.wheel = ev;
1117 if (_rail.enabled() &&
1118 _rail.translateToServer(ev.windowID, copy.wheel.mouse_x, copy.wheel.mouse_y))
1119 return SdlTouch::handleEvent(this, copy.wheel);
1120
1121 if (!getWindowForId(ev.windowID))
1122 return true;
1123 if (!eventToPixelCoordinates(ev.windowID, copy))
1124 return true;
1125 removeLocalScaling(copy.wheel.mouse_x, copy.wheel.mouse_y);
1126 return SdlTouch::handleEvent(this, copy.wheel);
1127}
1128
1129bool SdlContext::handleEvent(const SDL_WindowEvent& ev)
1130{
1131 if (!getDisplayChannelContext().handleEvent(ev))
1132 return false;
1133
1134 auto window = getWindowForId(ev.windowID);
1135 if (!window)
1136 {
1137 /* RAIL windows aren't in _windows; handle their events here. */
1138 if (_rail.enabled() && _rail.ownsWindow(ev.windowID))
1139 {
1140 switch (ev.type)
1141 {
1142 case SDL_EVENT_WINDOW_MOUSE_ENTER:
1143 /* Re-enter fires on move-grab end. */
1144 if (!(SDL_GetGlobalMouseState(nullptr, nullptr) & SDL_BUTTON_LMASK))
1145 _rail.completeLocalMoveIfPending(); /* X11 */
1146 _rail.completeWaylandResize(); /* Wayland: compositor grab ended */
1147 /* Restore the cursor or the pointer stays hidden over RemoteApp windows. */
1148 return restoreCursor();
1149 case SDL_EVENT_WINDOW_MOUSE_LEAVE:
1150 /* Compositor took the pointer for the pending Wayland resize grab. */
1151 _rail.noteResizeGrab(ev.windowID);
1152 return true;
1153 case SDL_EVENT_WINDOW_EXPOSED:
1154 /* Force repaint for exposed windows; skip during local drag to avoid redundant
1155 * frames. */
1156 if (!_rail.suppressServerInput(ev.windowID))
1157 _rail.invalidateWindow(ev.windowID);
1158 return true;
1159 case SDL_EVENT_WINDOW_MAXIMIZED:
1160 /* Maximize must complete move first. */
1161 _rail.completeLocalMoveIfPending();
1162 _rail.handleMaximized(ev.windowID);
1163 return true;
1164 case SDL_EVENT_WINDOW_MINIMIZED:
1165 _rail.handleMinimized(ev.windowID);
1166 return true;
1167 case SDL_EVENT_WINDOW_RESTORED:
1168 _rail.handleRestored(ev.windowID);
1169 return true;
1170 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
1171 /* Send SC_CLOSE, don't abort the session. */
1172 _rail.handleClose(ev.windowID);
1173 return true;
1174 case SDL_EVENT_WINDOW_FOCUS_GAINED:
1175 _rail.handleFocus(ev.windowID, true);
1176 return true;
1177 case SDL_EVENT_WINDOW_FOCUS_LOST:
1178 _rail.handleFocus(ev.windowID, false);
1179 return true;
1180 case SDL_EVENT_WINDOW_MOVED:
1181 if (!_rail.suppressServerInput(ev.windowID))
1182 _rail.syncGeometry(ev.windowID);
1183 return true;
1184 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
1185 /* No server frame during the drag: repaint synchronously each configure step;
1186 * also report the new size to catch a compositor snap/tile. */
1187 _rail.noteDragResize(ev.windowID, ev.data1, ev.data2);
1188 _rail.syncGeometry(ev.windowID);
1189 _rail.handleWaylandResize(ev.windowID);
1190 std::ignore = drawToWindows({});
1191 return true;
1192 default:
1193 break;
1194 }
1195 }
1196 return true;
1197 }
1198
1199 {
1200 const auto& r = window->rect();
1201 const auto& b = window->bounds();
1202 const auto& scale = window->scale();
1203 const auto& orientation = window->orientation();
1204 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
1205 "%s: [%u] %dx%d-%dx%d {%dx%d-%dx%d}{scale=%f,orientation=%s}",
1206 sdl::utils::toString(ev.type).c_str(), ev.windowID, r.x, r.y, r.w, r.h, b.x,
1207 b.y, b.w, b.h, static_cast<double>(scale),
1208 sdl::utils::toString(orientation).c_str());
1209 }
1210
1211 switch (ev.type)
1212 {
1213 case SDL_EVENT_WINDOW_MOUSE_ENTER:
1214 return restoreCursor();
1215 case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
1216 if (!resizeToScale(window))
1217 return false;
1218 if (isConnected())
1219 {
1220 if (!window->fill())
1221 return false;
1222 if (!drawToWindow(*window))
1223 return false;
1224 if (!restoreCursor())
1225 return false;
1226 }
1227 break;
1228 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
1229 if (!resizeToScale(window))
1230 return false;
1231 if (!window->fill())
1232 return false;
1233 if (!drawToWindow(*window))
1234 return false;
1235 if (!restoreCursor())
1236 return false;
1237 break;
1238 case SDL_EVENT_WINDOW_MOVED:
1239 {
1240 auto r = window->rect();
1241 auto id = window->id();
1242 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%u: %dx%d-%dx%d", id, r.x, r.y, r.w, r.h);
1243 }
1244 break;
1245 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
1246 {
1247 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Window closed, terminating RDP session...");
1248 freerdp_abort_connect_context(context());
1249 }
1250 break;
1251 default:
1252 break;
1253 }
1254 return true;
1255}
1256
1257bool SdlContext::handleEvent(const SDL_DisplayEvent& ev)
1258{
1259 if (!getDisplayChannelContext().handleEvent(ev))
1260 return false;
1261
1262 switch (ev.type)
1263 {
1264 case SDL_EVENT_DISPLAY_REMOVED: // Can't show details for this one...
1265 break;
1266 default:
1267 {
1268 SDL_Rect r = {};
1269 if (!SDL_GetDisplayBounds(ev.displayID, &r))
1270 return false;
1271 const auto name = SDL_GetDisplayName(ev.displayID);
1272 if (!name)
1273 return false;
1274 const auto orientation = SDL_GetCurrentDisplayOrientation(ev.displayID);
1275 const auto scale = SDL_GetDisplayContentScale(ev.displayID);
1276 const auto mode = SDL_GetCurrentDisplayMode(ev.displayID);
1277 if (!mode)
1278 return false;
1279
1280 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
1281 "%s: [%u, %s] %dx%d-%dx%d {orientation=%s, scale=%f}%s",
1282 sdl::utils::toString(ev.type).c_str(), ev.displayID, name, r.x, r.y, r.w,
1283 r.h, sdl::utils::toString(orientation).c_str(), static_cast<double>(scale),
1284 sdl::utils::toString(mode).c_str());
1285 }
1286 break;
1287 }
1288 return true;
1289}
1290
1291bool SdlContext::handleEvent(const SDL_MouseButtonEvent& ev)
1292{
1293 SDL_Event copy = {};
1294 copy.button = ev;
1295 if (_rail.enabled())
1296 {
1297 /* Suppress X11 raw button during WM op. */
1298 const bool suppress = _rail.suppressServerInput(ev.windowID);
1299 if (ev.type == SDL_EVENT_MOUSE_BUTTON_UP)
1300 _rail.completeLocalMoveIfPending();
1301 /* Backstop for a Wayland resize whose MOUSE_ENTER completion never arrived; a fresh press
1302 * proves the old grab is over, so a no-op click grab is cancelled here too. */
1303 if (ev.type == SDL_EVENT_MOUSE_BUTTON_DOWN)
1304 _rail.completeWaylandResize(true);
1305 /* activate the clicked window before forwarding, so the click routes to it. */
1306 if (ev.type == SDL_EVENT_MOUSE_BUTTON_DOWN)
1307 _rail.ensureActive(ev.windowID);
1308 if (suppress)
1309 return true;
1310 if (_rail.translateToServer(ev.windowID, copy.button.x, copy.button.y))
1311 {
1312 /* Record the forwarded left press: it is the anchor of any server modal move/size
1313 * loop this press starts (the loop moves the window by release - anchor). */
1314 if ((ev.type == SDL_EVENT_MOUSE_BUTTON_DOWN) && (ev.button == SDL_BUTTON_LEFT))
1315 _rail.noteLeftPress(copy.button.x, copy.button.y);
1316 return SdlTouch::handleEvent(this, copy.button);
1317 }
1318 }
1319
1320 if (!getWindowForId(ev.windowID))
1321 return true;
1322 if (!eventToPixelCoordinates(ev.windowID, copy))
1323 return true;
1324 removeLocalScaling(copy.button.x, copy.button.y);
1325 applyMonitorOffset(copy.button.windowID, copy.button.x, copy.button.y);
1326 return SdlTouch::handleEvent(this, copy.button);
1327}
1328
1329bool SdlContext::handleEvent(const SDL_TouchFingerEvent& ev)
1330{
1331 if (!getWindowForId(ev.windowID))
1332 return true;
1333 SDL_Event copy{};
1334 copy.tfinger = ev;
1335 if (!eventToPixelCoordinates(ev.windowID, copy))
1336 return true;
1337 removeLocalScaling(copy.tfinger.dx, copy.tfinger.dy);
1338 removeLocalScaling(copy.tfinger.x, copy.tfinger.y);
1339 applyMonitorOffset(copy.tfinger.windowID, copy.tfinger.x, copy.tfinger.y);
1340 return SdlTouch::handleEvent(this, copy.tfinger);
1341}
1342
1343void SdlContext::addOrUpdateDisplay(SDL_DisplayID id)
1344{
1345 auto monitor = SdlWindow::query(id, false);
1346 _displays.emplace(id, monitor);
1347
1348 /* Update actual display rectangles:
1349 *
1350 * 1. Get logical display bounds
1351 * 2. Use already known pixel width and height
1352 * 3. Iterate over each display and update the x and y offsets by adding all monitor
1353 * widths/heights from the primary
1354 */
1355 _offsets.clear();
1356 for (auto& entry : _displays)
1357 {
1358 SDL_Rect bounds{};
1359 std::ignore = SDL_GetDisplayBounds(entry.first, &bounds);
1360
1361 SDL_Rect pixel{};
1362 pixel.w = entry.second.width;
1363 pixel.h = entry.second.height;
1364 _offsets.emplace(entry.first, std::pair{ bounds, pixel });
1365 }
1366
1367 /* 1. Find primary and update all neighbors
1368 * 2. For each neighbor update all neighbors
1369 * 3. repeat until all displays updated.
1370 */
1371 const auto primary = SDL_GetPrimaryDisplay();
1372 std::vector<SDL_DisplayID> handled;
1373 handled.push_back(primary);
1374
1375 auto neighbors = updateDisplayOffsetsForNeighbours(primary);
1376 while (!neighbors.empty())
1377 {
1378 auto neighbor = neighbors.front();
1379 neighbors.erase(neighbors.begin());
1380
1381 if (std::find(handled.begin(), handled.end(), neighbor) != handled.end())
1382 continue;
1383 handled.push_back(neighbor);
1384
1385 auto next = updateDisplayOffsetsForNeighbours(neighbor, handled);
1386 neighbors.insert(neighbors.end(), next.begin(), next.end());
1387 }
1388 updateMonitorDataFromOffsets();
1389}
1390
1391void SdlContext::deleteDisplay(SDL_DisplayID id)
1392{
1393 _displays.erase(id);
1394}
1395
1396bool SdlContext::eventToPixelCoordinates(SDL_WindowID id, SDL_Event& ev)
1397{
1398 auto w = getWindowForId(id);
1399 if (!w)
1400 return false;
1401
1402 /* Ignore errors here, sometimes SDL has no renderer */
1403 auto renderer = w->renderer();
1404 if (!renderer)
1405 return true;
1406 return SDL_ConvertEventToRenderCoordinates(renderer, &ev);
1407}
1408
1409SDL_FPoint SdlContext::applyLocalScaling(const SDL_FPoint& val) const
1410{
1411 if (!useLocalScale())
1412 return val;
1413
1414 auto rval = val;
1415 rval.x *= _localScale.x;
1416 rval.y *= _localScale.y;
1417 return rval;
1418}
1419
1420void SdlContext::removeLocalScaling(float& x, float& y) const
1421{
1422 if (!useLocalScale())
1423 return;
1424 x /= _localScale.x;
1425 y /= _localScale.y;
1426}
1427
1428SDL_FPoint SdlContext::screenToPixel(SDL_WindowID id, const SDL_FPoint& pos)
1429{
1430 auto w = getWindowForId(id);
1431 if (!w)
1432 {
1433 /* RAIL windows are server-authoritative 1:1 (no local scaling) and not in _windows. */
1434 if (_rail.enabled() && _rail.ownsWindow(id))
1435 return pos;
1436 return {};
1437 }
1438
1439 /* Ignore errors here, sometimes SDL has no renderer */
1440 auto renderer = w->renderer();
1441 if (!renderer)
1442 return pos;
1443
1444 SDL_FPoint rpos{};
1445 if (!SDL_RenderCoordinatesFromWindow(renderer, pos.x, pos.y, &rpos.x, &rpos.y))
1446 return {};
1447 removeLocalScaling(rpos.x, rpos.y);
1448 return rpos;
1449}
1450
1451SDL_FPoint SdlContext::pixelToScreen(SDL_WindowID id, const SDL_FPoint& pos)
1452{
1453 auto w = getWindowForId(id);
1454 if (!w)
1455 {
1456 if (_rail.enabled() && _rail.ownsWindow(id))
1457 return pos;
1458 return {};
1459 }
1460
1461 /* Ignore errors here, sometimes SDL has no renderer */
1462 auto renderer = w->renderer();
1463 if (!renderer)
1464 return pos;
1465
1466 SDL_FPoint rpos{};
1467 if (!SDL_RenderCoordinatesToWindow(renderer, pos.x, pos.y, &rpos.x, &rpos.y))
1468 return {};
1469 return applyLocalScaling(rpos);
1470}
1471
1472SDL_FRect SdlContext::pixelToScreen(SDL_WindowID id, const SDL_FRect& pos, bool round)
1473{
1474 const auto fpos = pixelToScreen(id, SDL_FPoint{ pos.x, pos.y });
1475 const auto size = pixelToScreen(id, SDL_FPoint{ pos.w, pos.h });
1476 SDL_FRect r{ fpos.x, fpos.y, size.x, size.y };
1477 if (round)
1478 {
1479 r.w = std::ceil(r.w);
1480 r.h = std::ceil(r.h);
1481 r.x = std::floor(r.x);
1482 r.y = std::floor(r.y);
1483 }
1484 return r;
1485}
1486
1487bool SdlContext::handleEvent(const SDL_Event& ev)
1488{
1489 if ((ev.type >= SDL_EVENT_DISPLAY_FIRST) && (ev.type <= SDL_EVENT_DISPLAY_LAST))
1490 {
1491 const auto& dev = ev.display;
1492 return handleEvent(dev);
1493 }
1494 if ((ev.type >= SDL_EVENT_WINDOW_FIRST) && (ev.type <= SDL_EVENT_WINDOW_LAST))
1495 {
1496 const auto& wev = ev.window;
1497 return handleEvent(wev);
1498 }
1499 switch (ev.type)
1500 {
1501 case SDL_EVENT_RENDER_TARGETS_RESET:
1502 case SDL_EVENT_RENDER_DEVICE_RESET:
1503 case SDL_EVENT_WILL_ENTER_FOREGROUND:
1504 return redraw();
1505 default:
1506 break;
1507 }
1508
1509 if (!isConnected())
1510 return true;
1511
1512 switch (ev.type)
1513 {
1514 case SDL_EVENT_FINGER_DOWN:
1515 case SDL_EVENT_FINGER_UP:
1516 case SDL_EVENT_FINGER_MOTION:
1517 {
1518 const auto& cev = ev.tfinger;
1519 return handleEvent(cev);
1520 }
1521 case SDL_EVENT_MOUSE_MOTION:
1522
1523 {
1524 const auto& cev = ev.motion;
1525 return handleEvent(cev);
1526 }
1527 case SDL_EVENT_MOUSE_BUTTON_DOWN:
1528 case SDL_EVENT_MOUSE_BUTTON_UP:
1529 {
1530 const auto& cev = ev.button;
1531 return handleEvent(cev);
1532 }
1533 case SDL_EVENT_MOUSE_WHEEL:
1534 {
1535 const auto& cev = ev.wheel;
1536 return handleEvent(cev);
1537 }
1538 case SDL_EVENT_CLIPBOARD_UPDATE:
1539 {
1540 const auto& cev = ev.clipboard;
1541 return getClipboardChannelContext().handleEvent(cev);
1542 }
1543 case SDL_EVENT_KEY_DOWN:
1544 case SDL_EVENT_KEY_UP:
1545 {
1546 const auto& cev = ev.key;
1547 return getInputChannelContext().handleEvent(cev);
1548 }
1549 default:
1550 return true;
1551 }
1552}
1553
1554COMMAND_LINE_ARGUMENT_A* SdlContext::args()
1555{
1556 return _args.data();
1557}
1558
1559size_t SdlContext::argsCount() const
1560{
1561 if (_args.size() <= 1)
1562 return 0;
1563 return _args.size() - 1;
1564}
1565
1566int SdlContext::argumentHandler(const COMMAND_LINE_ARGUMENT_A* arg, void* custom)
1567{
1568 auto sdl = static_cast<SdlContext*>(custom);
1569 if (!sdl)
1570 return -1;
1571
1572 if (arg->Name)
1573 {
1574 if (strcmp(arg->Name, sdl_allow_screensaver) == 0)
1575 {
1576 if (arg->Value != nullptr)
1577 {
1578 if (!SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"))
1579 {
1580 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
1581 "SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER) failed with %s",
1582 SDL_GetError());
1583 return -2;
1584 }
1585 }
1586 }
1587 }
1588 return 0;
1589}
1590
1591CriticalSection& SdlContext::lock()
1592{
1593 return _critical;
1594}
1595
1596std::vector<rdpPointer*>& SdlContext::pointers()
1597{
1598 return _valid_pointers;
1599}
1600
1601bool SdlContext::contains(const rdpPointer* ptr) const
1602{
1603 for (const auto& cur : _valid_pointers)
1604 {
1605 if (cur == ptr)
1606 return true;
1607 }
1608 return false;
1609}
1610
1611bool SdlContext::credentialsRead() const
1612{
1613 return _credentialsRead;
1614}
1615
1616void SdlContext::setCredentialsRead()
1617{
1618 _credentialsRead = true;
1619}
1620
1621bool SdlContext::resizeToScale(SdlWindow* window)
1622{
1623 if (freerdp_settings_get_bool(context()->settings, FreeRDP_SmartSizing))
1624 return true;
1625 if (!useLocalScale())
1626 return true;
1627 if (!window)
1628 return false;
1629 return window->resizeToScale();
1630}
1631
1632bool SdlContext::useLocalScale() const
1633{
1634 const auto ssize = freerdp_settings_get_bool(context()->settings, FreeRDP_SmartSizing);
1635 if (ssize)
1636 return true;
1637 const auto dynResize =
1638 freerdp_settings_get_bool(context()->settings, FreeRDP_DynamicResolutionUpdate);
1639 const auto fs = freerdp_settings_get_bool(context()->settings, FreeRDP_Fullscreen);
1640 const auto multimon = freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon);
1641 return !dynResize && !fs && !multimon;
1642}
1643
1644bool SdlContext::drawToWindows(const std::vector<SDL_Rect>& rects)
1645{
1646 /* RAIL damage is per-window (_gfxDamage), not in the rects queue: repaint every tick. */
1647 if (_rail.enabled())
1648 {
1649 for (auto& window : _windows)
1650 {
1651 if ((SDL_GetWindowFlags(window.second.window()) & SDL_WINDOW_HIDDEN) == 0)
1652 SDL_HideWindow(window.second.window());
1653 }
1654
1655 std::unique_lock lock(_critical);
1656 _rail.paint(_primary.get(), pixelFormat(), rects);
1657 return true;
1658 }
1659
1660 /* Non-RAIL mode (e.g. Non-Monitored Desktop during UAC / Consent UI): show the desktop window.
1661 */
1662 bool firstShow = false;
1663 for (auto& window : _windows)
1664 {
1665 if (SDL_GetWindowFlags(window.second.window()) & SDL_WINDOW_HIDDEN)
1666 {
1667 SDL_ShowWindow(window.second.window());
1668 SDL_RaiseWindow(window.second.window());
1669 firstShow = true;
1670 }
1671 }
1672
1673 if (rects.empty() && !firstShow)
1674 return true;
1675
1676 std::vector<SDL_Rect> drawRects = rects;
1677 {
1678 std::unique_lock lock(_critical);
1679 if ((drawRects.empty() || firstShow) && _primary)
1680 drawRects = { { 0, 0, _primary->w, _primary->h } };
1681 }
1682
1683 for (auto& window : _windows)
1684 {
1685 if (!drawToWindow(window.second, drawRects))
1686 return false;
1687 }
1688
1689 return true;
1690}
1691
1692BOOL SdlContext::desktopResize(rdpContext* context)
1693{
1694 rdpGdi* gdi = nullptr;
1695 rdpSettings* settings = nullptr;
1696 auto sdl = get_context(context);
1697
1698 WINPR_ASSERT(sdl);
1699 WINPR_ASSERT(context);
1700
1701 settings = context->settings;
1702 WINPR_ASSERT(settings);
1703
1704 std::unique_lock lock(sdl->_critical);
1705 gdi = context->gdi;
1706 if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
1707 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
1708 return FALSE;
1709 return sdl->createPrimary();
1710}
1711
1712/* This function is called to output a System BEEP */
1713BOOL SdlContext::playSound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound)
1714{
1715 /* TODO: Implement */
1716 WINPR_UNUSED(context);
1717 WINPR_UNUSED(play_sound);
1718 return TRUE;
1719}
1720
1721/* This function is called whenever a new frame starts.
1722 * It can be used to reset invalidated areas. */
1723BOOL SdlContext::beginPaint(rdpContext* context)
1724{
1725 auto gdi = context->gdi;
1726 WINPR_ASSERT(gdi);
1727 WINPR_ASSERT(gdi->primary);
1728
1729 HGDI_DC hdc = gdi->primary->hdc;
1730 WINPR_ASSERT(hdc);
1731 if (!hdc->hwnd)
1732 return TRUE;
1733
1734 HGDI_WND hwnd = hdc->hwnd;
1735 WINPR_ASSERT(hwnd->invalid);
1736 hwnd->invalid->null = TRUE;
1737 hwnd->ninvalid = 0;
1738
1739 return TRUE;
1740}
1741
1742bool SdlContext::redraw(bool suppress) const
1743{
1744 if (!_connected)
1745 return true;
1746
1747 /* In RAIL mode, hiding the desktop window must not suppress server output. */
1748 if (suppress && _rail.enabled())
1749 return true;
1750
1751 auto gdi = context()->gdi;
1752 WINPR_ASSERT(gdi);
1753 return gdi_send_suppress_output(gdi, suppress);
1754}
1755
1756void SdlContext::setConnected(bool val)
1757{
1758 _connected = val;
1759}
1760
1761bool SdlContext::isConnected() const
1762{
1763 return _connected;
1764}
1765
1766rdpContext* SdlContext::context() const
1767{
1768 WINPR_ASSERT(_context);
1769 return _context;
1770}
1771
1772rdpClientContext* SdlContext::common() const
1773{
1774 return reinterpret_cast<rdpClientContext*>(context());
1775}
1776
1777bool SdlContext::setCursor(CursorType type)
1778{
1779 _cursorType = type;
1780 return restoreCursor();
1781}
1782
1783bool SdlContext::setCursor(const rdpPointer* cursor)
1784{
1785 std::unique_lock lock(_critical);
1786 if (!contains(cursor))
1787 return true;
1788
1789 _cursor = { sdl_Pointer_Copy(cursor), sdl_PointerFreeCopyAll };
1790 return setCursor(CURSOR_IMAGE);
1791}
1792
1793rdpPointer* SdlContext::cursor() const
1794{
1795 return _cursor.get();
1796}
1797
1798bool SdlContext::restoreCursor()
1799{
1800 WLog_Print(getWLog(), WLOG_TRACE, "restore cursor: %d", _cursorType);
1801 switch (_cursorType)
1802 {
1803 case CURSOR_NULL:
1804 if (!SDL_HideCursor())
1805 {
1806 WLog_Print(getWLog(), WLOG_ERROR, "SDL_HideCursor failed");
1807 return false;
1808 }
1809
1810 setHasCursor(false);
1811 return true;
1812
1813 case CURSOR_DEFAULT:
1814 {
1815 auto def = SDL_GetDefaultCursor();
1816 if (!SDL_SetCursor(def))
1817 {
1818 WLog_Print(getWLog(), WLOG_ERROR, "SDL_SetCursor(default=%p) failed",
1819 static_cast<void*>(def));
1820 return false;
1821 }
1822 if (!SDL_ShowCursor())
1823 {
1824 WLog_Print(getWLog(), WLOG_ERROR, "SDL_ShowCursor failed");
1825 return false;
1826 }
1827 setHasCursor(true);
1828 return true;
1829 }
1830 case CURSOR_IMAGE:
1831 setHasCursor(true);
1832 return sdl_Pointer_Set_Process(this);
1833 default:
1834 WLog_Print(getWLog(), WLOG_ERROR, "Unknown cursorType %s",
1835 sdl::utils::toString(_cursorType).c_str());
1836 return false;
1837 }
1838}
1839
1840void SdlContext::setMonitorIds(const std::vector<SDL_DisplayID>& ids)
1841{
1842 _monitorIds.clear();
1843 for (auto id : ids)
1844 {
1845 _monitorIds.push_back(id);
1846 }
1847}
1848
1849const std::vector<SDL_DisplayID>& SdlContext::monitorIds() const
1850{
1851 return _monitorIds;
1852}
1853
1854int64_t SdlContext::monitorId(uint32_t index) const
1855{
1856 if (index >= _monitorIds.size())
1857 {
1858 return -1;
1859 }
1860 return _monitorIds.at(index);
1861}
1862
1863void SdlContext::push(std::vector<SDL_Rect>&& rects)
1864{
1865 std::unique_lock lock(_queue_mux);
1866 _queue.emplace(std::move(rects));
1867}
1868
1869std::vector<SDL_Rect> SdlContext::pop()
1870{
1871 std::unique_lock lock(_queue_mux);
1872 if (_queue.empty())
1873 {
1874 return {};
1875 }
1876 auto val = std::move(_queue.front());
1877 _queue.pop();
1878 return val;
1879}
1880
1881bool SdlContext::setFullscreen(bool enter, bool forceOriginalDisplay)
1882{
1883 for (const auto& window : _windows)
1884 {
1885 if (!sdl_push_user_event(SDL_EVENT_USER_WINDOW_FULLSCREEN, &window.second, enter,
1886 forceOriginalDisplay))
1887 return false;
1888 }
1889 _fullscreen = enter;
1890 return true;
1891}
1892
1893bool SdlContext::setMinimized()
1894{
1895 return sdl_push_user_event(SDL_EVENT_USER_WINDOW_MINIMIZE);
1896}
1897
1898bool SdlContext::grabMouse() const
1899{
1900 return _grabMouse;
1901}
1902
1903bool SdlContext::toggleGrabMouse()
1904{
1905 return setGrabMouse(!grabMouse());
1906}
1907
1908bool SdlContext::setGrabMouse(bool enter)
1909{
1910 _grabMouse = enter;
1911 return true;
1912}
1913
1914bool SdlContext::grabKeyboard() const
1915{
1916 return _grabKeyboard;
1917}
1918
1919bool SdlContext::toggleGrabKeyboard()
1920{
1921 return setGrabKeyboard(!grabKeyboard());
1922}
1923
1924bool SdlContext::setGrabKeyboard(bool enter)
1925{
1926 _grabKeyboard = enter;
1927 return true;
1928}
1929
1930bool SdlContext::setResizeable(bool enable)
1931{
1932 const auto settings = context()->settings;
1933 const bool dyn = freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate);
1934 const bool smart = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing);
1935 bool use = (dyn && enable) || smart;
1936
1937 for (const auto& window : _windows)
1938 {
1939 if (!sdl_push_user_event(SDL_EVENT_USER_WINDOW_RESIZEABLE, &window.second, use))
1940 return false;
1941 }
1942 _resizeable = use;
1943
1944 return true;
1945}
1946
1947bool SdlContext::resizeable() const
1948{
1949 return _resizeable;
1950}
1951
1952bool SdlContext::toggleResizeable()
1953{
1954 return setResizeable(!resizeable());
1955}
1956
1957bool SdlContext::fullscreen() const
1958{
1959 return _fullscreen;
1960}
1961
1962bool SdlContext::toggleFullscreen()
1963{
1964 return setFullscreen(!fullscreen());
1965}
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.