Milestone 5: deliver embedded RDP sessions and lifecycle hardening

This commit is contained in:
Keith Smith
2026-03-03 18:59:26 -07:00
parent 230a401386
commit 36006bd4aa
2941 changed files with 724359 additions and 77 deletions

View File

@@ -0,0 +1,72 @@
set(SRCS
sdl_button.hpp
sdl_button.cpp
sdl_buttons.hpp
sdl_buttons.cpp
sdl_dialogs.cpp
sdl_dialogs.hpp
sdl_widget.hpp
sdl_widget.cpp
sdl_input.hpp
sdl_input.cpp
sdl_input_widgets.cpp
sdl_input_widgets.hpp
sdl_select.hpp
sdl_select.cpp
sdl_selectlist.hpp
sdl_selectlist.cpp
sdl_connection_dialog.cpp
sdl_connection_dialog.hpp
)
list(APPEND LIBS sdl2_client_res winpr)
if(NOT WITH_SDL_LINK_SHARED)
list(APPEND LIBS SDL2::SDL2-static)
else()
list(APPEND LIBS SDL2::SDL2)
endif()
macro(find_sdl_component name)
find_package(${name})
if(NOT ${name}_FOUND)
find_package(PkgConfig REQUIRED)
pkg_check_modules(${name} REQUIRED ${name})
if(BUILD_SHARED_LIBS)
list(APPEND LIBS ${${name}_LIBRARIES})
link_directories(${${name}_LIBRARY_DIRS})
include_directories(SYSTEM ${${name}_INCLUDE_DIRS})
else()
list(APPEND LIBS ${${name}_STATIC_LIBRARIES})
link_directories(${${name}_STATIC_LIBRARY_DIRS})
include_directories(SYSTEM ${${name}_STATIC_INCLUDE_DIRS})
endif()
else()
if(WITH_SDL_LINK_SHARED)
list(APPEND LIBS ${name}::${name})
set_target_properties(${name}::${name} PROPERTIES SYSTEM TRUE)
else()
list(APPEND LIBS ${name}::${name}-static)
set_target_properties(${name}::${name}-static PROPERTIES SYSTEM TRUE)
endif()
endif()
endmacro()
find_sdl_component(SDL2_ttf)
option(WITH_SDL_IMAGE_DIALOGS "Build with SDL_image support (recommended)" OFF)
if(WITH_SDL_IMAGE_DIALOGS)
find_sdl_component(SDL2_image)
add_compile_definitions(WITH_SDL_IMAGE_DIALOGS)
endif()
add_subdirectory(res)
add_library(sdl2-dialogs STATIC ${SRCS})
target_link_libraries(sdl2-dialogs PRIVATE ${LIBS})
if(BUILD_TESTING_INTERNAL OR BUILD_TESTING)
# add_subdirectory(test)
endif()

View File

@@ -0,0 +1,30 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP SDL Client
#
# Copyright 2024 Armin Novak <anovak@thincast.com>
# Copyright 2024 Thincast Technologies GmbH
#
# 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.
set(SRCS sdl2_resource_manager.cpp sdl2_resource_manager.hpp)
add_library(sdl2_client_res STATIC ${SRCS})
if(NOT WITH_SDL_LINK_SHARED)
target_link_libraries(sdl2_client_res ${SDL2_STATIC_LIBRARIES})
else()
target_link_libraries(sdl2_client_res ${SDL2_LIBRARIES})
endif()
set_target_properties(sdl2_client_res PROPERTIES POSITION_INDEPENDENT_CODE ON INTERPROCEDURAL_OPTIMIZATION OFF)
target_link_libraries(sdl2_client_res sdl-common-client-res)

View File

@@ -0,0 +1,37 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
*
* 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 "sdl2_resource_manager.hpp"
#include <iostream>
SDL_RWops* SDL2ResourceManager::get(const std::string& type, const std::string& id)
{
if (useCompiledResources())
{
auto d = data(type, id);
if (!d)
return nullptr;
auto s = d->size();
if (s > INT32_MAX)
return nullptr;
return SDL_RWFromConstMem(d->data(), static_cast<int>(s));
}
auto name = filename(type, id);
return SDL_RWFromFile(name.c_str(), "rb");
}

View File

@@ -0,0 +1,38 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
*
* 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 <string>
#include <map>
#include <vector>
#include <SDL.h>
#include <res/sdl_resource_manager.hpp>
class SDL2ResourceManager : public SDLResourceManager
{
public:
SDL2ResourceManager() = delete;
SDL2ResourceManager(const SDL2ResourceManager& other) = delete;
SDL2ResourceManager(const SDL2ResourceManager&& other) = delete;
~SDL2ResourceManager() = delete;
SDL2ResourceManager& operator=(const SDL2ResourceManager& other) = delete;
SDL2ResourceManager& operator=(SDL2ResourceManager&& other) = delete;
static SDL_RWops* get(const std::string& type, const std::string& id);
};

View File

@@ -0,0 +1,71 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client Channels
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
* Copyright 2023 Thincast Technologies GmbH
*
* 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 <cassert>
#include <utility>
#include "sdl_button.hpp"
static const SDL_Color buttonbackgroundcolor = { 0x69, 0x66, 0x63, 0xff };
static const SDL_Color buttonhighlightcolor = { 0xcd, 0xca, 0x35, 0x60 };
static const SDL_Color buttonmouseovercolor = { 0x66, 0xff, 0x66, 0x60 };
static const SDL_Color buttonfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
SdlButton::SdlButton(SDL_Renderer* renderer, std::string label, int id, SDL_Rect rect)
: SdlWidget(renderer, rect, false), _name(std::move(label)), _id(id)
{
assert(renderer);
update_text(renderer, _name, buttonfontcolor, buttonbackgroundcolor);
}
SdlButton::SdlButton(SdlButton&& other) noexcept = default;
SdlButton::~SdlButton() = default;
bool SdlButton::highlight(SDL_Renderer* renderer)
{
assert(renderer);
std::vector<SDL_Color> colors = { buttonbackgroundcolor, buttonhighlightcolor };
if (!fill(renderer, colors))
return false;
return update_text(renderer, _name, buttonfontcolor);
}
bool SdlButton::mouseover(SDL_Renderer* renderer)
{
std::vector<SDL_Color> colors = { buttonbackgroundcolor, buttonmouseovercolor };
if (!fill(renderer, colors))
return false;
return update_text(renderer, _name, buttonfontcolor);
}
bool SdlButton::update(SDL_Renderer* renderer)
{
assert(renderer);
return update_text(renderer, _name, buttonfontcolor, buttonbackgroundcolor);
}
int SdlButton::id() const
{
return _id;
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <string>
#include "sdl_widget.hpp"
class SdlButton : public SdlWidget
{
public:
SdlButton(SDL_Renderer* renderer, std::string label, int id, SDL_Rect rect);
SdlButton(const SdlButton& other) = delete;
SdlButton(SdlButton&& other) noexcept;
~SdlButton() override;
SdlButton& operator=(const SdlButton& other) = delete;
SdlButton& operator=(SdlButton&& other) = delete;
bool highlight(SDL_Renderer* renderer);
bool mouseover(SDL_Renderer* renderer);
bool update(SDL_Renderer* renderer);
[[nodiscard]] int id() const;
private:
std::string _name;
int _id;
};

View File

@@ -0,0 +1,112 @@
#include <cassert>
#include <algorithm>
#include <winpr/cast.h>
#include "sdl_buttons.hpp"
static const Uint32 hpadding = 10;
SdlButtonList::~SdlButtonList() = default;
bool SdlButtonList::populate(SDL_Renderer* renderer, const std::vector<std::string>& labels,
const std::vector<int>& ids, Sint32 total_width, Sint32 offsetY,
Sint32 width, Sint32 height)
{
assert(renderer);
assert(width >= 0);
assert(height >= 0);
assert(labels.size() == ids.size());
_list.clear();
size_t button_width =
ids.size() * (WINPR_ASSERTING_INT_CAST(uint32_t, width) + hpadding) + hpadding;
size_t offsetX =
WINPR_ASSERTING_INT_CAST(uint32_t, total_width) -
std::min<size_t>(WINPR_ASSERTING_INT_CAST(uint32_t, total_width), button_width);
for (size_t x = 0; x < ids.size(); x++)
{
const size_t curOffsetX = offsetX + x * (static_cast<size_t>(width) + hpadding);
const SDL_Rect rect = { static_cast<int>(curOffsetX), offsetY, width, height };
_list.emplace_back(renderer, labels.at(x), ids.at(x), rect);
}
return true;
}
SdlButton* SdlButtonList::get_selected(const SDL_MouseButtonEvent& button)
{
const Sint32 x = button.x;
const Sint32 y = button.y;
return get_selected(x, y);
}
SdlButton* SdlButtonList::get_selected(Sint32 x, Sint32 y)
{
for (auto& btn : _list)
{
auto r = btn.rect();
if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
return &btn;
}
return nullptr;
}
bool SdlButtonList::set_highlight_next(bool reset)
{
if (reset)
_highlighted = nullptr;
else
{
auto next = _highlight_index++;
_highlight_index %= _list.size();
auto& element = _list.at(next);
_highlighted = &element;
}
return true;
}
bool SdlButtonList::set_highlight(size_t index)
{
if (index >= _list.size())
{
_highlighted = nullptr;
return false;
}
auto& element = _list.at(index);
_highlighted = &element;
_highlight_index = ++index % _list.size();
return true;
}
bool SdlButtonList::set_mouseover(Sint32 x, Sint32 y)
{
_mouseover = get_selected(x, y);
return _mouseover != nullptr;
}
void SdlButtonList::clear()
{
_list.clear();
_mouseover = nullptr;
_highlighted = nullptr;
_highlight_index = 0;
}
bool SdlButtonList::update(SDL_Renderer* renderer)
{
assert(renderer);
for (auto& btn : _list)
{
if (!btn.update(renderer))
return false;
}
if (_highlighted)
_highlighted->highlight(renderer);
if (_mouseover)
_mouseover->mouseover(renderer);
return true;
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include <vector>
#include <cstdint>
#include "sdl_button.hpp"
class SdlButtonList
{
public:
SdlButtonList() = default;
virtual ~SdlButtonList();
bool populate(SDL_Renderer* renderer, const std::vector<std::string>& labels,
const std::vector<int>& ids, Sint32 total_width, Sint32 offsetY, Sint32 width,
Sint32 height);
bool update(SDL_Renderer* renderer);
SdlButton* get_selected(const SDL_MouseButtonEvent& button);
SdlButton* get_selected(Sint32 x, Sint32 y);
bool set_highlight_next(bool reset = false);
bool set_highlight(size_t index);
bool set_mouseover(Sint32 x, Sint32 y);
void clear();
SdlButtonList(const SdlButtonList& other) = delete;
SdlButtonList(SdlButtonList&& other) = delete;
SdlButtonList& operator=(const SdlButtonList& other) = delete;
SdlButtonList& operator=(SdlButtonList&& other) = delete;
private:
std::vector<SdlButton> _list;
SdlButton* _highlighted = nullptr;
size_t _highlight_index = 0;
SdlButton* _mouseover = nullptr;
};

View File

@@ -0,0 +1,539 @@
/**
* 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 <cassert>
#include <thread>
#include "sdl_connection_dialog.hpp"
#include "../sdl_utils.hpp"
#include "../sdl_freerdp.hpp"
#include "res/sdl2_resource_manager.hpp"
static const SDL_Color backgroundcolor = { 0x38, 0x36, 0x35, 0xff };
static const SDL_Color textcolor = { 0xd1, 0xcf, 0xcd, 0xff };
static const SDL_Color infocolor = { 0x43, 0xe0, 0x0f, 0x60 };
static const SDL_Color warncolor = { 0xcd, 0xca, 0x35, 0x60 };
static const SDL_Color errorcolor = { 0xf7, 0x22, 0x30, 0x60 };
static const Uint32 vpadding = 5;
static const Uint32 hpadding = 5;
SDLConnectionDialog::SDLConnectionDialog(rdpContext* context) : _context(context)
{
SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);
hide();
}
SDLConnectionDialog::~SDLConnectionDialog()
{
resetTimer();
destroyWindow();
SDL_Quit();
}
bool SDLConnectionDialog::visible() const
{
if (!_window || !_renderer)
return false;
auto flags = SDL_GetWindowFlags(_window);
return (flags & (SDL_WINDOW_HIDDEN | SDL_WINDOW_MINIMIZED)) == 0;
}
bool SDLConnectionDialog::setTitle(const char* fmt, ...)
{
std::scoped_lock 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);
if (!rc)
return rc;
return setTimer();
}
bool SDLConnectionDialog::show()
{
std::scoped_lock lock(_mux);
return show(_type_active);
}
bool SDLConnectionDialog::hide()
{
std::scoped_lock lock(_mux);
return show(MSG_DISCARD);
}
bool SDLConnectionDialog::running() const
{
std::scoped_lock lock(_mux);
return _running;
}
bool SDLConnectionDialog::update()
{
std::scoped_lock lock(_mux);
switch (_type)
{
case MSG_INFO:
case MSG_WARN:
case MSG_ERROR:
_type_active = _type;
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.begin()->second.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)
{
std::scoped_lock lock(_mux);
if (!renderer)
return false;
if (!clearWindow(renderer))
return false;
for (auto& btn : _list)
{
if (!btn.widget.update_text(renderer, _msg, btn.fgcolor, btn.bgcolor))
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:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_KeyboardEvent&>(event);
update(_renderer);
switch (event.key.keysym.sym)
{
case SDLK_RETURN:
case SDLK_RETURN2:
case SDLK_ESCAPE:
case SDLK_KP_ENTER:
if (event.type == SDL_KEYUP)
{
freerdp_abort_connect_context(_context);
sdl_push_quit();
}
break;
case SDLK_TAB:
_buttons.set_highlight_next();
break;
default:
break;
}
return windowID == ev.windowID;
}
return false;
case SDL_MOUSEMOTION:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_MouseMotionEvent&>(event);
_buttons.set_mouseover(event.button.x, event.button.y);
update(_renderer);
return windowID == ev.windowID;
}
return false;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_MouseButtonEvent&>(event);
update(_renderer);
auto button = _buttons.get_selected(event.button);
if (button)
{
if (event.type == SDL_MOUSEBUTTONUP)
{
freerdp_abort_connect_context(_context);
sdl_push_quit();
}
}
return windowID == ev.windowID;
}
return false;
case SDL_MOUSEWHEEL:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_MouseWheelEvent&>(event);
update(_renderer);
return windowID == ev.windowID;
}
return false;
case SDL_FINGERUP:
case SDL_FINGERDOWN:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_TouchFingerEvent&>(event);
update(_renderer);
#if SDL_VERSION_ATLEAST(2, 0, 18)
return windowID == ev.windowID;
#else
return false;
#endif
}
return false;
case SDL_WINDOWEVENT:
{
auto& ev = reinterpret_cast<const SDL_WindowEvent&>(event);
switch (ev.event)
{
case SDL_WINDOWEVENT_CLOSE:
if (windowID == ev.windowID)
{
freerdp_abort_connect_context(_context);
sdl_push_quit();
}
break;
default:
update(_renderer);
setModal();
break;
}
return windowID == ev.windowID;
}
default:
return false;
}
}
bool SDLConnectionDialog::createWindow()
{
destroyWindow();
const int widget_height = 50;
const int widget_width = 600;
const int total_height = 300;
auto flags = WINPR_ASSERTING_INT_CAST(
uint32_t, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS);
auto rc = SDL_CreateWindowAndRenderer(widget_width, total_height, flags, &_window, &_renderer);
if (rc != 0)
{
widget_log_error(rc, "SDL_CreateWindowAndRenderer");
return false;
}
SDL_SetWindowTitle(_window, _title.c_str());
setModal();
SDL_Color res_bgcolor;
switch (_type_active)
{
case MSG_INFO:
res_bgcolor = infocolor;
break;
case MSG_WARN:
res_bgcolor = warncolor;
break;
case MSG_ERROR:
res_bgcolor = errorcolor;
break;
case MSG_DISCARD:
default:
res_bgcolor = backgroundcolor;
break;
}
#if defined(WITH_SDL_IMAGE_DIALOGS)
std::string res_name;
switch (_type_active)
{
case MSG_INFO:
res_name = "icon_info.svg";
break;
case MSG_WARN:
res_name = "icon_warning.svg";
break;
case MSG_ERROR:
res_name = "icon_error.svg";
break;
case MSG_DISCARD:
default:
res_name = "";
break;
}
int height = (total_height - 3ul * vpadding) / 2ul;
SDL_Rect iconRect{ hpadding, vpadding, widget_width / 4ul - 2ul * hpadding, height };
widget_cfg_t icon{ textcolor,
res_bgcolor,
{ _renderer, iconRect,
SDL2ResourceManager::get(SDLResourceManager::typeImages(), res_name) } };
_list.emplace_back(std::move(icon));
iconRect.y += height;
widget_cfg_t logo{ textcolor,
backgroundcolor,
{ _renderer, iconRect,
SDL2ResourceManager::get(SDLResourceManager::typeImages(),
"FreeRDP_Icon.svg") } };
_list.emplace_back(std::move(logo));
SDL_Rect rect = { widget_width / 4ul, vpadding, widget_width * 3ul / 4ul,
total_height - 3ul * vpadding - widget_height };
#else
SDL_Rect rect = { hpadding, vpadding, widget_width - 2ul * hpadding,
total_height - 2ul * vpadding };
#endif
widget_cfg_t w{ textcolor, backgroundcolor, { _renderer, rect, false } };
w.widget.set_wrap(true, widget_width);
_list.emplace_back(std::move(w));
rect.y += widget_height + vpadding;
const std::vector<int> buttonids = { 1 };
const std::vector<std::string> buttonlabels = { "cancel" };
_buttons.populate(_renderer, buttonlabels, buttonids, widget_width,
total_height - widget_height - vpadding,
static_cast<Sint32>(widget_width / 2), static_cast<Sint32>(widget_height));
_buttons.set_highlight(0);
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::scoped_lock 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(WINPR_ASSERTING_INT_CAST(size_t, size));
va_list copy = {};
va_copy(copy, ap);
WINPR_PRAGMA_DIAG_PUSH
WINPR_PRAGMA_DIAG_IGNORED_FORMAT_NONLITERAL
size = vsnprintf(res.data(), res.size(), fmt, copy);
WINPR_PRAGMA_DIAG_POP
va_end(copy);
} while ((size > 0) && (static_cast<size_t>(size) > res.size()));
return res;
}
bool SDLConnectionDialog::setTimer(Uint32 timeoutMS)
{
std::scoped_lock 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([[maybe_unused]] Uint32 intervalMS, void* pvthis)
{
auto self = static_cast<SDLConnectionDialog*>(pvthis);
self->hide();
self->_running = false;
return 0;
}
SDLConnectionDialogHider::SDLConnectionDialogHider(freerdp* instance)
: SDLConnectionDialogHider(get(instance))
{
}
SDLConnectionDialogHider::SDLConnectionDialogHider(rdpContext* context)
: SDLConnectionDialogHider(get(context))
{
}
SDLConnectionDialogHider::SDLConnectionDialogHider(SDLConnectionDialog* dialog) : _dialog(dialog)
{
if (_dialog)
{
_visible = _dialog->visible();
if (_visible)
{
_dialog->hide();
}
}
}
SDLConnectionDialogHider::~SDLConnectionDialogHider()
{
if (_dialog && _visible)
{
_dialog->show();
}
}
SDLConnectionDialog* SDLConnectionDialogHider::get(freerdp* instance)
{
if (!instance)
return nullptr;
return get(instance->context);
}
SDLConnectionDialog* SDLConnectionDialogHider::get(rdpContext* context)
{
auto sdl = get_context(context);
if (!sdl)
return nullptr;
return sdl->connection_dialog.get();
}

View File

@@ -0,0 +1,131 @@
/**
* 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:
explicit SDLConnectionDialog(rdpContext* context);
SDLConnectionDialog(const SDLConnectionDialog& other) = delete;
SDLConnectionDialog(const SDLConnectionDialog&& other) = delete;
virtual ~SDLConnectionDialog();
SDLConnectionDialog& operator=(const SDLConnectionDialog& other) = delete;
SDLConnectionDialog& operator=(SDLConnectionDialog&& other) = delete;
bool visible() const;
bool setTitle(const char* fmt, ...);
bool showInfo(const char* fmt, ...);
bool showWarn(const char* fmt, ...);
bool showError(const char* fmt, ...);
bool show();
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
};
bool createWindow();
void destroyWindow();
bool update();
bool setModal();
static bool clearWindow(SDL_Renderer* renderer);
bool update(SDL_Renderer* renderer);
bool show(MsgType type, const char* fmt, va_list ap);
bool show(MsgType type);
static std::string print(const char* fmt, va_list ap);
bool setTimer(Uint32 timeoutMS = 15000);
void resetTimer();
static Uint32 timeout(Uint32 intervalMS, void* pvthis);
struct widget_cfg_t
{
SDL_Color fgcolor = {};
SDL_Color bgcolor = {};
SdlWidget widget;
};
rdpContext* _context = nullptr;
SDL_Window* _window = nullptr;
SDL_Renderer* _renderer = nullptr;
mutable std::mutex _mux;
std::string _title;
std::string _msg;
MsgType _type = MSG_NONE;
MsgType _type_active = MSG_NONE;
SDL_TimerID _timer = -1;
bool _running = false;
std::vector<widget_cfg_t> _list;
SdlButtonList _buttons;
};
class SDLConnectionDialogHider
{
public:
explicit SDLConnectionDialogHider(freerdp* instance);
explicit SDLConnectionDialogHider(rdpContext* context);
explicit SDLConnectionDialogHider(SDLConnectionDialog* dialog);
SDLConnectionDialogHider(const SDLConnectionDialogHider& other) = delete;
SDLConnectionDialogHider(SDLConnectionDialogHider&& other) = delete;
SDLConnectionDialogHider& operator=(const SDLConnectionDialogHider& other) = delete;
SDLConnectionDialogHider& operator=(SDLConnectionDialogHider&& other) = delete;
~SDLConnectionDialogHider();
private:
SDLConnectionDialog* get(freerdp* instance);
static SDLConnectionDialog* get(rdpContext* context);
SDLConnectionDialog* _dialog = nullptr;
bool _visible = false;
};

View File

@@ -0,0 +1,626 @@
/**
* 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 <vector>
#include <string>
#include <cassert>
#include <freerdp/log.h>
#include <freerdp/utils/smartcardlogon.h>
#include <SDL.h>
#include "../sdl_freerdp.hpp"
#include "sdl_dialogs.hpp"
#include "sdl_input.hpp"
#include "sdl_input_widgets.hpp"
#include "sdl_select.hpp"
#include "sdl_selectlist.hpp"
enum
{
SHOW_DIALOG_ACCEPT_REJECT = 1,
SHOW_DIALOG_TIMED_ACCEPT = 2
};
static const char* type_str_for_flags(UINT32 flags)
{
const char* type = "RDP-Server";
if (flags & VERIFY_CERT_FLAG_GATEWAY)
type = "RDP-Gateway";
if (flags & VERIFY_CERT_FLAG_REDIRECT)
type = "RDP-Redirect";
return type;
}
static BOOL sdl_wait_for_result(rdpContext* context, Uint32 type, SDL_Event* result)
{
const SDL_Event empty = {};
WINPR_ASSERT(context);
WINPR_ASSERT(result);
while (!freerdp_shall_disconnect_context(context))
{
*result = empty;
const int rc = SDL_PeepEvents(result, 1, SDL_GETEVENT, type, type);
if (rc > 0)
return TRUE;
Sleep(1);
}
return FALSE;
}
static int sdl_show_dialog(rdpContext* context, const char* title, const char* message,
Sint32 flags)
{
SDL_Event event = {};
if (!sdl_push_user_event(SDL_USEREVENT_SHOW_DIALOG, title, message, flags))
return 0;
if (!sdl_wait_for_result(context, SDL_USEREVENT_SHOW_RESULT, &event))
return 0;
return event.user.code;
}
BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
rdp_auth_reason reason)
{
SDL_Event event = {};
BOOL res = FALSE;
SDLConnectionDialogHider hider(instance);
const char* target = freerdp_settings_get_server_name(instance->context->settings);
switch (reason)
{
case AUTH_NLA:
break;
case AUTH_TLS:
case AUTH_RDP:
case AUTH_SMARTCARD_PIN: /* in this case password is pin code */
if ((*username) && (*password))
return TRUE;
break;
case GW_AUTH_HTTP:
case GW_AUTH_RDG:
case GW_AUTH_RPC:
target =
freerdp_settings_get_string(instance->context->settings, FreeRDP_GatewayHostname);
break;
default:
break;
}
char* title = nullptr;
size_t titlesize = 0;
winpr_asprintf(&title, &titlesize, "Credentials required for %s", target);
CStringPtr scope(title, free);
char* u = nullptr;
char* d = nullptr;
char* p = nullptr;
assert(username);
assert(domain);
assert(password);
u = *username;
d = *domain;
p = *password;
if (!sdl_push_user_event(SDL_USEREVENT_AUTH_DIALOG, title, u, d, p, reason))
return res;
if (!sdl_wait_for_result(instance->context, SDL_USEREVENT_AUTH_RESULT, &event))
return res;
auto arg = reinterpret_cast<SDL_UserAuthArg*>(event.padding);
res = arg->result > 0;
free(*username);
free(*domain);
free(*password);
*username = arg->user;
*domain = arg->domain;
*password = arg->password;
return res;
}
BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
DWORD* choice, BOOL gateway)
{
BOOL res = FALSE;
WINPR_ASSERT(instance);
WINPR_ASSERT(cert_list);
WINPR_ASSERT(choice);
SDLConnectionDialogHider hider(instance);
std::vector<std::string> strlist;
std::vector<const char*> list;
for (DWORD i = 0; i < count; i++)
{
const SmartcardCertInfo* cert = cert_list[i];
char* reader = ConvertWCharToUtf8Alloc(cert->reader, nullptr);
char* container_name = ConvertWCharToUtf8Alloc(cert->containerName, nullptr);
char* msg = nullptr;
size_t len = 0;
winpr_asprintf(&msg, &len,
"%s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s",
container_name, reader, cert->userHint, cert->domainHint, cert->subject,
cert->issuer, cert->upn);
strlist.emplace_back(msg);
free(msg);
free(reader);
free(container_name);
auto& m = strlist.back();
list.push_back(m.c_str());
}
SDL_Event event = {};
const char* title = "Select a logon smartcard certificate";
if (gateway)
title = "Select a gateway logon smartcard certificate";
if (!sdl_push_user_event(SDL_USEREVENT_SCARD_DIALOG, title, list.data(), count))
return res;
if (!sdl_wait_for_result(instance->context, SDL_USEREVENT_SCARD_RESULT, &event))
return res;
res = (event.user.code >= 0);
*choice = static_cast<DWORD>(event.user.code);
return res;
}
SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current,
[[maybe_unused]] void* userarg)
{
WINPR_ASSERT(instance);
WINPR_ASSERT(instance->context);
WINPR_ASSERT(what);
auto sdl = get_context(instance->context);
auto settings = instance->context->settings;
const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
std::scoped_lock lock(sdl->critical);
if (!sdl->connection_dialog)
return WINPR_ASSERTING_INT_CAST(SSIZE_T, delay);
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);
}
const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
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);
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 + 1, max, delay);
return WINPR_ASSERTING_INT_CAST(SSIZE_T, delay);
}
BOOL sdl_present_gateway_message(freerdp* instance, [[maybe_unused]] UINT32 type,
BOOL isDisplayMandatory, BOOL isConsentMandatory, size_t length,
const WCHAR* wmessage)
{
if (!isDisplayMandatory)
return TRUE;
char* title = nullptr;
size_t len = 0;
winpr_asprintf(&title, &len, "[gateway]");
Sint32 flags = 0;
if (isConsentMandatory)
flags = SHOW_DIALOG_ACCEPT_REJECT;
else if (isDisplayMandatory)
flags = SHOW_DIALOG_TIMED_ACCEPT;
char* message = ConvertWCharNToUtf8Alloc(wmessage, length, nullptr);
SDLConnectionDialogHider hider(instance);
const int rc = sdl_show_dialog(instance->context, title, message, flags);
free(title);
free(message);
return rc > 0;
}
int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
{
int rc = -1;
const char* str_data = freerdp_get_logon_error_info_data(data);
const char* str_type = freerdp_get_logon_error_info_type(type);
if (!instance || !instance->context)
return -1;
/* ignore LOGON_MSG_SESSION_CONTINUE message */
if (type == LOGON_MSG_SESSION_CONTINUE)
return 0;
SDLConnectionDialogHider hider(instance);
char* title = nullptr;
size_t tlen = 0;
winpr_asprintf(&title, &tlen, "[%s] info",
freerdp_settings_get_server_name(instance->context->settings));
char* message = nullptr;
size_t mlen = 0;
winpr_asprintf(&message, &mlen, "Logon Error Info %s [%s]", str_data, str_type);
rc = sdl_show_dialog(instance->context, title, message, SHOW_DIALOG_ACCEPT_REJECT);
free(title);
free(message);
return rc;
}
static DWORD sdl_show_ceritifcate_dialog(rdpContext* context, const char* title,
const char* message)
{
SDLConnectionDialogHider hider(context);
if (!sdl_push_user_event(SDL_USEREVENT_CERT_DIALOG, title, message))
return 0;
SDL_Event event = {};
if (!sdl_wait_for_result(context, SDL_USEREVENT_CERT_RESULT, &event))
return 0;
return static_cast<DWORD>(event.user.code);
}
static char* sdl_pem_cert(const char* pem)
{
rdpCertificate* cert = freerdp_certificate_new_from_pem(pem);
if (!cert)
return nullptr;
char* fp = freerdp_certificate_get_fingerprint(cert);
char* start = freerdp_certificate_get_validity(cert, TRUE);
char* end = freerdp_certificate_get_validity(cert, FALSE);
freerdp_certificate_free(cert);
char* str = nullptr;
size_t slen = 0;
winpr_asprintf(&str, &slen,
"Valid from: %s\n"
"Valid to: %s\n"
"Thumbprint: %s\n",
start, end, fp);
free(fp);
free(start);
free(end);
return str;
}
DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
const char* common_name, const char* subject,
const char* issuer, const char* new_fingerprint,
const char* old_subject, const char* old_issuer,
const char* old_fingerprint, DWORD flags)
{
const char* type = type_str_for_flags(flags);
WINPR_ASSERT(instance);
WINPR_ASSERT(instance->context);
WINPR_ASSERT(instance->context->settings);
SDLConnectionDialogHider hider(instance);
/* Newer versions of FreeRDP allow exposing the whole PEM by setting
* FreeRDP_CertificateCallbackPreferPEM to TRUE
*/
char* new_fp_str = nullptr;
size_t len = 0;
if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
new_fp_str = sdl_pem_cert(new_fingerprint);
else
winpr_asprintf(&new_fp_str, &len, "Thumbprint: %s\n", new_fingerprint);
/* Newer versions of FreeRDP allow exposing the whole PEM by setting
* FreeRDP_CertificateCallbackPreferPEM to TRUE
*/
char* old_fp_str = nullptr;
size_t olen = 0;
if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
old_fp_str = sdl_pem_cert(old_fingerprint);
else
winpr_asprintf(&old_fp_str, &olen, "Thumbprint: %s\n", old_fingerprint);
const char* collission_str = "";
if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1)
{
collission_str =
"A matching entry with legacy SHA1 was found in local known_hosts2 store.\n"
"If you just upgraded from a FreeRDP version before 2.0 this is expected.\n"
"The hashing algorithm has been upgraded from SHA1 to SHA256.\n"
"All manually accepted certificates must be reconfirmed!\n"
"\n";
}
char* title = nullptr;
size_t tlen = 0;
winpr_asprintf(&title, &tlen, "Certificate for %s:%" PRIu16 " (%s) has changed", host, port,
type);
char* message = nullptr;
size_t mlen = 0;
winpr_asprintf(&message, &mlen,
"New Certificate details:\n"
"Common Name: %s\n"
"Subject: %s\n"
"Issuer: %s\n"
"%s\n"
"Old Certificate details:\n"
"Subject: %s\n"
"Issuer: %s\n"
"%s\n"
"%s\n"
"The above X.509 certificate does not match the certificate used for previous "
"connections.\n"
"This may indicate that the certificate has been tampered with.\n"
"Please contact the administrator of the RDP server and clarify.\n",
common_name, subject, issuer, new_fp_str, old_subject, old_issuer, old_fp_str,
collission_str);
const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
free(title);
free(message);
free(new_fp_str);
free(old_fp_str);
return rc;
}
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)
{
const char* type = type_str_for_flags(flags);
/* Newer versions of FreeRDP allow exposing the whole PEM by setting
* FreeRDP_CertificateCallbackPreferPEM to TRUE
*/
char* fp_str = nullptr;
size_t len = 0;
if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
fp_str = sdl_pem_cert(fingerprint);
else
winpr_asprintf(&fp_str, &len, "Thumbprint: %s\n", fingerprint);
char* title = nullptr;
size_t tlen = 0;
winpr_asprintf(&title, &tlen, "New certificate for %s:%" PRIu16 " (%s)", host, port, type);
char* message = nullptr;
size_t mlen = 0;
winpr_asprintf(
&message, &mlen,
"Common Name: %s\n"
"Subject: %s\n"
"Issuer: %s\n"
"%s\n"
"The above X.509 certificate could not be verified, possibly because you do not have\n"
"the CA certificate in your certificate store, or the certificate has expired.\n"
"Please look at the OpenSSL documentation on how to add a private CA to the store.\n",
common_name, subject, issuer, fp_str);
SDLConnectionDialogHider hider(instance);
const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
free(fp_str);
free(title);
free(message);
return rc;
}
BOOL sdl_cert_dialog_show(const char* title, const char* message)
{
int buttonid = -1;
enum
{
BUTTONID_CERT_ACCEPT_PERMANENT = 23,
BUTTONID_CERT_ACCEPT_TEMPORARY = 24,
BUTTONID_CERT_DENY = 25
};
const SDL_MessageBoxButtonData buttons[] = {
{ 0, BUTTONID_CERT_ACCEPT_PERMANENT, "permanent" },
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_CERT_ACCEPT_TEMPORARY, "temporary" },
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_CERT_DENY, "cancel" }
};
const SDL_MessageBoxData data = { SDL_MESSAGEBOX_WARNING, nullptr, title, message,
ARRAYSIZE(buttons), buttons, nullptr };
const int rc = SDL_ShowMessageBox(&data, &buttonid);
Sint32 value = -1;
if (rc < 0)
value = 0;
else
{
switch (buttonid)
{
case BUTTONID_CERT_ACCEPT_PERMANENT:
value = 1;
break;
case BUTTONID_CERT_ACCEPT_TEMPORARY:
value = 2;
break;
default:
value = 0;
break;
}
}
return sdl_push_user_event(SDL_USEREVENT_CERT_RESULT, value);
}
BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags)
{
int buttonid = -1;
enum
{
BUTTONID_SHOW_ACCEPT = 24,
BUTTONID_SHOW_DENY = 25
};
const SDL_MessageBoxButtonData buttons[] = {
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_SHOW_ACCEPT, "accept" },
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_SHOW_DENY, "cancel" }
};
const int button_cnt = (flags & SHOW_DIALOG_ACCEPT_REJECT) ? 2 : 1;
const SDL_MessageBoxData data = {
SDL_MESSAGEBOX_WARNING, nullptr, title, message, button_cnt, buttons, nullptr
};
const int rc = SDL_ShowMessageBox(&data, &buttonid);
Sint32 value = -1;
if (rc < 0)
value = 0;
else
{
switch (buttonid)
{
case BUTTONID_SHOW_ACCEPT:
value = 1;
break;
default:
value = 0;
break;
}
}
return sdl_push_user_event(SDL_USEREVENT_SHOW_RESULT, value);
}
BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args)
{
const std::vector<std::string> auth = { "Username: ", "Domain: ",
"Password: " };
const std::vector<std::string> authPin = { "Device: ", "PIN: " };
const std::vector<std::string> gw = { "GatewayUsername: ", "GatewayDomain: ",
"GatewayPassword: " };
std::vector<std::string> prompt;
Sint32 rc = -1;
switch (args->result)
{
case AUTH_SMARTCARD_PIN:
prompt = authPin;
break;
case AUTH_TLS:
case AUTH_RDP:
case AUTH_NLA:
prompt = auth;
break;
case GW_AUTH_HTTP:
case GW_AUTH_RDG:
case GW_AUTH_RPC:
prompt = gw;
break;
default:
break;
}
std::vector<std::string> result;
if (!prompt.empty())
{
std::vector<std::string> initial{ args->user ? args->user : "Smartcard", "" };
std::vector<Uint32> flags = { SdlInputWidget::SDL_INPUT_READONLY,
SdlInputWidget::SDL_INPUT_MASK };
if (args->result != AUTH_SMARTCARD_PIN)
{
initial = { args->user ? args->user : "", args->domain ? args->domain : "",
args->password ? args->password : "" };
flags = { 0, 0, SdlInputWidget::SDL_INPUT_MASK };
}
SdlInputWidgetList ilist(args->title, prompt, initial, flags);
rc = ilist.run(result);
}
if ((result.size() < prompt.size()))
rc = -1;
char* user = nullptr;
char* domain = nullptr;
char* pwd = nullptr;
if (rc > 0)
{
user = _strdup(result.at(0).c_str());
if (args->result == AUTH_SMARTCARD_PIN)
pwd = _strdup(result.at(1).c_str());
else
{
domain = _strdup(result.at(1).c_str());
pwd = _strdup(result.at(2).c_str());
}
}
return sdl_push_user_event(SDL_USEREVENT_AUTH_RESULT, user, domain, pwd, rc);
}
BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list)
{
const auto scount = WINPR_ASSERTING_INT_CAST(size_t, count);
std::vector<std::string> vlist;
vlist.reserve(scount);
for (size_t x = 0; x < scount; x++)
vlist.emplace_back(list[x]);
SdlSelectList slist(title, vlist);
Sint32 value = slist.run();
return sdl_push_user_event(SDL_USEREVENT_SCARD_RESULT, value);
}

View File

@@ -0,0 +1,53 @@
/**
* 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 <winpr/wtypes.h>
#include <freerdp/freerdp.h>
#include "../sdl_types.hpp"
#include "../sdl_utils.hpp"
BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
rdp_auth_reason reason);
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);
DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
const char* common_name, const char* subject,
const char* issuer, const char* new_fingerprint,
const char* old_subject, const char* old_issuer,
const char* old_fingerprint, DWORD flags);
int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type);
BOOL sdl_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
BOOL isConsentMandatory, size_t length, const WCHAR* message);
BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags);
BOOL sdl_cert_dialog_show(const char* title, const char* message);
BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list);
BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args);

View File

@@ -0,0 +1,177 @@
/**
* 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 "sdl_input.hpp"
#include <cassert>
#include <string>
#include <utility>
#include <SDL.h>
#include <SDL_ttf.h>
#include "sdl_widget.hpp"
#include "sdl_button.hpp"
#include "sdl_buttons.hpp"
static const SDL_Color inputbackgroundcolor = { 0x56, 0x56, 0x56, 0xff };
static const SDL_Color inputhighlightcolor = { 0x80, 0, 0, 0x60 };
static const SDL_Color inputmouseovercolor = { 0, 0x80, 0, 0x60 };
static const SDL_Color inputfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
static const SDL_Color labelbackgroundcolor = { 0x56, 0x56, 0x56, 0xff };
static const SDL_Color labelfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
static const Uint32 vpadding = 5;
static const Uint32 hpadding = 10;
SdlInputWidget::SdlInputWidget(SDL_Renderer* renderer, std::string label, std::string initial,
Uint32 flags, size_t offset, size_t width, size_t height)
: _flags(flags), _text(std::move(initial)), _text_label(std::move(label)),
_label(renderer,
{ 0, static_cast<int>(offset * (height + vpadding)), static_cast<int>(width),
static_cast<int>(height) },
false),
_input(renderer,
{ static_cast<int>(width + hpadding), static_cast<int>(offset * (height + vpadding)),
static_cast<int>(width), static_cast<int>(height) },
true),
_highlight(false), _mouseover(false)
{
}
SdlInputWidget::SdlInputWidget(SdlInputWidget&& other) noexcept
: _flags(other._flags), _text(std::move(other._text)),
_text_label(std::move(other._text_label)), _label(std::move(other._label)),
_input(std::move(other._input)), _highlight(other._highlight), _mouseover(other._mouseover)
{
}
bool SdlInputWidget::fill_label(SDL_Renderer* renderer, SDL_Color color)
{
if (!_label.fill(renderer, color))
return false;
return _label.update_text(renderer, _text_label, labelfontcolor);
}
bool SdlInputWidget::update_label(SDL_Renderer* renderer)
{
return _label.update_text(renderer, _text_label, labelfontcolor, labelbackgroundcolor);
}
bool SdlInputWidget::set_mouseover(SDL_Renderer* renderer, bool mouseOver)
{
if (readonly())
return true;
_mouseover = mouseOver;
return update_input(renderer);
}
bool SdlInputWidget::set_highlight(SDL_Renderer* renderer, bool highlight)
{
if (readonly())
return true;
_highlight = highlight;
return update_input(renderer);
}
bool SdlInputWidget::update_input(SDL_Renderer* renderer)
{
std::vector<SDL_Color> colors = { inputbackgroundcolor };
if (_highlight)
colors.push_back(inputhighlightcolor);
if (_mouseover)
colors.push_back(inputmouseovercolor);
if (!_input.fill(renderer, colors))
return false;
return update_input(renderer, inputfontcolor);
}
bool SdlInputWidget::resize_input(size_t size)
{
_text.resize(size);
return true;
}
bool SdlInputWidget::set_str(SDL_Renderer* renderer, const std::string& text)
{
if (readonly())
return true;
_text = text;
if (!resize_input(_text.size()))
return false;
return update_input(renderer);
}
bool SdlInputWidget::remove_str(SDL_Renderer* renderer, size_t count)
{
if (readonly())
return true;
assert(renderer);
if (_text.empty())
return true;
if (!resize_input(_text.size() - count))
return false;
return update_input(renderer);
}
bool SdlInputWidget::append_str(SDL_Renderer* renderer, const std::string& text)
{
assert(renderer);
if (readonly())
return true;
_text.append(text);
if (!resize_input(_text.size()))
return false;
return update_input(renderer);
}
const SDL_Rect& SdlInputWidget::input_rect() const
{
return _input.rect();
}
std::string SdlInputWidget::value() const
{
return _text;
}
bool SdlInputWidget::readonly() const
{
return (_flags & SDL_INPUT_READONLY) != 0;
}
bool SdlInputWidget::update_input(SDL_Renderer* renderer, SDL_Color fgcolor)
{
std::string text = _text;
if (!text.empty())
{
if (_flags & SDL_INPUT_MASK)
{
for (char& x : text)
x = '*';
}
}
return _input.update_text(renderer, text, fgcolor);
}

View File

@@ -0,0 +1,74 @@
/**
* 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 <vector>
#include <string>
#include <SDL.h>
#include "sdl_widget.hpp"
class SdlInputWidget
{
public:
enum
{
SDL_INPUT_MASK = 1,
SDL_INPUT_READONLY = 2
};
SdlInputWidget(SDL_Renderer* renderer, std::string label, std::string initial, Uint32 flags,
size_t offset, size_t width, size_t height);
SdlInputWidget(SdlInputWidget&& other) noexcept;
SdlInputWidget(const SdlInputWidget& other) = delete;
~SdlInputWidget() = default;
SdlInputWidget& operator=(const SdlInputWidget& other) = delete;
SdlInputWidget& operator=(SdlInputWidget&& other) = delete;
bool fill_label(SDL_Renderer* renderer, SDL_Color color);
bool update_label(SDL_Renderer* renderer);
bool set_mouseover(SDL_Renderer* renderer, bool mouseOver);
bool set_highlight(SDL_Renderer* renderer, bool highlight);
bool update_input(SDL_Renderer* renderer);
bool resize_input(size_t size);
bool set_str(SDL_Renderer* renderer, const std::string& text);
bool remove_str(SDL_Renderer* renderer, size_t count);
bool append_str(SDL_Renderer* renderer, const std::string& text);
[[nodiscard]] const SDL_Rect& input_rect() const;
[[nodiscard]] std::string value() const;
[[nodiscard]] bool readonly() const;
protected:
bool update_input(SDL_Renderer* renderer, SDL_Color fgcolor);
private:
Uint32 _flags;
std::string _text;
std::string _text_label;
SdlWidget _label;
SdlWidget _input;
bool _highlight;
bool _mouseover;
};

View File

@@ -0,0 +1,299 @@
#include <cassert>
#include <limits>
#include <algorithm>
#include <cinttypes>
#include "sdl_input_widgets.hpp"
static const Uint32 vpadding = 5;
SdlInputWidgetList::SdlInputWidgetList(const std::string& title,
const std::vector<std::string>& labels,
const std::vector<std::string>& initial,
const std::vector<Uint32>& flags)
: _window(nullptr), _renderer(nullptr)
{
assert(labels.size() == initial.size());
assert(labels.size() == flags.size());
const std::vector<int> buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL };
const std::vector<std::string> buttonlabels = { "accept", "cancel" };
const size_t widget_width = 300;
const size_t widget_heigth = 50;
const size_t total_width = widget_width + widget_width;
const size_t input_height = labels.size() * (widget_heigth + vpadding) + vpadding;
const size_t total_height = input_height + widget_heigth;
assert(total_width <= INT32_MAX);
assert(total_height <= INT32_MAX);
Uint32 wflags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS;
auto rc =
SDL_CreateWindowAndRenderer(static_cast<int>(total_width), static_cast<int>(total_height),
wflags, &_window, &_renderer);
if (rc != 0)
widget_log_error(rc, "SDL_CreateWindowAndRenderer");
else
{
SDL_SetWindowTitle(_window, title.c_str());
for (size_t x = 0; x < labels.size(); x++)
_list.emplace_back(_renderer, labels.at(x), initial.at(x), flags.at(x), x, widget_width,
widget_heigth);
_buttons.populate(_renderer, buttonlabels, buttonids, total_width,
static_cast<Sint32>(input_height), static_cast<Sint32>(widget_width),
static_cast<Sint32>(widget_heigth));
_buttons.set_highlight(0);
}
}
ssize_t SdlInputWidgetList::next(ssize_t current)
{
size_t iteration = 0;
auto val = static_cast<size_t>(current);
do
{
if (iteration >= _list.size())
return -1;
if (iteration == 0)
{
if (current < 0)
val = 0;
else
val++;
}
else
val++;
iteration++;
val %= _list.size();
} while (!valid(static_cast<ssize_t>(val)));
return static_cast<ssize_t>(val);
}
bool SdlInputWidgetList::valid(ssize_t current) const
{
if (current < 0)
return false;
auto s = static_cast<size_t>(current);
if (s >= _list.size())
return false;
return !_list.at(s).readonly();
}
SdlInputWidget* SdlInputWidgetList::get(ssize_t index)
{
if (index < 0)
return nullptr;
auto s = static_cast<size_t>(index);
if (s >= _list.size())
return nullptr;
return &_list.at(s);
}
SdlInputWidgetList::~SdlInputWidgetList()
{
_list.clear();
_buttons.clear();
SDL_DestroyRenderer(_renderer);
SDL_DestroyWindow(_window);
}
bool SdlInputWidgetList::update(SDL_Renderer* renderer)
{
for (auto& btn : _list)
{
if (!btn.update_label(renderer))
return false;
if (!btn.update_input(renderer))
return false;
}
return _buttons.update(renderer);
}
ssize_t SdlInputWidgetList::get_index(const SDL_MouseButtonEvent& button)
{
const Sint32 x = button.x;
const Sint32 y = button.y;
assert(_list.size() <= std::numeric_limits<ssize_t>::max());
for (size_t i = 0; i < _list.size(); i++)
{
auto& cur = _list.at(i);
auto r = cur.input_rect();
if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
return static_cast<ssize_t>(i);
}
return -1;
}
int SdlInputWidgetList::run(std::vector<std::string>& result)
{
int res = -1;
ssize_t LastActiveTextInput = -1;
ssize_t CurrentActiveTextInput = next(-1);
if (!_window || !_renderer)
return -2;
try
{
bool running = true;
std::vector<SDL_Keycode> pressed;
while (running)
{
if (!clear_window(_renderer))
throw;
if (!update(_renderer))
throw;
if (!_buttons.update(_renderer))
throw;
SDL_Event event = {};
SDL_WaitEvent(&event);
switch (event.type)
{
case SDL_KEYUP:
{
auto it = std::remove(pressed.begin(), pressed.end(), event.key.keysym.sym);
pressed.erase(it, pressed.end());
switch (event.key.keysym.sym)
{
case SDLK_BACKSPACE:
{
auto cur = get(CurrentActiveTextInput);
if (cur)
{
if (!cur->remove_str(_renderer, 1))
throw;
}
}
break;
case SDLK_TAB:
CurrentActiveTextInput = next(CurrentActiveTextInput);
break;
case SDLK_RETURN:
case SDLK_RETURN2:
case SDLK_KP_ENTER:
running = false;
res = INPUT_BUTTON_ACCEPT;
break;
case SDLK_ESCAPE:
running = false;
res = INPUT_BUTTON_CANCEL;
break;
case SDLK_v:
if (pressed.size() == 2)
{
if ((pressed.at(0) == SDLK_LCTRL) || (pressed.at(0) == SDLK_RCTRL))
{
auto cur = get(CurrentActiveTextInput);
if (cur)
{
auto text = SDL_GetClipboardText();
cur->set_str(_renderer, text);
}
}
}
break;
default:
break;
}
}
break;
case SDL_KEYDOWN:
pressed.push_back(event.key.keysym.sym);
break;
case SDL_TEXTINPUT:
{
auto cur = get(CurrentActiveTextInput);
if (cur)
{
if (!cur->append_str(_renderer, event.text.text))
throw;
}
}
break;
case SDL_MOUSEMOTION:
{
auto TextInputIndex = get_index(event.button);
for (auto& cur : _list)
{
if (!cur.set_mouseover(_renderer, false))
throw;
}
if (TextInputIndex >= 0)
{
auto& cur = _list.at(static_cast<size_t>(TextInputIndex));
if (!cur.set_mouseover(_renderer, true))
throw;
}
_buttons.set_mouseover(event.button.x, event.button.y);
}
break;
case SDL_MOUSEBUTTONDOWN:
{
auto val = get_index(event.button);
if (valid(val))
CurrentActiveTextInput = val;
auto button = _buttons.get_selected(event.button);
if (button)
{
running = false;
if (button->id() == INPUT_BUTTON_CANCEL)
res = INPUT_BUTTON_CANCEL;
else
res = INPUT_BUTTON_ACCEPT;
}
}
break;
case SDL_QUIT:
res = INPUT_BUTTON_CANCEL;
running = false;
break;
default:
break;
}
if (LastActiveTextInput != CurrentActiveTextInput)
{
if (CurrentActiveTextInput < 0)
SDL_StopTextInput();
else
SDL_StartTextInput();
LastActiveTextInput = CurrentActiveTextInput;
}
for (auto& cur : _list)
{
if (!cur.set_highlight(_renderer, false))
throw;
}
auto cur = get(CurrentActiveTextInput);
if (cur)
{
if (!cur->set_highlight(_renderer, true))
throw;
}
SDL_RenderPresent(_renderer);
}
for (auto& cur : _list)
result.push_back(cur.value());
}
catch (...)
{
res = -2;
}
return res;
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include <string>
#include <vector>
#include <SDL.h>
#include "sdl_input.hpp"
#include "sdl_buttons.hpp"
class SdlInputWidgetList
{
public:
SdlInputWidgetList(const std::string& title, const std::vector<std::string>& labels,
const std::vector<std::string>& initial, const std::vector<Uint32>& flags);
SdlInputWidgetList(const SdlInputWidgetList& other) = delete;
SdlInputWidgetList(SdlInputWidgetList&& other) = delete;
SdlInputWidgetList& operator=(const SdlInputWidgetList& other) = delete;
SdlInputWidgetList& operator=(SdlInputWidgetList&& other) = delete;
virtual ~SdlInputWidgetList();
int run(std::vector<std::string>& result);
protected:
bool update(SDL_Renderer* renderer);
ssize_t get_index(const SDL_MouseButtonEvent& button);
private:
enum
{
INPUT_BUTTON_ACCEPT = 1,
INPUT_BUTTON_CANCEL = -2
};
ssize_t next(ssize_t current);
[[nodiscard]] bool valid(ssize_t current) const;
SdlInputWidget* get(ssize_t index);
SDL_Window* _window;
SDL_Renderer* _renderer;
std::vector<SdlInputWidget> _list;
SdlButtonList _buttons;
};

View File

@@ -0,0 +1,72 @@
/**
* 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 <cassert>
#include <string>
#include <utility>
#include <SDL.h>
#include <SDL_ttf.h>
#include "sdl_select.hpp"
#include "sdl_widget.hpp"
#include "sdl_button.hpp"
#include "sdl_buttons.hpp"
#include "sdl_input_widgets.hpp"
static const SDL_Color labelmouseovercolor = { 0, 0x80, 0, 0x60 };
static const SDL_Color labelbackgroundcolor = { 0x69, 0x66, 0x63, 0xff };
static const SDL_Color labelhighlightcolor = { 0xcd, 0xca, 0x35, 0x60 };
static const SDL_Color labelfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
SdlSelectWidget::SdlSelectWidget(SDL_Renderer* renderer, std::string label, SDL_Rect rect)
: SdlWidget(renderer, rect, true), _text(std::move(label)), _mouseover(false), _highlight(false)
{
update_text(renderer);
}
SdlSelectWidget::SdlSelectWidget(SdlSelectWidget&& other) noexcept = default;
SdlSelectWidget::~SdlSelectWidget() = default;
bool SdlSelectWidget::set_mouseover(SDL_Renderer* renderer, bool mouseOver)
{
_mouseover = mouseOver;
return update_text(renderer);
}
bool SdlSelectWidget::set_highlight(SDL_Renderer* renderer, bool highlight)
{
_highlight = highlight;
return update_text(renderer);
}
bool SdlSelectWidget::update_text(SDL_Renderer* renderer)
{
assert(renderer);
std::vector<SDL_Color> colors = { labelbackgroundcolor };
if (_highlight)
colors.push_back(labelhighlightcolor);
if (_mouseover)
colors.push_back(labelmouseovercolor);
if (!fill(renderer, colors))
return false;
return SdlWidget::update_text(renderer, _text, labelfontcolor);
}

View File

@@ -0,0 +1,47 @@
/**
* 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 <string>
#include <vector>
#include <SDL.h>
#include "sdl_widget.hpp"
class SdlSelectWidget : public SdlWidget
{
public:
SdlSelectWidget(SDL_Renderer* renderer, std::string label, SDL_Rect rect);
SdlSelectWidget(SdlSelectWidget&& other) noexcept;
~SdlSelectWidget() override;
bool set_mouseover(SDL_Renderer* renderer, bool mouseOver);
bool set_highlight(SDL_Renderer* renderer, bool highlight);
bool update_text(SDL_Renderer* renderer);
SdlSelectWidget(const SdlSelectWidget& other) = delete;
SdlSelectWidget& operator=(const SdlSelectWidget& other) = delete;
SdlSelectWidget& operator=(SdlSelectWidget&& other) = delete;
private:
std::string _text;
bool _mouseover;
bool _highlight;
};

View File

@@ -0,0 +1,217 @@
#include <cassert>
#include <winpr/cast.h>
#include "sdl_selectlist.hpp"
static const Uint32 vpadding = 5;
SdlSelectList::SdlSelectList(const std::string& title, const std::vector<std::string>& labels)
: _window(nullptr), _renderer(nullptr)
{
const size_t widget_height = 50;
const size_t widget_width = 600;
const size_t total_height = labels.size() * (widget_height + vpadding) + vpadding;
const size_t height = total_height + widget_height;
assert(widget_width <= INT32_MAX);
assert(height <= INT32_MAX);
auto flags = WINPR_ASSERTING_INT_CAST(
uint32_t, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS);
auto rc = SDL_CreateWindowAndRenderer(static_cast<int>(widget_width), static_cast<int>(height),
flags, &_window, &_renderer);
if (rc != 0)
widget_log_error(rc, "SDL_CreateWindowAndRenderer");
else
{
SDL_SetWindowTitle(_window, title.c_str());
SDL_Rect rect = { 0, 0, widget_width, widget_height };
for (auto& label : labels)
{
_list.emplace_back(_renderer, label, rect);
rect.y += widget_height + vpadding;
}
const std::vector<int> buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL };
const std::vector<std::string> buttonlabels = { "accept", "cancel" };
_buttons.populate(_renderer, buttonlabels, buttonids, widget_width,
static_cast<Sint32>(total_height), static_cast<Sint32>(widget_width / 2),
static_cast<Sint32>(widget_height));
_buttons.set_highlight(0);
}
}
SdlSelectList::~SdlSelectList()
{
_list.clear();
_buttons.clear();
SDL_DestroyRenderer(_renderer);
SDL_DestroyWindow(_window);
}
int SdlSelectList::run()
{
int res = -2;
ssize_t CurrentActiveTextInput = 0;
bool running = true;
if (!_window || !_renderer)
return -2;
try
{
while (running)
{
if (!clear_window(_renderer))
throw;
if (!update_text())
throw;
if (!_buttons.update(_renderer))
throw;
SDL_Event event = {};
SDL_WaitEvent(&event);
switch (event.type)
{
case SDL_KEYDOWN:
switch (event.key.keysym.sym)
{
case SDLK_UP:
case SDLK_BACKSPACE:
if (CurrentActiveTextInput > 0)
CurrentActiveTextInput--;
else
CurrentActiveTextInput =
WINPR_ASSERTING_INT_CAST(ssize_t, _list.size() - 1);
break;
case SDLK_DOWN:
case SDLK_TAB:
{
if (CurrentActiveTextInput < 0)
CurrentActiveTextInput = 0;
else
CurrentActiveTextInput++;
const auto s = _list.size();
if (s <= 0)
CurrentActiveTextInput = 0;
else
CurrentActiveTextInput =
CurrentActiveTextInput % WINPR_ASSERTING_INT_CAST(ssize_t, s);
}
break;
case SDLK_RETURN:
case SDLK_RETURN2:
case SDLK_KP_ENTER:
running = false;
res = WINPR_ASSERTING_INT_CAST(int, CurrentActiveTextInput);
break;
case SDLK_ESCAPE:
running = false;
res = INPUT_BUTTON_CANCEL;
break;
default:
break;
}
break;
case SDL_MOUSEMOTION:
{
ssize_t TextInputIndex = get_index(event.button);
reset_mouseover();
if (TextInputIndex >= 0)
{
auto& cur = _list.at(WINPR_ASSERTING_INT_CAST(size_t, TextInputIndex));
if (!cur.set_mouseover(_renderer, true))
throw;
}
_buttons.set_mouseover(event.button.x, event.button.y);
}
break;
case SDL_MOUSEBUTTONDOWN:
{
auto button = _buttons.get_selected(event.button);
if (button)
{
running = false;
if (button->id() == INPUT_BUTTON_CANCEL)
res = INPUT_BUTTON_CANCEL;
else
res = static_cast<int>(CurrentActiveTextInput);
}
else
{
CurrentActiveTextInput = get_index(event.button);
}
}
break;
case SDL_QUIT:
res = INPUT_BUTTON_CANCEL;
running = false;
break;
default:
break;
}
reset_highlight();
if (CurrentActiveTextInput >= 0)
{
auto& cur = _list.at(WINPR_ASSERTING_INT_CAST(size_t, CurrentActiveTextInput));
if (!cur.set_highlight(_renderer, true))
throw;
}
SDL_RenderPresent(_renderer);
}
}
catch (...)
{
return -1;
}
return res;
}
ssize_t SdlSelectList::get_index(const SDL_MouseButtonEvent& button)
{
const Sint32 x = button.x;
const Sint32 y = button.y;
for (size_t i = 0; i < _list.size(); i++)
{
auto& cur = _list.at(i);
auto r = cur.rect();
if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
return WINPR_ASSERTING_INT_CAST(ssize_t, i);
}
return -1;
}
bool SdlSelectList::update_text()
{
for (auto& cur : _list)
{
if (!cur.update_text(_renderer))
return false;
}
return true;
}
void SdlSelectList::reset_mouseover()
{
for (auto& cur : _list)
{
cur.set_mouseover(_renderer, false);
}
}
void SdlSelectList::reset_highlight()
{
for (auto& cur : _list)
{
cur.set_highlight(_renderer, false);
}
}

View File

@@ -0,0 +1,41 @@
#pragma once
#include <string>
#include <vector>
#include <SDL.h>
#include "sdl_select.hpp"
#include "sdl_button.hpp"
#include "sdl_buttons.hpp"
class SdlSelectList
{
public:
SdlSelectList(const std::string& title, const std::vector<std::string>& labels);
virtual ~SdlSelectList();
int run();
SdlSelectList(const SdlSelectList& other) = delete;
SdlSelectList(SdlSelectList&& other) = delete;
SdlSelectList& operator=(const SdlSelectList& other) = delete;
SdlSelectList& operator=(SdlSelectList&& other) = delete;
private:
enum
{
INPUT_BUTTON_ACCEPT = 0,
INPUT_BUTTON_CANCEL = -2
};
ssize_t get_index(const SDL_MouseButtonEvent& button);
bool update_text();
void reset_mouseover();
void reset_highlight();
SDL_Window* _window;
SDL_Renderer* _renderer;
std::vector<SdlSelectWidget> _list;
SdlButtonList _buttons;
};

View File

@@ -0,0 +1,291 @@
/**
* 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 <cassert>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <SDL.h>
#include <SDL_ttf.h>
#include "sdl_widget.hpp"
#include "../sdl_utils.hpp"
#include "res/sdl2_resource_manager.hpp"
#include <freerdp/log.h>
#if defined(WITH_SDL_IMAGE_DIALOGS)
#include <SDL_image.h>
#endif
#define TAG CLIENT_TAG("SDL.widget")
static const SDL_Color backgroundcolor = { 0x38, 0x36, 0x35, 0xff };
static const Uint32 hpadding = 10;
SdlWidget::SdlWidget([[maybe_unused]] SDL_Renderer* renderer, SDL_Rect rect, bool input)
: _rect(rect), _input(input)
{
assert(renderer);
auto ops = SDL2ResourceManager::get(SDLResourceManager::typeFonts(),
"OpenSans-VariableFont_wdth,wght.ttf");
if (!ops)
widget_log_error(-1, "SDLResourceManager::get");
else
{
_font = TTF_OpenFontRW(ops, 1, 64);
if (!_font)
widget_log_error(-1, "TTF_OpenFontRW");
}
}
#if defined(WITH_SDL_IMAGE_DIALOGS)
SdlWidget::SdlWidget(SDL_Renderer* renderer, SDL_Rect rect, SDL_RWops* ops) : _rect(rect)
{
if (ops)
{
_image = IMG_LoadTexture_RW(renderer, ops, 1);
if (!_image)
widget_log_error(-1, "IMG_LoadTextureTyped_RW");
}
}
#endif
SdlWidget::SdlWidget(SdlWidget&& other) noexcept
: _font(other._font), _image(other._image), _rect(other._rect), _input(other._input),
_wrap(other._wrap), _text_width(other._text_width)
{
other._font = nullptr;
other._image = nullptr;
}
SDL_Texture* SdlWidget::render_text(SDL_Renderer* renderer, const std::string& text,
SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst)
{
auto surface = TTF_RenderUTF8_Blended(_font, text.c_str(), fgcolor);
if (!surface)
{
widget_log_error(-1, "TTF_RenderText_Blended");
return nullptr;
}
auto texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
if (!texture)
{
widget_log_error(-1, "SDL_CreateTextureFromSurface");
return nullptr;
}
TTF_SizeUTF8(_font, text.c_str(), &src.w, &src.h);
/* Do some magic:
* - Add padding before and after text
* - if text is too long only show the last elements
* - if text is too short only update used space
*/
dst = _rect;
dst.x += hpadding;
dst.w -= 2 * hpadding;
const auto scale = static_cast<float>(dst.h) / static_cast<float>(src.h);
const auto sws = static_cast<float>(src.w) * scale;
const auto dws = static_cast<float>(dst.w) / scale;
if (static_cast<float>(dst.w) > sws)
dst.w = static_cast<int>(sws);
if (static_cast<float>(src.w) > dws)
{
src.x = src.w - static_cast<int>(dws);
src.w = static_cast<int>(dws);
}
return texture;
}
static int scale(int w, int h)
{
const auto dw = static_cast<double>(w);
const auto dh = static_cast<double>(h);
const auto scale = dh / dw;
const auto dr = dh * scale;
return static_cast<int>(dr);
}
SDL_Texture* SdlWidget::render_text_wrapped(SDL_Renderer* renderer, const std::string& text,
SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst)
{
Sint32 w = 0;
Sint32 h = 0;
TTF_SizeUTF8(_font, " ", &w, &h);
assert(_text_width <= UINT32_MAX);
auto surface = TTF_RenderUTF8_Blended_Wrapped(_font, text.c_str(), fgcolor,
static_cast<Uint32>(_text_width));
if (!surface)
{
widget_log_error(-1, "TTF_RenderText_Blended");
return nullptr;
}
src.w = surface->w;
src.h = surface->h;
auto texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
if (!texture)
{
widget_log_error(-1, "SDL_CreateTextureFromSurface");
return nullptr;
}
/* Do some magic:
* - Add padding before and after text
* - if text is too long only show the last elements
* - if text is too short only update used space
*/
dst = _rect;
dst.x += hpadding;
dst.w -= 2 * hpadding;
auto dh = scale(src.w, src.h);
dst.h = std::min<int>(dh, dst.h);
return texture;
}
SdlWidget::~SdlWidget()
{
TTF_CloseFont(_font);
if (_image)
SDL_DestroyTexture(_image);
}
bool SdlWidget::error_ex(Sint32 res, const char* what, const char* file, size_t line,
const char* fkt)
{
static wLog* log = nullptr;
if (!log)
log = WLog_Get(TAG);
return sdl_log_error_ex(res, log, what, file, line, fkt);
}
static bool draw_rect(SDL_Renderer* renderer, const SDL_Rect* rect, SDL_Color color)
{
const int drc = SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
return false;
const int rc = SDL_RenderFillRect(renderer, rect);
return !widget_log_error(rc, "SDL_RenderFillRect");
}
bool SdlWidget::fill(SDL_Renderer* renderer, SDL_Color color)
{
std::vector<SDL_Color> colors = { color };
return fill(renderer, colors);
}
bool SdlWidget::fill(SDL_Renderer* renderer, const std::vector<SDL_Color>& colors)
{
assert(renderer);
SDL_BlendMode mode = SDL_BLENDMODE_INVALID;
SDL_GetRenderDrawBlendMode(renderer, &mode);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
for (auto color : colors)
{
draw_rect(renderer, &_rect, color);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD);
}
SDL_SetRenderDrawBlendMode(renderer, mode);
return true;
}
bool SdlWidget::update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
SDL_Color bgcolor)
{
assert(renderer);
if (!fill(renderer, bgcolor))
return false;
return update_text(renderer, text, fgcolor);
}
bool SdlWidget::wrap() const
{
return _wrap;
}
bool SdlWidget::set_wrap(bool wrap, size_t width)
{
_wrap = wrap;
_text_width = width;
return _wrap;
}
const SDL_Rect& SdlWidget::rect() const
{
return _rect;
}
bool SdlWidget::update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor)
{
if (text.empty())
return true;
SDL_Rect src{};
SDL_Rect dst{};
SDL_Texture* texture = nullptr;
if (_image)
{
texture = _image;
dst = _rect;
auto rc = SDL_QueryTexture(_image, nullptr, nullptr, &src.w, &src.h);
if (rc < 0)
widget_log_error(rc, "SDL_QueryTexture");
}
else if (_wrap)
texture = render_text_wrapped(renderer, text, fgcolor, src, dst);
else
texture = render_text(renderer, text, fgcolor, src, dst);
if (!texture)
return false;
const int rc = SDL_RenderCopy(renderer, texture, &src, &dst);
if (!_image)
SDL_DestroyTexture(texture);
if (rc < 0)
return !widget_log_error(rc, "SDL_RenderCopy");
return true;
}
bool clear_window(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");
}

View File

@@ -0,0 +1,90 @@
/**
* 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 <string>
#include <vector>
#include <SDL.h>
#include <SDL_ttf.h>
#if defined(_MSC_VER)
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#endif
#if !defined(HAS_NOEXCEPT)
#if defined(__clang__)
#if __has_feature(cxx_noexcept)
#define HAS_NOEXCEPT
#endif
#elif defined(__GXX_EXPERIMENTAL_CXX0X__) && __GNUC__ * 10 + __GNUC_MINOR__ >= 46 || \
defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023026
#define HAS_NOEXCEPT
#endif
#endif
#ifndef HAS_NOEXCEPT
#define noexcept
#endif
class SdlWidget
{
public:
SdlWidget(SDL_Renderer* renderer, SDL_Rect rect, bool input);
#if defined(WITH_SDL_IMAGE_DIALOGS)
SdlWidget(SDL_Renderer* renderer, SDL_Rect rect, SDL_RWops* ops);
#endif
SdlWidget(SdlWidget&& other) noexcept;
virtual ~SdlWidget();
bool fill(SDL_Renderer* renderer, SDL_Color color);
bool fill(SDL_Renderer* renderer, const std::vector<SDL_Color>& colors);
bool update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor);
bool update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
SDL_Color bgcolor);
[[nodiscard]] bool wrap() const;
bool set_wrap(bool wrap = true, size_t width = 0);
[[nodiscard]] const SDL_Rect& rect() const;
#define widget_log_error(res, what) SdlWidget::error_ex(res, what, __FILE__, __LINE__, __func__)
static bool error_ex(Sint32 res, const char* what, const char* file, size_t line,
const char* fkt);
SdlWidget(const SdlWidget& other) = delete;
SdlWidget& operator=(const SdlWidget& other) = delete;
SdlWidget& operator=(SdlWidget&& other) = delete;
private:
SDL_Texture* render_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
SDL_Rect& src, SDL_Rect& dst);
SDL_Texture* render_text_wrapped(SDL_Renderer* renderer, const std::string& text,
SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst);
TTF_Font* _font = nullptr;
SDL_Texture* _image = nullptr;
SDL_Rect _rect = {};
bool _input = false;
bool _wrap = false;
size_t _text_width = 0;
};
bool clear_window(SDL_Renderer* renderer);

View File

@@ -0,0 +1,25 @@
set(MODULE_NAME "TestSDL")
set(MODULE_PREFIX "TEST_SDL")
set(DRIVER ${MODULE_NAME}.cpp)
disable_warnings_for_directory(${CMAKE_CURRENT_BINARY_DIR})
set(TEST_SRCS TestSDLDialogs.cpp)
create_test_sourcelist(SRCS ${DRIVER} ${TEST_SRCS})
add_library(${MODULE_NAME} ${SRCS})
list(APPEND LIBS dialogs)
target_link_libraries(${MODULE_NAME} ${LIBS})
set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
foreach(test ${TESTS})
get_filename_component(TestName ${test} NAME_WE)
add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
endforeach()
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Client/Test")

View File

@@ -0,0 +1,99 @@
#include "../sdl_selectlist.hpp"
#include "../sdl_input_widgets.hpp"
#include <freerdp/api.h>
#include <winpr/wlog.h>
BOOL sdl_log_error_ex(Uint32 res, wLog* log, const char* what, const char* file, size_t line,
const char* fkt)
{
return FALSE;
}
static bool test_input_dialog()
{
const auto title = "sometitle";
std::vector<std::string> labels;
std::vector<std::string> initial;
std::vector<Uint32> flags;
for (size_t x = 0; x < 12; x++)
{
labels.push_back("label" + std::to_string(x));
initial.push_back(std::to_string(x));
Uint32 flag = 0;
if ((x % 2) != 0)
flag |= SdlInputWidget::SDL_INPUT_MASK;
if ((x % 3) == 0)
flag |= SdlInputWidget::SDL_INPUT_READONLY;
flags.push_back(flag);
}
SdlInputWidgetList list{ title, labels, initial, flags };
std::vector<std::string> result;
auto rc = list.run(result);
if (rc < 0)
{
return false;
}
if (result.size() != labels.size())
{
return false;
}
return true;
}
static bool test_select_dialog()
{
const auto title = "sometitle";
std::vector<std::string> labels;
for (size_t x = 0; x < 12; x++)
{
labels.push_back("label" + std::to_string(x));
}
SdlSelectList list{ title, labels };
auto rc = list.run();
if (rc < 0)
{
return false;
}
if (static_cast<size_t>(rc) >= labels.size())
return false;
return true;
}
extern "C"
{
FREERDP_API int TestSDLDialogs(int argc, char* argv[]);
}
int TestSDLDialogs(int argc, char* argv[])
{
int rc = 0;
(void)argc;
(void)argv;
#if 0
SDL_Init(SDL_INIT_VIDEO);
try
{
#if 1
if (!test_input_dialog())
throw -1;
#endif
#if 1
if (!test_select_dialog())
throw -2;
#endif
}
catch (int e)
{
rc = e;
}
SDL_Quit();
#endif
return rc;
}