diff --git a/client/SDL/dialogs/CMakeLists.txt b/client/SDL/dialogs/CMakeLists.txt index a7e9efd85..12bf91018 100644 --- a/client/SDL/dialogs/CMakeLists.txt +++ b/client/SDL/dialogs/CMakeLists.txt @@ -15,6 +15,8 @@ set(SRCS sdl_select.cpp sdl_selectlist.hpp sdl_selectlist.cpp + sdl_connection_dialog.cpp + sdl_connection_dialog.hpp ) set(LIBS diff --git a/client/SDL/dialogs/sdl_connection_dialog.cpp b/client/SDL/dialogs/sdl_connection_dialog.cpp new file mode 100644 index 000000000..0be787eff --- /dev/null +++ b/client/SDL/dialogs/sdl_connection_dialog.cpp @@ -0,0 +1,363 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include + +#include "sdl_connection_dialog.hpp" +#include "../sdl_utils.hpp" +#include "../sdl_freerdp.hpp" + +static const SDL_Color backgroundcolor = { 0x38, 0x36, 0x35, 0xff }; +static const SDL_Color textcolor = { 0xd1, 0xcf, 0xcd, 0xff }; + +static const Uint32 hpadding = 10; +static const Uint32 vpadding = 5; + +SDLConnectionDialog::SDLConnectionDialog(rdpContext* context) + : _context(context), _window(nullptr), _renderer(nullptr) +{ + hide(); +} + +SDLConnectionDialog::~SDLConnectionDialog() +{ + resetTimer(); + destroyWindow(); +} + +bool SDLConnectionDialog::visible() const +{ + return _window && _renderer; +} + +bool SDLConnectionDialog::setTitle(const char* fmt, ...) +{ + std::lock_guard lock(_mux); + va_list ap; + va_start(ap, fmt); + _title = print(fmt, ap); + va_end(ap); + + return show(MSG_NONE); +} + +bool SDLConnectionDialog::showInfo(const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + auto rc = show(MSG_INFO, fmt, ap); + va_end(ap); + return rc; +} + +bool SDLConnectionDialog::showWarn(const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + auto rc = show(MSG_WARN, fmt, ap); + va_end(ap); + return rc; +} + +bool SDLConnectionDialog::showError(const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + auto rc = show(MSG_ERROR, fmt, ap); + va_end(ap); + return setTimer(); +} + +bool SDLConnectionDialog::hide() +{ + std::lock_guard lock(_mux); + return show(MSG_DISCARD); +} + +bool SDLConnectionDialog::running() const +{ + std::lock_guard lock(_mux); + return _running; +} + +bool SDLConnectionDialog::update() +{ + std::lock_guard lock(_mux); + switch (_type) + { + case MSG_INFO: + case MSG_WARN: + case MSG_ERROR: + createWindow(); + break; + case MSG_DISCARD: + resetTimer(); + destroyWindow(); + break; + default: + if (_window) + { + SDL_SetWindowTitle(_window, _title.c_str()); + } + break; + } + _type = MSG_NONE; + return true; +} + +bool SDLConnectionDialog::setModal() +{ + if (_window) + { + auto sdl = get_context(_context); + if (sdl->windows.empty()) + return true; + + auto parent = sdl->windows.front().window; + SDL_SetWindowModalFor(_window, parent); + SDL_RaiseWindow(_window); + } + return true; +} + +bool SDLConnectionDialog::clearWindow(SDL_Renderer* renderer) +{ + assert(renderer); + + const int drc = SDL_SetRenderDrawColor(renderer, backgroundcolor.r, backgroundcolor.g, + backgroundcolor.b, backgroundcolor.a); + if (widget_log_error(drc, "SDL_SetRenderDrawColor")) + return false; + + const int rcls = SDL_RenderClear(renderer); + return !widget_log_error(rcls, "SDL_RenderClear"); +} + +bool SDLConnectionDialog::update(SDL_Renderer* renderer) +{ + if (!renderer) + return false; + + if (!clearWindow(renderer)) + return false; + + for (auto& btn : _list) + { + if (!btn.update_text(renderer, _msg, textcolor)) + return false; + } + + if (!_buttons.update(renderer)) + return false; + + SDL_RenderPresent(renderer); + return true; +} + +bool SDLConnectionDialog::wait(bool ignoreRdpContext) +{ + while (running()) + { + if (!ignoreRdpContext) + { + if (freerdp_shall_disconnect_context(_context)) + return false; + } + std::this_thread::yield(); + } + return true; +} + +bool SDLConnectionDialog::handle(const SDL_Event& event) +{ + Uint32 windowID = 0; + if (_window) + { + windowID = SDL_GetWindowID(_window); + } + + switch (event.type) + { + case SDL_USEREVENT_RETRY_DIALOG: + return update(); + case SDL_QUIT: + resetTimer(); + destroyWindow(); + return false; + case SDL_KEYDOWN: + case SDL_KEYUP: + { + auto ev = reinterpret_cast(event); + update(_renderer); + return windowID == ev.windowID; + } + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + auto ev = reinterpret_cast(event); + update(_renderer); + return windowID == ev.windowID; + } + break; + case SDL_MOUSEWHEEL: + { + auto ev = reinterpret_cast(event); + update(_renderer); + return windowID == ev.windowID; + } + break; + case SDL_FINGERUP: + case SDL_FINGERDOWN: + { + auto ev = reinterpret_cast(event); + update(_renderer); + return windowID == ev.windowID; + } + case SDL_WINDOWEVENT: + { + auto ev = reinterpret_cast(event); + switch (ev.event) + { + case SDL_WINDOWEVENT_CLOSE: + if (windowID == ev.windowID) + { + resetTimer(); + destroyWindow(); + } + break; + default: + update(_renderer); + setModal(); + break; + } + + return windowID == ev.windowID; + } + default: + return false; + } +} + +bool SDLConnectionDialog::createWindow() +{ + destroyWindow(); + + const size_t widget_height = 50; + const size_t widget_width = 600; + const size_t total_height = 400; + + _window = SDL_CreateWindow(_title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + widget_width, total_height, 0); + if (_window == nullptr) + { + widget_log_error(-1, "SDL_CreateWindow"); + return false; + } + setModal(); + + _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED); + if (_renderer == nullptr) + { + widget_log_error(-1, "SDL_CreateRenderer"); + return false; + } + + SDL_Rect rect = { 0, 0, widget_width, widget_height }; + _list.push_back(SdlWidget(_renderer, rect, false)); + rect.y += widget_height + vpadding; + + const std::vector buttonids = { 1 }; + const std::vector buttonlabels = { "cancel" }; + _buttons.populate(_renderer, buttonlabels, buttonids, static_cast(total_height), + static_cast(widget_width / 2), static_cast(widget_height)); + + SDL_ShowWindow(_window); + SDL_RaiseWindow(_window); + + return true; +} + +void SDLConnectionDialog::destroyWindow() +{ + _buttons.clear(); + _list.clear(); + SDL_DestroyRenderer(_renderer); + SDL_DestroyWindow(_window); + _renderer = nullptr; + _window = nullptr; +} + +bool SDLConnectionDialog::show(MsgType type, const char* fmt, va_list ap) +{ + std::lock_guard lock(_mux); + _msg = print(fmt, ap); + return show(type); +} + +bool SDLConnectionDialog::show(MsgType type) +{ + _type = type; + return sdl_push_user_event(SDL_USEREVENT_RETRY_DIALOG); +} + +std::string SDLConnectionDialog::print(const char* fmt, va_list ap) +{ + int size = -1; + std::string res; + + do + { + res.resize(128); + if (size > 0) + res.resize(size); + + va_list copy; + va_copy(copy, ap); + size = vsnprintf(res.data(), res.size(), fmt, copy); + va_end(copy); + + } while ((size > 0) && (size > res.size())); + + return res; +} + +bool SDLConnectionDialog::setTimer(Uint32 timeoutMS) +{ + std::lock_guard lock(_mux); + resetTimer(); + + _timer = SDL_AddTimer(timeoutMS, &SDLConnectionDialog::timeout, this); + _running = true; + return true; +} + +void SDLConnectionDialog::resetTimer() +{ + if (_running) + SDL_RemoveTimer(_timer); + _running = false; +} + +Uint32 SDLConnectionDialog::timeout(Uint32 intervalMS, void* pvthis) +{ + auto ths = static_cast(pvthis); + ths->hide(); + ths->_running = false; + return 0; +} diff --git a/client/SDL/dialogs/sdl_connection_dialog.hpp b/client/SDL/dialogs/sdl_connection_dialog.hpp new file mode 100644 index 000000000..1ad90a585 --- /dev/null +++ b/client/SDL/dialogs/sdl_connection_dialog.hpp @@ -0,0 +1,97 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +#include + +#include "sdl_widget.hpp" +#include "sdl_buttons.hpp" + +class SDLConnectionDialog +{ + public: + SDLConnectionDialog(rdpContext* context); + virtual ~SDLConnectionDialog(); + + bool visible() const; + + bool setTitle(const char* fmt, ...); + bool showInfo(const char* fmt, ...); + bool showWarn(const char* fmt, ...); + bool showError(const char* fmt, ...); + bool hide(); + + bool running() const; + bool wait(bool ignoreRdpContextQuit = false); + + bool handle(const SDL_Event& event); + + private: + enum MsgType + { + MSG_NONE, + MSG_INFO, + MSG_WARN, + MSG_ERROR, + MSG_DISCARD + }; + + private: + bool createWindow(); + void destroyWindow(); + + bool update(); + + bool setModal(); + + bool clearWindow(SDL_Renderer* renderer); + + bool update(SDL_Renderer* renderer); + + bool show(MsgType type, const char* fmt, va_list ap); + bool show(MsgType type); + + std::string print(const char* fmt, va_list ap); + bool setTimer(Uint32 timeoutMS = 15000); + void resetTimer(); + + private: + static Uint32 timeout(Uint32 intervalMS, void* _this); + + private: + rdpContext* _context; + SDL_Window* _window; + SDL_Renderer* _renderer; + mutable std::mutex _mux; + std::string _title; + std::string _msg; + MsgType _type = MSG_NONE; + SDL_TimerID _timer = -1; + bool _running = false; + std::vector _list; + SdlButtonList _buttons; +}; diff --git a/client/SDL/dialogs/sdl_dialogs.cpp b/client/SDL/dialogs/sdl_dialogs.cpp index 45e78b8f3..30e8fad4d 100644 --- a/client/SDL/dialogs/sdl_dialogs.cpp +++ b/client/SDL/dialogs/sdl_dialogs.cpp @@ -26,6 +26,7 @@ #include +#include "../sdl_freerdp.hpp" #include "sdl_dialogs.hpp" #include "sdl_input.hpp" #include "sdl_input_widgets.hpp" @@ -199,6 +200,63 @@ fail: return res; } +SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current, void* userarg) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + WINPR_ASSERT(what); + + auto sdl = get_context(instance->context); + WINPR_ASSERT(sdl->connection_dialog); + + sdl->connection_dialog->setTitle("Retry connection to %s", + freerdp_settings_get_server_name(instance->context->settings)); + + if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0)) + { + sdl->connection_dialog->showError("Unknown module %s, aborting", what); + return -1; + } + + if (current == 0) + { + if (strcmp(what, "arm-transport") == 0) + sdl->connection_dialog->showWarn("[%s] Starting your VM. It may take up to 5 minutes", + what); + } + + auto settings = instance->context->settings; + const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled); + + SDL_Window* window = nullptr; + if (!sdl->windows.empty()) + { + window = sdl->windows.begin()->window; + } + if (!enabled) + { + sdl->connection_dialog->showError( + "Automatic reconnection disabled, terminating. Try to connect again later"); + return -1; + } + + const size_t max = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries); + const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout); + if (current >= max) + { + sdl->connection_dialog->showError( + "[%s] retries exceeded. Your VM failed to start. Try again later or contact your " + "tech support for help if this keeps happening.", + what); + return -1; + } + + sdl->connection_dialog->showInfo("[%s] retry %" PRIuz "/%" PRIuz ", delaying %" PRIuz + "ms before next attempt", + what, current, max, delay); + return delay; +} + BOOL sdl_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory, BOOL isConsentMandatory, size_t length, const WCHAR* wmessage) { diff --git a/client/SDL/dialogs/sdl_dialogs.hpp b/client/SDL/dialogs/sdl_dialogs.hpp index 9092f3962..ae9bbe6a0 100644 --- a/client/SDL/dialogs/sdl_dialogs.hpp +++ b/client/SDL/dialogs/sdl_dialogs.hpp @@ -30,6 +30,8 @@ BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, ch BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count, DWORD* choice, BOOL gateway); +SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current, void* userarg); + DWORD sdl_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port, const char* common_name, const char* subject, const char* issuer, const char* fingerprint, DWORD flags); diff --git a/client/SDL/dialogs/sdl_input_widgets.cpp b/client/SDL/dialogs/sdl_input_widgets.cpp index 7525c051e..0ec81bf3b 100644 --- a/client/SDL/dialogs/sdl_input_widgets.cpp +++ b/client/SDL/dialogs/sdl_input_widgets.cpp @@ -16,8 +16,6 @@ SdlInputWidgetList::SdlInputWidgetList(const std::string& title, const std::vector buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL }; const std::vector buttonlabels = { "accept", "cancel" }; - TTF_Init(); - const size_t widget_width = 300; const size_t widget_heigth = 50; @@ -102,8 +100,6 @@ SdlInputWidgetList::~SdlInputWidgetList() _buttons.clear(); SDL_DestroyRenderer(_renderer); SDL_DestroyWindow(_window); - - TTF_Quit(); } bool SdlInputWidgetList::update(SDL_Renderer* renderer) diff --git a/client/SDL/dialogs/sdl_selectlist.cpp b/client/SDL/dialogs/sdl_selectlist.cpp index 16325a35b..34643dd2c 100644 --- a/client/SDL/dialogs/sdl_selectlist.cpp +++ b/client/SDL/dialogs/sdl_selectlist.cpp @@ -5,8 +5,6 @@ static const Uint32 vpadding = 5; SdlSelectList::SdlSelectList(const std::string& title, const std::vector& labels) : _window(nullptr), _renderer(nullptr) { - TTF_Init(); - const size_t widget_height = 50; const size_t widget_width = 600; @@ -48,8 +46,6 @@ SdlSelectList::~SdlSelectList() _buttons.clear(); SDL_DestroyRenderer(_renderer); SDL_DestroyWindow(_window); - - TTF_Quit(); } int SdlSelectList::run() diff --git a/client/SDL/sdl_freerdp.cpp b/client/SDL/sdl_freerdp.cpp index f793e1271..49ac10196 100644 --- a/client/SDL/sdl_freerdp.cpp +++ b/client/SDL/sdl_freerdp.cpp @@ -598,6 +598,12 @@ static BOOL sdl_pre_connect(freerdp* instance) if (!sdl_wait_for_init(sdl)) return FALSE; + sdl->connection_dialog.reset(new SDLConnectionDialog(instance->context)); + + sdl->connection_dialog->setTitle("Connecting to '%s'", + freerdp_settings_get_server_name(settings)); + sdl->connection_dialog->showInfo("Please wait while the connection is being established"); + if (!sdl_detect_monitors(sdl, &maxWidth, &maxHeight)) return FALSE; @@ -678,6 +684,7 @@ static void sdl_cleanup_sdl(SdlContext* sdl) sdl_destroy_primary(sdl); freerdp_del_signal_cleanup_handler(sdl->context(), sdl_term_handler); + TTF_Quit(); SDL_Quit(); } @@ -777,6 +784,18 @@ static BOOL sdl_wait_create_windows(SdlContext* sdl) } } +static bool shall_abort(SdlContext* sdl) +{ + std::lock_guard lock(sdl->critical); + if (freerdp_shall_disconnect_context(sdl->context())) + { + if (!sdl->connection_dialog) + return true; + return !sdl->connection_dialog->running(); + } + return false; +} + static int sdl_run(SdlContext* sdl) { int rc = -1; @@ -792,7 +811,8 @@ static int sdl_run(SdlContext* sdl) return -1; } - SDL_Init(SDL_INIT_VIDEO); + SDL_Init(SDL_INIT_EVERYTHING); + TTF_Init(); #if SDL_VERSION_ATLEAST(2, 0, 16) SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0"); #endif @@ -804,17 +824,16 @@ static int sdl_run(SdlContext* sdl) sdl->initialized.set(); - while (!freerdp_shall_disconnect_context(sdl->context())) + while (!shall_abort(sdl)) { SDL_Event windowEvent = { 0 }; - while (!freerdp_shall_disconnect_context(sdl->context()) && - SDL_WaitEventTimeout(nullptr, 1000)) + while (!shall_abort(sdl) && SDL_WaitEventTimeout(nullptr, 1000)) { /* Only poll standard SDL events and SDL_USEREVENTS meant to create dialogs. * do not process the dialog return value events here. */ const int prc = SDL_PeepEvents(&windowEvent, 1, SDL_GETEVENT, SDL_FIRSTEVENT, - SDL_USEREVENT_SCARD_DIALOG); + SDL_USEREVENT_RETRY_DIALOG); if (prc < 0) { if (sdl_log_error(prc, sdl->log, "SDL_PeepEvents")) @@ -826,6 +845,14 @@ static int sdl_run(SdlContext* sdl) windowEvent.type); #endif std::lock_guard lock(sdl->critical); + if (sdl->connection_dialog) + { + if (sdl->connection_dialog->handle(windowEvent)) + { + continue; + } + } + switch (windowEvent.type) { case SDL_QUIT: @@ -1078,6 +1105,10 @@ static BOOL sdl_post_connect(freerdp* instance) auto sdl = get_context(context); + // Retry was successful, discard dialog + if (sdl->connection_dialog) + sdl->connection_dialog->hide(); + if (freerdp_settings_get_bool(context->settings, FreeRDP_AuthenticationOnly)) { /* Check +auth-only has a username and password. */ @@ -1130,14 +1161,11 @@ static void sdl_post_disconnect(freerdp* instance) if (!instance->context) return; - auto context = get_context(instance->context); PubSub_UnsubscribeChannelConnected(instance->context->pubSub, sdl_OnChannelConnectedEventHandler); PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub, sdl_OnChannelDisconnectedEventHandler); gdi_free(instance); - /* TODO : Clean up custom stuff */ - WINPR_UNUSED(context); } static void sdl_post_final_disconnect(freerdp* instance) @@ -1147,6 +1175,12 @@ static void sdl_post_final_disconnect(freerdp* instance) if (!instance->context) return; + + auto context = get_context(instance->context); + + if (context->connection_dialog) + context->connection_dialog->wait(true); + context->connection_dialog.reset(); } /* RDP main loop. @@ -1338,6 +1372,7 @@ static BOOL sdl_client_new(freerdp* instance, rdpContext* context) instance->LogonErrorInfo = sdl_logon_error_info; instance->PresentGatewayMessage = sdl_present_gateway_message; instance->ChooseSmartcard = sdl_choose_smartcard; + instance->RetryDialog = sdl_retry_dialog; #ifdef WITH_WEBVIEW instance->GetAccessToken = sdl_webview_get_access_token; diff --git a/client/SDL/sdl_freerdp.hpp b/client/SDL/sdl_freerdp.hpp index 1f5e686c4..1debc75fe 100644 --- a/client/SDL/sdl_freerdp.hpp +++ b/client/SDL/sdl_freerdp.hpp @@ -37,6 +37,7 @@ #include "sdl_kbd.hpp" #include "sdl_utils.hpp" #include "sdl_window.hpp" +#include "dialogs/sdl_connection_dialog.hpp" using SDLSurfacePtr = std::unique_ptr; using SDLPixelFormatPtr = std::unique_ptr; @@ -76,6 +77,8 @@ class SdlContext Uint32 sdl_pixel_format = 0; + std::unique_ptr connection_dialog; + public: BOOL update_resizeable(BOOL enable); BOOL update_fullscreen(BOOL enter); diff --git a/client/SDL/sdl_utils.cpp b/client/SDL/sdl_utils.cpp index a6c8c9ae3..a6a1f7054 100644 --- a/client/SDL/sdl_utils.cpp +++ b/client/SDL/sdl_utils.cpp @@ -102,6 +102,7 @@ const char* sdl_event_type_str(Uint32 type) EV_CASE_STR(SDL_USEREVENT_AUTH_DIALOG); EV_CASE_STR(SDL_USEREVENT_AUTH_RESULT); EV_CASE_STR(SDL_USEREVENT_SCARD_DIALOG); + EV_CASE_STR(SDL_USEREVENT_RETRY_DIALOG); EV_CASE_STR(SDL_USEREVENT_SCARD_RESULT); EV_CASE_STR(SDL_USEREVENT_UPDATE); EV_CASE_STR(SDL_USEREVENT_CREATE_WINDOWS); @@ -180,6 +181,8 @@ BOOL sdl_push_user_event(Uint32 type, ...) event->code = va_arg(ap, Sint32); } break; + case SDL_USEREVENT_RETRY_DIALOG: + break; case SDL_USEREVENT_SCARD_RESULT: case SDL_USEREVENT_SHOW_RESULT: case SDL_USEREVENT_CERT_RESULT: diff --git a/client/SDL/sdl_utils.hpp b/client/SDL/sdl_utils.hpp index c064452f7..60a6a29ba 100644 --- a/client/SDL/sdl_utils.hpp +++ b/client/SDL/sdl_utils.hpp @@ -69,6 +69,7 @@ enum SDL_USEREVENT_SHOW_DIALOG, SDL_USEREVENT_AUTH_DIALOG, SDL_USEREVENT_SCARD_DIALOG, + SDL_USEREVENT_RETRY_DIALOG, SDL_USEREVENT_CERT_RESULT, SDL_USEREVENT_SHOW_RESULT,