[client,sdl] add connection dialog

This commit is contained in:
Armin Novak 2023-12-15 15:55:27 +01:00 committed by akallabeth
parent 49936fc529
commit e1de32f9ab
11 changed files with 572 additions and 16 deletions

View File

@ -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

View File

@ -0,0 +1,363 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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 <assert.h>
#include <thread>
#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<const SDL_KeyboardEvent&>(event);
update(_renderer);
return windowID == ev.windowID;
}
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
{
auto ev = reinterpret_cast<const SDL_MouseButtonEvent&>(event);
update(_renderer);
return windowID == ev.windowID;
}
break;
case SDL_MOUSEWHEEL:
{
auto ev = reinterpret_cast<const SDL_MouseWheelEvent&>(event);
update(_renderer);
return windowID == ev.windowID;
}
break;
case SDL_FINGERUP:
case SDL_FINGERDOWN:
{
auto ev = reinterpret_cast<const SDL_TouchFingerEvent&>(event);
update(_renderer);
return windowID == ev.windowID;
}
case SDL_WINDOWEVENT:
{
auto ev = reinterpret_cast<const SDL_WindowEvent&>(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<int> buttonids = { 1 };
const std::vector<std::string> buttonlabels = { "cancel" };
_buttons.populate(_renderer, buttonlabels, buttonids, static_cast<Sint32>(total_height),
static_cast<Sint32>(widget_width / 2), static_cast<Sint32>(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<SDLConnectionDialog*>(pvthis);
ths->hide();
ths->_running = false;
return 0;
}

View File

@ -0,0 +1,97 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* 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 <memory>
#include <mutex>
#include <string>
#include <vector>
#include <SDL.h>
#include <freerdp/freerdp.h>
#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<SdlWidget> _list;
SdlButtonList _buttons;
};

View File

@ -26,6 +26,7 @@
#include <SDL.h>
#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)
{

View File

@ -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);

View File

@ -16,8 +16,6 @@ SdlInputWidgetList::SdlInputWidgetList(const std::string& title,
const std::vector<int> buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL };
const std::vector<std::string> 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)

View File

@ -5,8 +5,6 @@ static const Uint32 vpadding = 5;
SdlSelectList::SdlSelectList(const std::string& title, const std::vector<std::string>& 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()

View File

@ -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<CriticalSection> 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<CriticalSection> 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;

View File

@ -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<SDL_Surface, decltype(&SDL_FreeSurface)>;
using SDLPixelFormatPtr = std::unique_ptr<SDL_PixelFormat, decltype(&SDL_FreeFormat)>;
@ -76,6 +77,8 @@ class SdlContext
Uint32 sdl_pixel_format = 0;
std::unique_ptr<SDLConnectionDialog> connection_dialog;
public:
BOOL update_resizeable(BOOL enable);
BOOL update_fullscreen(BOOL enter);

View File

@ -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:

View File

@ -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,