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,87 @@
# 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
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_widget_list.hpp
sdl_widget_list.cpp
sdl_input_widget.hpp
sdl_input_widget.cpp
sdl_input_widget_pair.hpp
sdl_input_widget_pair.cpp
sdl_input_widget_pair_list.hpp
sdl_input_widget_pair_list.cpp
sdl_select.hpp
sdl_select.cpp
sdl_select_list.hpp
sdl_select_list.cpp
sdl_selectable_widget.cpp
sdl_selectable_widget.hpp
sdl_connection_dialog.cpp
sdl_connection_dialog.hpp
sdl_connection_dialog_wrapper.cpp
sdl_connection_dialog_wrapper.hpp
sdl_blend_mode_guard.cpp
sdl_blend_mode_guard.hpp
)
list(APPEND LIBS sdl3_client_res winpr)
if(NOT WITH_SDL_LINK_SHARED)
list(APPEND LIBS ${SDL3_STATIC_LIBRARIES})
else()
list(APPEND LIBS ${SDL3_LIBRARIES})
endif()
macro(find_sdl_component name)
find_package(${name} REQUIRED)
if(WITH_SDL_LINK_SHARED)
list(APPEND LIBS ${name}::${name})
set_target_properties(${name}::${name}-shared PROPERTIES SYSTEM TRUE)
else()
list(APPEND LIBS ${name}::${name}-static)
set_target_properties(${name}::${name}-static PROPERTIES SYSTEM TRUE)
endif()
endmacro()
find_sdl_component(SDL3_ttf)
option(WITH_SDL_IMAGE_DIALOGS "Build with SDL_image support (recommended)" OFF)
if(WITH_SDL_IMAGE_DIALOGS)
find_sdl_component(SDL3_image)
add_compile_definitions(WITH_SDL_IMAGE_DIALOGS)
endif()
add_subdirectory(res)
add_library(sdl3-dialogs STATIC ${SRCS} sdl_connection_dialog_hider.hpp sdl_connection_dialog_hider.cpp)
set_property(TARGET sdl3-dialogs PROPERTY FOLDER "Client/SDL")
target_link_libraries(sdl3-dialogs PRIVATE ${LIBS})
option(SDL_DIALOG_TEST "Build dialog test binaries" OFF)
if(SDL_DIALOG_TEST)
add_subdirectory(test)
endif()

View File

@@ -0,0 +1,93 @@
Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,100 @@
Open Sans Variable Font
=======================
This download contains Open Sans as both variable fonts and static fonts.
Open Sans is a variable font with these axes:
wdth
wght
This means all the styles are contained in these files:
OpenSans-VariableFont_wdth,wght.ttf
OpenSans-Italic-VariableFont_wdth,wght.ttf
If your app fully supports variable fonts, you can now pick intermediate styles
that arent available as static fonts. Not all apps support variable fonts, and
in those cases you can use the static font files for Open Sans:
static/OpenSans_Condensed-Light.ttf
static/OpenSans_Condensed-Regular.ttf
static/OpenSans_Condensed-Medium.ttf
static/OpenSans_Condensed-SemiBold.ttf
static/OpenSans_Condensed-Bold.ttf
static/OpenSans_Condensed-ExtraBold.ttf
static/OpenSans_SemiCondensed-Light.ttf
static/OpenSans_SemiCondensed-Regular.ttf
static/OpenSans_SemiCondensed-Medium.ttf
static/OpenSans_SemiCondensed-SemiBold.ttf
static/OpenSans_SemiCondensed-Bold.ttf
static/OpenSans_SemiCondensed-ExtraBold.ttf
static/OpenSans-Light.ttf
static/OpenSans-Regular.ttf
static/OpenSans-Medium.ttf
static/OpenSans-SemiBold.ttf
static/OpenSans-Bold.ttf
static/OpenSans-ExtraBold.ttf
static/OpenSans_Condensed-LightItalic.ttf
static/OpenSans_Condensed-Italic.ttf
static/OpenSans_Condensed-MediumItalic.ttf
static/OpenSans_Condensed-SemiBoldItalic.ttf
static/OpenSans_Condensed-BoldItalic.ttf
static/OpenSans_Condensed-ExtraBoldItalic.ttf
static/OpenSans_SemiCondensed-LightItalic.ttf
static/OpenSans_SemiCondensed-Italic.ttf
static/OpenSans_SemiCondensed-MediumItalic.ttf
static/OpenSans_SemiCondensed-SemiBoldItalic.ttf
static/OpenSans_SemiCondensed-BoldItalic.ttf
static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf
static/OpenSans-LightItalic.ttf
static/OpenSans-Italic.ttf
static/OpenSans-MediumItalic.ttf
static/OpenSans-SemiBoldItalic.ttf
static/OpenSans-BoldItalic.ttf
static/OpenSans-ExtraBoldItalic.ttf
Get started
-----------
1. Install the font files you want to use
2. Use your app's font picker to view the font family and all the
available styles
Learn more about variable fonts
-------------------------------
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
https://variablefonts.typenetwork.com
https://medium.com/variable-fonts
In desktop apps
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
Online
https://developers.google.com/fonts/docs/getting_started
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
Installing fonts
MacOS: https://support.apple.com/en-us/HT201749
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
Android Apps
https://developers.google.com/fonts/docs/android
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
License
-------
Please read the full license text (OFL.txt) to understand the permissions,
restrictions and requirements for usage, redistribution, and modification.
You can use them in your products & projects print or digital,
commercial or otherwise.
This isn't legal advice, please consider consulting a lawyer and see the full
license for all details.

View File

@@ -0,0 +1,31 @@
# 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 sdl3_resource_manager.cpp sdl3_resource_manager.hpp)
add_library(sdl3_client_res STATIC ${SRCS})
set_property(TARGET sdl3_client_res PROPERTY FOLDER "Client/SDL")
if(NOT WITH_SDL_LINK_SHARED)
target_link_libraries(sdl3_client_res ${SDL3_STATIC_LIBRARIES})
else()
target_link_libraries(sdl3_client_res ${SDL3_LIBRARIES})
endif()
target_link_libraries(sdl3_client_res sdl-common-client-res)
set_target_properties(sdl3_client_res PROPERTIES POSITION_INDEPENDENT_CODE ON INTERPROCEDURAL_OPTIMIZATION OFF)

View File

@@ -0,0 +1,43 @@
/**
* 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 "sdl3_resource_manager.hpp"
#include <iostream>
#if __has_include(<filesystem>)
#include <filesystem>
namespace fs = std::filesystem;
#elif __has_include(<experimental/filesystem>)
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
#error Could not find system header "<filesystem>" or "<experimental/filesystem>"
#endif
SDL_IOStream* SDL3ResourceManager::get(const std::string& type, const std::string& id)
{
if (useCompiledResources())
{
auto d = data(type, id);
if (!d)
return nullptr;
return SDL_IOFromConstMem(d->data(), d->size());
}
auto name = filename(type, id);
return SDL_IOFromFile(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 <SDL3/SDL.h>
#include <res/sdl_resource_manager.hpp>
class SDL3ResourceManager : public SDLResourceManager
{
public:
SDL3ResourceManager() = delete;
SDL3ResourceManager(const SDL3ResourceManager& other) = delete;
SDL3ResourceManager(const SDL3ResourceManager&& other) = delete;
~SDL3ResourceManager() = delete;
SDL3ResourceManager& operator=(const SDL3ResourceManager& other) = delete;
SDL3ResourceManager& operator=(SDL3ResourceManager&& other) = delete;
[[nodiscard]] static SDL_IOStream* get(const std::string& type, const std::string& id);
};

View File

@@ -0,0 +1,45 @@
#include "sdl_blend_mode_guard.hpp"
SdlBlendModeGuard::SdlBlendModeGuard(const std::shared_ptr<SDL_Renderer>& renderer,
SDL_BlendMode mode)
: _renderer(renderer)
{
const auto rcb = SDL_GetRenderDrawBlendMode(_renderer.get(), &_restore_mode);
if (!rcb)
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[%s] SDL_GetRenderDrawBlendMode() failed with %s", __func__, SDL_GetError());
else
{
const auto rbm = SDL_SetRenderDrawBlendMode(_renderer.get(), mode);
if (!rbm)
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[%s] SDL_SetRenderDrawBlendMode() failed with %s", __func__,
SDL_GetError());
else
_current_mode = mode;
}
}
bool SdlBlendModeGuard::update(SDL_BlendMode mode)
{
if (_current_mode != mode)
{
if (!SDL_SetRenderDrawBlendMode(_renderer.get(), mode))
{
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[%s] SDL_SetRenderDrawBlendMode() failed with %s", __func__,
SDL_GetError());
return false;
}
_current_mode = mode;
}
return true;
}
SdlBlendModeGuard::~SdlBlendModeGuard()
{
const auto rbm = SDL_SetRenderDrawBlendMode(_renderer.get(), _restore_mode);
if (!rbm)
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[%s] SDL_SetRenderDrawBlendMode() failed with %s", __func__, SDL_GetError());
}

View File

@@ -0,0 +1,46 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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.
*/
#pragma once
#include <memory>
#include <SDL3/SDL.h>
class SdlBlendModeGuard
{
public:
explicit SdlBlendModeGuard(const std::shared_ptr<SDL_Renderer>& renderer,
SDL_BlendMode mode = SDL_BLENDMODE_NONE);
~SdlBlendModeGuard();
SdlBlendModeGuard(SdlBlendModeGuard&& other) noexcept;
SdlBlendModeGuard(const SdlBlendModeGuard& other) = delete;
SdlBlendModeGuard& operator=(const SdlBlendModeGuard& other) = delete;
SdlBlendModeGuard& operator=(SdlBlendModeGuard&& other) = delete;
[[nodiscard]] bool update(SDL_BlendMode mode);
private:
SDL_BlendMode _restore_mode = SDL_BLENDMODE_INVALID;
SDL_BlendMode _current_mode = SDL_BLENDMODE_INVALID;
std::shared_ptr<SDL_Renderer> _renderer;
};

View File

@@ -0,0 +1,45 @@
/**
* 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"
SdlButton::SdlButton(std::shared_ptr<SDL_Renderer>& renderer, const std::string& label, int id,
const SDL_FRect& rect)
: SdlSelectableWidget(renderer, rect), _id(id)
{
_backgroundcolor = { 0x69, 0x66, 0x63, 0xff };
_highlightcolor = { 0xcd, 0xca, 0x35, 0x60 };
_mouseovercolor = { 0x66, 0xff, 0x66, 0x60 };
_fontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
std::ignore = update_text(label);
std::ignore = update();
}
SdlButton::SdlButton(SdlButton&& other) noexcept = default;
SdlButton::~SdlButton() = default;
int SdlButton::id() const
{
return _id;
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include <string>
#include <memory>
#include "sdl_selectable_widget.hpp"
class SdlButton : public SdlSelectableWidget
{
public:
SdlButton(std::shared_ptr<SDL_Renderer>& renderer, const std::string& label, int id,
const SDL_FRect& rect);
SdlButton(SdlButton&& other) noexcept;
SdlButton(const SdlButton& other) = delete;
~SdlButton() override;
SdlButton& operator=(const SdlButton& other) = delete;
SdlButton& operator=(SdlButton&& other) = delete;
[[nodiscard]] int id() const;
private:
int _id;
};

View File

@@ -0,0 +1,108 @@
#include <cassert>
#include <algorithm>
#include "sdl_buttons.hpp"
static const Uint32 hpadding = 10;
SdlButtonList::~SdlButtonList() = default;
bool SdlButtonList::populate(std::shared_ptr<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() * (static_cast<size_t>(width) + hpadding) + hpadding;
size_t offsetX = static_cast<size_t>(total_width) -
std::min<size_t>(static_cast<size_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_FRect rect = { static_cast<float>(curOffsetX), static_cast<float>(offsetY),
static_cast<float>(width), static_cast<float>(height) };
auto button = std::make_shared<SdlButton>(renderer, labels.at(x), ids.at(x), rect);
_list.emplace_back(button);
}
return true;
}
std::shared_ptr<SdlButton> SdlButtonList::get_selected(const SDL_MouseButtonEvent& button)
{
const auto x = button.x;
const auto y = button.y;
return get_selected(x, y);
}
std::shared_ptr<SdlButton> SdlButtonList::get_selected(float x, float 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(float x, float y)
{
_mouseover = get_selected(x, y);
return _mouseover != nullptr;
}
void SdlButtonList::clear()
{
_list.clear();
_mouseover = nullptr;
_highlighted = nullptr;
_highlight_index = 0;
}
bool SdlButtonList::update()
{
for (auto& btn : _list)
{
if (!btn->highlight(btn == _highlighted))
return false;
if (!btn->mouseover(btn == _mouseover))
return false;
if (!btn->update())
return false;
}
return true;
}

View File

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

View File

@@ -0,0 +1,479 @@
/**
* 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_context.hpp"
#include "res/sdl3_resource_manager.hpp"
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)
{
std::ignore = hide();
}
SDLConnectionDialog::~SDLConnectionDialog()
{
resetTimer();
destroyWindow();
}
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(SdlConnectionDialogWrapper::MSG_NONE);
}
bool SDLConnectionDialog::showInfo(const char* fmt, ...)
{
va_list ap = {};
va_start(ap, fmt);
auto rc = show(SdlConnectionDialogWrapper::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(SdlConnectionDialogWrapper::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(SdlConnectionDialogWrapper::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(SdlConnectionDialogWrapper::MSG_DISCARD);
}
bool SDLConnectionDialog::running() const
{
std::scoped_lock lock(_mux);
return _running;
}
bool SDLConnectionDialog::updateMsg(SdlConnectionDialogWrapper::MsgType type)
{
switch (type)
{
case SdlConnectionDialogWrapper::MSG_INFO:
case SdlConnectionDialogWrapper::MSG_WARN:
case SdlConnectionDialogWrapper::MSG_ERROR:
_type_active = type;
if (!createWindow())
return false;
break;
case SdlConnectionDialogWrapper::MSG_DISCARD:
resetTimer();
destroyWindow();
break;
default:
if (_window)
{
SDL_SetWindowTitle(_window.get(), _title.c_str());
}
break;
}
return true;
}
bool SDLConnectionDialog::setModal()
{
if (_window)
{
auto sdl = get_context(_context);
auto parent = sdl->getFirstWindow();
if (!parent)
return true;
if (!SDL_SetWindowParent(_window.get(), parent->window()))
return false;
if (!SDL_SetWindowModal(_window.get(), true))
return false;
if (!SDL_RaiseWindow(_window.get()))
return false;
}
return true;
}
bool SDLConnectionDialog::updateInternal()
{
std::scoped_lock lock(_mux);
for (auto& btn : _list)
{
if (!btn.widget.update_text(_msg))
return false;
}
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.get());
}
switch (event.type)
{
case SDL_EVENT_USER_RETRY_DIALOG:
{
std::scoped_lock lock(_mux);
auto type = static_cast<SdlConnectionDialogWrapper::MsgType>(event.user.code);
return updateMsg(type);
}
case SDL_EVENT_QUIT:
resetTimer();
destroyWindow();
return false;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_KeyboardEvent&>(event);
if (!update())
return false;
switch (event.key.key)
{
case SDLK_RETURN:
case SDLK_RETURN2:
case SDLK_ESCAPE:
case SDLK_KP_ENTER:
if (event.type == SDL_EVENT_KEY_UP)
{
freerdp_abort_connect_context(_context);
std::ignore = sdl_push_quit();
}
break;
case SDLK_TAB:
if (!_buttons.set_highlight_next())
return false;
break;
default:
break;
}
return windowID == ev.windowID;
}
return false;
case SDL_EVENT_MOUSE_MOTION:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_MouseMotionEvent&>(event);
_buttons.set_mouseover(event.button.x, event.button.y);
if (!update())
return false;
return windowID == ev.windowID;
}
return false;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_MouseButtonEvent&>(event);
if (!update())
return false;
auto button = _buttons.get_selected(event.button);
if (button)
{
if (event.type == SDL_EVENT_MOUSE_BUTTON_UP)
{
freerdp_abort_connect_context(_context);
std::ignore = sdl_push_quit();
}
}
return windowID == ev.windowID;
}
return false;
case SDL_EVENT_MOUSE_WHEEL:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_MouseWheelEvent&>(event);
if (!update())
return false;
return windowID == ev.windowID;
}
return false;
case SDL_EVENT_FINGER_UP:
case SDL_EVENT_FINGER_DOWN:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_TouchFingerEvent&>(event);
if (!update())
return false;
return windowID == ev.windowID;
}
return false;
default:
if ((event.type >= SDL_EVENT_WINDOW_FIRST) && (event.type <= SDL_EVENT_WINDOW_LAST))
{
auto& ev = reinterpret_cast<const SDL_WindowEvent&>(event);
switch (ev.type)
{
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
if (windowID == ev.windowID)
{
freerdp_abort_connect_context(_context);
std::ignore = sdl_push_quit();
}
break;
default:
if (!update())
return false;
if (!setModal())
return false;
break;
}
return windowID == ev.windowID;
}
return false;
}
}
bool SDLConnectionDialog::visible() const
{
std::scoped_lock lock(_mux);
return SdlWidgetList::visible();
}
bool SDLConnectionDialog::createWindow()
{
destroyWindow();
const size_t widget_height = 50;
const size_t widget_width = 600;
const size_t total_height = 300;
if (!reset(_title, widget_width, total_height))
return false;
if (!setModal())
return false;
SDL_Color res_bgcolor;
switch (_type_active)
{
case SdlConnectionDialogWrapper::MSG_INFO:
res_bgcolor = infocolor;
break;
case SdlConnectionDialogWrapper::MSG_WARN:
res_bgcolor = warncolor;
break;
case SdlConnectionDialogWrapper::MSG_ERROR:
res_bgcolor = errorcolor;
break;
case SdlConnectionDialogWrapper::MSG_DISCARD:
default:
res_bgcolor = _backgroundcolor;
break;
}
#if defined(WITH_SDL_IMAGE_DIALOGS)
std::string res_name;
switch (_type_active)
{
case SdlConnectionDialogWrapper::MSG_INFO:
res_name = "icon_info.svg";
break;
case SdlConnectionDialogWrapper::MSG_WARN:
res_name = "icon_warning.svg";
break;
case SdlConnectionDialogWrapper::MSG_ERROR:
res_name = "icon_error.svg";
break;
case SdlConnectionDialogWrapper::MSG_DISCARD:
default:
res_name = "";
break;
}
const auto height = (total_height - 3.0f * vpadding) / 2.0f;
SDL_FRect iconRect{ hpadding, vpadding, widget_width / 4.0f - 2.0f * hpadding, height };
widget_cfg_t icon{ textcolor,
res_bgcolor,
{ _renderer, iconRect,
SDL3ResourceManager::get(SDLResourceManager::typeImages(), res_name) } };
_list.emplace_back(std::move(icon));
iconRect.y += height;
widget_cfg_t logo{ textcolor,
_backgroundcolor,
{ _renderer, iconRect,
SDL3ResourceManager::get(SDLResourceManager::typeImages(),
"FreeRDP_Icon.svg") } };
_list.emplace_back(std::move(logo));
SDL_FRect rect = { widget_width / 4.0f, vpadding, widget_width * 3.0f / 4.0f,
total_height - 3ul * vpadding - widget_height };
#else
SDL_FRect rect = { hpadding, vpadding, widget_width - 2ul * hpadding,
total_height - 2ul * vpadding };
#endif
widget_cfg_t w{ textcolor, _backgroundcolor, { _renderer, rect } };
if (!w.widget.set_wrap(true, widget_width))
return false;
_list.emplace_back(std::move(w));
rect.y += widget_height + vpadding;
const std::vector<int> buttonids = { 1 };
const std::vector<std::string> buttonlabels = { "cancel" };
if (!_buttons.populate(_renderer, buttonlabels, buttonids, widget_width,
total_height - widget_height - vpadding,
static_cast<Sint32>(widget_width / 2),
static_cast<Sint32>(widget_height)))
return false;
if (!_buttons.set_highlight(0))
return false;
if (!SDL_ShowWindow(_window.get()))
return false;
if (!SDL_RaiseWindow(_window.get()))
return false;
return true;
}
void SDLConnectionDialog::destroyWindow()
{
_buttons.clear();
_list.clear();
_renderer = nullptr;
_window = nullptr;
}
bool SDLConnectionDialog::show(SdlConnectionDialogWrapper::MsgType type, const char* fmt,
va_list ap)
{
std::scoped_lock lock(_mux);
_msg = print(fmt, ap);
return show(type);
}
bool SDLConnectionDialog::show(SdlConnectionDialogWrapper::MsgType type)
{
if (SDL_IsMainThread())
return updateMsg(type);
else
return sdl_push_user_event(SDL_EVENT_USER_RETRY_DIALOG, type);
}
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(uint32_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(void* pvthis, [[maybe_unused]] SDL_TimerID timerID,
[[maybe_unused]] Uint32 intervalMS)
{
auto self = static_cast<SDLConnectionDialog*>(pvthis);
std::ignore = self->hide();
self->_running = false;
return 0;
}

View File

@@ -0,0 +1,95 @@
/**
* 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 <SDL3/SDL.h>
#include "sdl_buttons.hpp"
#include "sdl_connection_dialog_wrapper.hpp"
#include "sdl_widget.hpp"
#include "sdl_widget_list.hpp"
class SDLConnectionDialog : public SdlWidgetList
{
public:
explicit SDLConnectionDialog(rdpContext* context);
SDLConnectionDialog(const SDLConnectionDialog& other) = delete;
SDLConnectionDialog(const SDLConnectionDialog&& other) = delete;
~SDLConnectionDialog() override;
SDLConnectionDialog& operator=(const SDLConnectionDialog& other) = delete;
SDLConnectionDialog& operator=(SDLConnectionDialog&& other) = delete;
[[nodiscard]] bool setTitle(const char* fmt, ...);
[[nodiscard]] bool showInfo(const char* fmt, ...);
[[nodiscard]] bool showWarn(const char* fmt, ...);
[[nodiscard]] bool showError(const char* fmt, ...);
[[nodiscard]] bool show();
[[nodiscard]] bool hide();
[[nodiscard]] bool running() const;
[[nodiscard]] bool wait(bool ignoreRdpContextQuit = false);
[[nodiscard]] bool handle(const SDL_Event& event);
[[nodiscard]] bool visible() const override;
protected:
[[nodiscard]] bool updateInternal() override;
private:
[[nodiscard]] bool createWindow();
void destroyWindow();
[[nodiscard]] bool updateMsg(SdlConnectionDialogWrapper::MsgType type);
[[nodiscard]] bool setModal();
[[nodiscard]] bool show(SdlConnectionDialogWrapper::MsgType type, const char* fmt, va_list ap);
[[nodiscard]] bool show(SdlConnectionDialogWrapper::MsgType type);
[[nodiscard]] static std::string print(const char* fmt, va_list ap);
[[nodiscard]] bool setTimer(Uint32 timeoutMS = 15000);
void resetTimer();
[[nodiscard]] static Uint32 timeout(void* pvthis, SDL_TimerID timerID, Uint32 intervalMS);
struct widget_cfg_t
{
SDL_Color fgcolor = {};
SDL_Color bgcolor = {};
SdlWidget widget;
};
rdpContext* _context = nullptr;
mutable std::mutex _mux;
std::string _title;
std::string _msg;
SdlConnectionDialogWrapper::MsgType _type_active = SdlConnectionDialogWrapper::MSG_NONE;
SDL_TimerID _timer = 0;
bool _running = false;
std::vector<widget_cfg_t> _list;
};

View File

@@ -0,0 +1,13 @@
#include "sdl_connection_dialog_hider.hpp"
#include "../sdl_context.hpp"
SDLConnectionDialogHider::SDLConnectionDialogHider(SdlContext* sdl)
: _sdl(sdl), _visible(_sdl->getDialog().isVisible())
{
_sdl->getDialog().show(false);
}
SDLConnectionDialogHider::~SDLConnectionDialogHider()
{
_sdl->getDialog().show(_visible);
}

View File

@@ -0,0 +1,39 @@
/**
* 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 "../sdl_types.hpp"
class SDLConnectionDialogHider
{
public:
explicit SDLConnectionDialogHider(SdlContext* sdl);
SDLConnectionDialogHider(const SDLConnectionDialogHider& other) = delete;
SDLConnectionDialogHider(SDLConnectionDialogHider&& other) = delete;
SDLConnectionDialogHider& operator=(const SDLConnectionDialogHider& other) = delete;
SDLConnectionDialogHider& operator=(SDLConnectionDialogHider&& other) = delete;
~SDLConnectionDialogHider();
private:
SdlContext* _sdl = nullptr;
bool _visible = false;
};

View File

@@ -0,0 +1,287 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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 <sstream>
#include <freerdp/freerdp.h>
#include <freerdp/settings.h>
#include <freerdp/log.h>
#include "../sdl_utils.hpp"
#include "sdl_connection_dialog.hpp"
#include "sdl_connection_dialog_wrapper.hpp"
SdlConnectionDialogWrapper::SdlConnectionDialogWrapper(wLog* log) : _log(log)
{
}
SdlConnectionDialogWrapper::~SdlConnectionDialogWrapper() = default;
void SdlConnectionDialogWrapper::create(rdpContext* context)
{
const auto enabled =
freerdp_settings_get_bool(context->settings, FreeRDP_UseCommonStdioCallbacks);
_connection_dialog.reset();
if (!enabled)
_connection_dialog = std::make_unique<SDLConnectionDialog>(context);
}
void SdlConnectionDialogWrapper::destroy()
{
_connection_dialog.reset();
}
bool SdlConnectionDialogWrapper::isRunning() const
{
std::unique_lock lock(_mux);
if (!_connection_dialog)
return false;
return _connection_dialog->running();
}
bool SdlConnectionDialogWrapper::isVisible() const
{
std::unique_lock lock(_mux);
if (!_connection_dialog)
return false;
return _connection_dialog->visible();
}
bool SdlConnectionDialogWrapper::handleEvent(const SDL_Event& event)
{
std::unique_lock lock(_mux);
if (!_connection_dialog)
return false;
return _connection_dialog->handle(event);
}
WINPR_ATTR_FORMAT_ARG(1, 0)
static std::string format(WINPR_FORMAT_ARG const char* fmt, va_list ap)
{
va_list ap1 = {};
va_copy(ap1, ap);
const int size = vsnprintf(nullptr, 0, fmt, ap1);
va_end(ap1);
if (size < 0)
return "";
std::string msg;
msg.resize(static_cast<size_t>(size) + 1);
va_list ap2 = {};
va_copy(ap2, ap);
std::ignore = vsnprintf(msg.data(), msg.size(), fmt, ap2);
va_end(ap2);
return msg;
}
void SdlConnectionDialogWrapper::setTitle(const char* fmt, ...)
{
va_list ap = {};
va_start(ap, fmt);
setTitle(format(fmt, ap));
va_end(ap);
}
void SdlConnectionDialogWrapper::setTitle(const std::string& title)
{
push(EventArg{ title });
}
void SdlConnectionDialogWrapper::showInfo(const char* fmt, ...)
{
va_list ap = {};
va_start(ap, fmt);
showInfo(format(fmt, ap));
va_end(ap);
}
void SdlConnectionDialogWrapper::showInfo(const std::string& info)
{
show(MSG_INFO, info);
}
void SdlConnectionDialogWrapper::showWarn(const char* fmt, ...)
{
va_list ap = {};
va_start(ap, fmt);
showWarn(format(fmt, ap));
va_end(ap);
}
void SdlConnectionDialogWrapper::showWarn(const std::string& info)
{
show(MSG_WARN, info);
}
void SdlConnectionDialogWrapper::showError(const char* fmt, ...)
{
va_list ap = {};
va_start(ap, fmt);
showError(format(fmt, ap));
va_end(ap);
}
void SdlConnectionDialogWrapper::showError(const std::string& error)
{
show(MSG_ERROR, error);
}
void SdlConnectionDialogWrapper::show(SdlConnectionDialogWrapper::MsgType type,
const std::string& msg)
{
push({ type, msg, true });
}
void SdlConnectionDialogWrapper::show(bool visible)
{
push(EventArg{ visible });
}
void SdlConnectionDialogWrapper::handleShow()
{
std::unique_lock lock(_mux);
while (!_queue.empty())
{
auto arg = _queue.front();
_queue.pop();
if (arg.hasTitle() && _connection_dialog)
{
std::ignore = _connection_dialog->setTitle(arg.title().c_str());
}
if (arg.hasType() && arg.hasMessage())
{
switch (arg.type())
{
case SdlConnectionDialogWrapper::MSG_INFO:
if (_connection_dialog)
std::ignore = _connection_dialog->showInfo(arg.message().c_str());
else
WLog_Print(_log, WLOG_INFO, "%s", arg.message().c_str());
break;
case SdlConnectionDialogWrapper::MSG_WARN:
if (_connection_dialog)
std::ignore = _connection_dialog->showWarn(arg.message().c_str());
else
WLog_Print(_log, WLOG_WARN, "%s", arg.message().c_str());
break;
case SdlConnectionDialogWrapper::MSG_ERROR:
if (_connection_dialog)
std::ignore = _connection_dialog->showError(arg.message().c_str());
else
WLog_Print(_log, WLOG_ERROR, "%s", arg.message().c_str());
break;
default:
break;
}
}
if (arg.hasVisibility() && _connection_dialog)
{
if (arg.visible())
std::ignore = _connection_dialog->show();
else
std::ignore = _connection_dialog->hide();
}
}
}
void SdlConnectionDialogWrapper::push(EventArg&& arg)
{
{
std::unique_lock lock(_mux);
_queue.push(std::move(arg));
}
auto rc = SDL_RunOnMainThread(
[](void* user)
{
auto dlg = static_cast<SdlConnectionDialogWrapper*>(user);
dlg->handleShow();
},
this, false);
if (!rc)
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[%s] SDL_RunOnMainThread failed with %s",
__func__, SDL_GetError());
}
SdlConnectionDialogWrapper::EventArg::EventArg(bool visible) : _visible(visible), _mask(8)
{
}
SdlConnectionDialogWrapper::EventArg::EventArg(const std::string& title) : _title(title), _mask(1)
{
}
SdlConnectionDialogWrapper::EventArg::EventArg(MsgType type, const std::string& msg, bool visible)
: _message(msg), _type(type), _visible(visible), _mask(14)
{
}
bool SdlConnectionDialogWrapper::EventArg::hasTitle() const
{
return _mask & 0x01;
}
const std::string& SdlConnectionDialogWrapper::EventArg::title() const
{
return _title;
}
bool SdlConnectionDialogWrapper::EventArg::hasMessage() const
{
return _mask & 0x02;
}
const std::string& SdlConnectionDialogWrapper::EventArg::message() const
{
return _message;
}
bool SdlConnectionDialogWrapper::EventArg::hasType() const
{
return _mask & 0x04;
}
SdlConnectionDialogWrapper::MsgType SdlConnectionDialogWrapper::EventArg::type() const
{
return _type;
}
bool SdlConnectionDialogWrapper::EventArg::hasVisibility() const
{
return _mask & 0x08;
}
bool SdlConnectionDialogWrapper::EventArg::visible() const
{
return _visible;
}
std::string SdlConnectionDialogWrapper::EventArg::str() const
{
std::stringstream ss;
ss << "{ title:" << _title << ", message:" << _message << ", type:" << _type
<< ", visible:" << _visible << ", mask:" << _mask << "}";
return ss.str();
}

View File

@@ -0,0 +1,122 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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.
*/
#pragma once
#include <mutex>
#include <memory>
#include <string>
#include <queue>
#include <winpr/wlog.h>
#include <winpr/platform.h>
#include <freerdp/types.h>
#include <SDL3/SDL.h>
class SDLConnectionDialog;
class SdlConnectionDialogWrapper
{
public:
enum MsgType
{
MSG_NONE,
MSG_INFO,
MSG_WARN,
MSG_ERROR,
MSG_DISCARD
};
explicit SdlConnectionDialogWrapper(wLog* log);
~SdlConnectionDialogWrapper();
SdlConnectionDialogWrapper(const SdlConnectionDialogWrapper& other) = delete;
SdlConnectionDialogWrapper(SdlConnectionDialogWrapper&& other) = delete;
SdlConnectionDialogWrapper& operator=(const SdlConnectionDialogWrapper& other) = delete;
SdlConnectionDialogWrapper& operator=(SdlConnectionDialogWrapper&& other) = delete;
void create(rdpContext* context);
void destroy();
[[nodiscard]] bool isRunning() const;
[[nodiscard]] bool isVisible() const;
[[nodiscard]] bool handleEvent(const SDL_Event& event);
WINPR_ATTR_FORMAT_ARG(2, 3)
void setTitle(WINPR_FORMAT_ARG const char* fmt, ...);
void setTitle(const std::string& title);
WINPR_ATTR_FORMAT_ARG(2, 3)
void showInfo(WINPR_FORMAT_ARG const char* fmt, ...);
void showInfo(const std::string& info);
WINPR_ATTR_FORMAT_ARG(2, 3)
void showWarn(WINPR_FORMAT_ARG const char* fmt, ...);
void showWarn(const std::string& info);
WINPR_ATTR_FORMAT_ARG(2, 3)
void showError(WINPR_FORMAT_ARG const char* fmt, ...);
void showError(const std::string& error);
void show(SdlConnectionDialogWrapper::MsgType type, const std::string& msg);
void show(bool visible = true);
void handleShow();
private:
class EventArg
{
public:
explicit EventArg(bool visible);
explicit EventArg(const std::string& title);
EventArg(SdlConnectionDialogWrapper::MsgType type, const std::string& msg, bool visible);
[[nodiscard]] bool hasTitle() const;
[[nodiscard]] const std::string& title() const;
[[nodiscard]] bool hasMessage() const;
[[nodiscard]] const std::string& message() const;
[[nodiscard]] bool hasType() const;
[[nodiscard]] SdlConnectionDialogWrapper::MsgType type() const;
[[nodiscard]] bool hasVisibility() const;
[[nodiscard]] bool visible() const;
[[nodiscard]] std::string str() const;
private:
std::string _title;
std::string _message;
SdlConnectionDialogWrapper::MsgType _type = MSG_NONE;
bool _visible = false;
uint32_t _mask = 0;
};
void push(EventArg&& arg);
mutable std::mutex _mux;
std::unique_ptr<SDLConnectionDialog> _connection_dialog;
std::queue<EventArg> _queue;
wLog* _log = nullptr;
};

View File

@@ -0,0 +1,648 @@
/**
* 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 <SDL3/SDL.h>
#include "../sdl_context.hpp"
#include "sdl_dialogs.hpp"
#include "sdl_input_widget_pair.hpp"
#include "sdl_input_widget_pair_list.hpp"
#include "sdl_select.hpp"
#include "sdl_select_list.hpp"
#include "sdl_connection_dialog.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_EVENT_USER_SHOW_DIALOG, title, message, flags))
return 0;
if (!sdl_wait_for_result(context, SDL_EVENT_USER_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;
const char* target = freerdp_settings_get_server_name(instance->context->settings);
switch (reason)
{
case AUTH_RDSTLS:
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 guard(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_EVENT_USER_AUTH_DIALOG, title, u, d, p, reason))
return res;
if (!sdl_wait_for_result(instance->context, SDL_EVENT_USER_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);
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_EVENT_USER_SCARD_DIALOG, title, list.data(), count))
return res;
if (!sdl_wait_for_result(instance->context, SDL_EVENT_USER_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 BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
sdl->getDialog().setTitle("Retry connection to %s",
freerdp_settings_get_server_name(instance->context->settings));
if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0))
{
sdl->getDialog().showError("Unknown module %s, aborting", what);
return -1;
}
if (current == 0)
{
if (strcmp(what, "arm-transport") == 0)
sdl->getDialog().showWarn("[%s] Starting your VM. It may take up to 5 minutes", what);
}
if (!enabled)
{
sdl->getDialog().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->getDialog().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->getDialog().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);
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;
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)
{
if (!sdl_push_user_event(SDL_EVENT_USER_CERT_DIALOG, title, message))
return 0;
SDL_Event event = {};
if (!sdl_wait_for_result(context, SDL_EVENT_USER_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);
/* 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);
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_EVENT_USER_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_EVENT_USER_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_RDSTLS:
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 = { SdlInputWidgetPair::SDL_INPUT_READONLY,
SdlInputWidgetPair::SDL_INPUT_MASK };
if (args->result != AUTH_SMARTCARD_PIN)
{
if (args->result == AUTH_RDSTLS)
{
initial = { args->user ? args->user : "", args->password ? args->password : "" };
flags = { 0, SdlInputWidgetPair::SDL_INPUT_MASK };
}
else
{
initial = { args->user ? args->user : "", args->domain ? args->domain : "",
args->password ? args->password : "" };
flags = { 0, 0, SdlInputWidgetPair::SDL_INPUT_MASK };
}
}
ssize_t selected = -1;
switch (args->result)
{
case AUTH_SMARTCARD_PIN:
case AUTH_RDSTLS:
break;
default:
if (args->user)
{
selected++;
if (args->domain)
selected++;
}
break;
}
SdlInputWidgetPairList ilist(args->title, prompt, initial, flags, selected);
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_EVENT_USER_AUTH_RESULT, user, domain, pwd, rc);
}
BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list)
{
std::vector<std::string> vlist;
vlist.reserve(WINPR_ASSERTING_INT_CAST(size_t, count));
for (Sint32 x = 0; x < count; x++)
vlist.emplace_back(list[x]);
SdlSelectList slist(title, vlist);
Sint32 value = slist.run();
return sdl_push_user_event(SDL_EVENT_USER_SCARD_RESULT, value);
}
void sdl_dialogs_uninit()
{
TTF_Quit();
}
void sdl_dialogs_init()
{
TTF_Init();
}

View File

@@ -0,0 +1,59 @@
/**
* 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"
[[nodiscard]] BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password,
char** domain, rdp_auth_reason reason);
[[nodiscard]] BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list,
DWORD count, DWORD* choice, BOOL gateway);
[[nodiscard]] SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current,
void* userarg);
[[nodiscard]] 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);
[[nodiscard]] 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);
[[nodiscard]] int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type);
[[nodiscard]] BOOL sdl_present_gateway_message(freerdp* instance, UINT32 type,
BOOL isDisplayMandatory, BOOL isConsentMandatory,
size_t length, const WCHAR* message);
[[nodiscard]] BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags);
[[nodiscard]] BOOL sdl_cert_dialog_show(const char* title, const char* message);
[[nodiscard]] BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list);
[[nodiscard]] BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args);
void sdl_dialogs_init();
void sdl_dialogs_uninit();

View File

@@ -0,0 +1,52 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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 "sdl_input_widget.hpp"
SdlInputWidget::SdlInputWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect)
: SdlSelectableWidget(renderer, rect)
{
init();
}
SdlInputWidget::~SdlInputWidget() = default;
std::string SdlInputWidget::text() const
{
return _text;
}
void SdlInputWidget::init()
{
_backgroundcolor = { 0x56, 0x56, 0x56, 0xff };
_fontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
_highlightcolor = { 0x80, 0, 0, 0x60 };
_mouseovercolor = { 0, 0x80, 0, 0x60 };
}
#if defined(WITH_SDL_IMAGE_DIALOGS)
SdlInputWidget::SdlInputWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect,
SDL_IOStream* ops)
: SdlSelectableWidget(renderer, rect, ops)
{
init();
}
#endif
SdlInputWidget::SdlInputWidget(SdlInputWidget&& other) noexcept = default;

View File

@@ -0,0 +1,43 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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.
*/
#pragma once
#include "sdl_selectable_widget.hpp"
class SdlInputWidget : public SdlSelectableWidget
{
public:
SdlInputWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect);
SdlInputWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect,
SDL_IOStream* ops);
SdlInputWidget(SdlInputWidget&& other) noexcept;
SdlInputWidget(const SdlInputWidget& other) = delete;
SdlInputWidget& operator=(const SdlInputWidget& other) = delete;
SdlInputWidget& operator=(SdlInputWidget&& other) noexcept = delete;
~SdlInputWidget() override;
[[nodiscard]] std::string text() const;
private:
void init();
};

View File

@@ -0,0 +1,129 @@
/**
* 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_widget_pair.hpp"
#include <cassert>
#include <algorithm>
#include <string>
#include <utility>
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include "sdl_widget.hpp"
#include "sdl_button.hpp"
#include "sdl_buttons.hpp"
SdlInputWidgetPair::SdlInputWidgetPair(std::shared_ptr<SDL_Renderer>& renderer,
const std::string& label, const std::string& initial,
Uint32 flags, size_t offset, size_t width, size_t height)
: _flags(flags), _label(renderer, { 0, static_cast<float>(offset * (height + _vpadding)),
static_cast<float>(width), static_cast<float>(height) }),
_input(renderer, { static_cast<float>(width + _hpadding),
static_cast<float>(offset * (height + _vpadding)),
static_cast<float>(width), static_cast<float>(height) })
{
std::ignore = _label.update_text(label);
std::ignore = update_input_text(initial);
}
SdlInputWidgetPair::SdlInputWidgetPair(SdlInputWidgetPair&& other) noexcept = default;
bool SdlInputWidgetPair::set_mouseover(bool mouseOver)
{
if (readonly())
return true;
return _input.mouseover(mouseOver);
}
bool SdlInputWidgetPair::set_highlight(bool highlight)
{
if (readonly())
return true;
return _input.highlight(highlight);
}
bool SdlInputWidgetPair::set_str(const std::string& text)
{
if (readonly())
return true;
return update_input_text(text);
}
bool SdlInputWidgetPair::remove_str(size_t count)
{
if (readonly())
return true;
auto text = _text;
if (text.empty())
return true;
auto newsize = text.size() - std::min<size_t>(text.size(), count);
return update_input_text(text.substr(0, newsize));
}
bool SdlInputWidgetPair::append_str(const std::string& text)
{
if (readonly())
return true;
auto itext = _text;
itext.append(text);
return update_input_text(itext);
}
const SDL_FRect& SdlInputWidgetPair::input_rect() const
{
return _input.rect();
}
std::string SdlInputWidgetPair::value() const
{
return _text;
}
bool SdlInputWidgetPair::readonly() const
{
return (_flags & SDL_INPUT_READONLY) != 0;
}
bool SdlInputWidgetPair::update()
{
// TODO: Draw the pair area
if (!_label.update())
return false;
if (!_input.update())
return false;
return true;
}
bool SdlInputWidgetPair::update_input_text(const std::string& txt)
{
_text = txt;
auto text = txt;
if (_flags & SDL_INPUT_MASK)
{
std::fill(text.begin(), text.end(), '*');
}
return _input.update_text(text);
}

View File

@@ -0,0 +1,73 @@
/**
* 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 <string>
#include <SDL3/SDL.h>
#include "sdl_widget.hpp"
#include "sdl_input_widget.hpp"
class SdlInputWidgetPair
{
public:
enum
{
SDL_INPUT_MASK = 1,
SDL_INPUT_READONLY = 2
};
SdlInputWidgetPair(std::shared_ptr<SDL_Renderer>& renderer, const std::string& label,
const std::string& initial, Uint32 flags, size_t offset, size_t width,
size_t height);
SdlInputWidgetPair(SdlInputWidgetPair&& other) noexcept;
SdlInputWidgetPair(const SdlInputWidgetPair& other) = delete;
~SdlInputWidgetPair() = default;
SdlInputWidgetPair& operator=(const SdlInputWidgetPair& other) = delete;
SdlInputWidgetPair& operator=(SdlInputWidgetPair&& other) = delete;
bool set_mouseover(bool mouseOver);
bool set_highlight(bool highlight);
[[nodiscard]] bool set_str(const std::string& text);
[[nodiscard]] bool remove_str(size_t count);
[[nodiscard]] bool append_str(const std::string& text);
[[nodiscard]] const SDL_FRect& input_rect() const;
[[nodiscard]] std::string value() const;
[[nodiscard]] bool readonly() const;
[[nodiscard]] bool update();
protected:
[[nodiscard]] bool update_input_text(const std::string& txt);
Uint32 _vpadding = 5;
Uint32 _hpadding = 10;
private:
Uint32 _flags{};
SdlWidget _label;
SdlInputWidget _input;
std::string _text;
};

View File

@@ -0,0 +1,313 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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 <algorithm>
#include <winpr/cast.h>
#include "sdl_widget_list.hpp"
#include "sdl_input_widget_pair_list.hpp"
static const Uint32 vpadding = 5;
SdlInputWidgetPairList::SdlInputWidgetPairList(const std::string& title,
const std::vector<std::string>& labels,
const std::vector<std::string>& initial,
const std::vector<Uint32>& flags, ssize_t selected)
{
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);
if (reset(title, total_width, total_height))
{
for (size_t x = 0; x < labels.size(); x++)
{
auto widget =
std::make_shared<SdlInputWidgetPair>(_renderer, labels.at(x), initial.at(x),
flags.at(x), x, widget_width, widget_heigth);
m_list.emplace_back(widget);
}
std::ignore = _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);
m_currentActiveTextInput = selected;
}
}
ssize_t SdlInputWidgetPairList::next(ssize_t current)
{
size_t iteration = 0;
auto val = static_cast<size_t>(current);
do
{
if (iteration >= m_list.size())
return -1;
if (iteration == 0)
{
if (current < 0)
val = 0;
else
val++;
}
else
val++;
iteration++;
val %= m_list.size();
} while (!valid(static_cast<ssize_t>(val)));
return static_cast<ssize_t>(val);
}
bool SdlInputWidgetPairList::valid(ssize_t current) const
{
if (current < 0)
return false;
auto s = static_cast<size_t>(current);
if (s >= m_list.size())
return false;
return !m_list.at(s)->readonly();
}
std::shared_ptr<SdlInputWidgetPair> SdlInputWidgetPairList::get(ssize_t index)
{
if (index < 0)
return nullptr;
auto s = static_cast<size_t>(index);
if (s >= m_list.size())
return nullptr;
return m_list.at(s);
}
SdlInputWidgetPairList::~SdlInputWidgetPairList()
{
m_list.clear();
_buttons.clear();
}
bool SdlInputWidgetPairList::updateInternal()
{
for (auto& btn : m_list)
{
if (!btn->update())
return false;
if (!btn->update())
return false;
}
return true;
}
ssize_t SdlInputWidgetPairList::get_index(const SDL_MouseButtonEvent& button)
{
const auto x = button.x;
const auto y = button.y;
for (size_t i = 0; i < m_list.size(); i++)
{
auto& cur = m_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 WINPR_ASSERTING_INT_CAST(ssize_t, i);
}
return -1;
}
int SdlInputWidgetPairList::run(std::vector<std::string>& result)
{
int res = -1;
ssize_t LastActiveTextInput = -1;
m_currentActiveTextInput = next(m_currentActiveTextInput);
if (!_window || !_renderer)
return -2;
if (!SDL_StartTextInput(_window.get()))
return -3;
try
{
bool running = true;
while (running)
{
if (!update())
throw;
SDL_Event event = {};
if (!SDL_WaitEvent(&event))
throw;
do
{
switch (event.type)
{
case SDL_EVENT_KEY_UP:
{
switch (event.key.key)
{
case SDLK_BACKSPACE:
{
auto cur = get(m_currentActiveTextInput);
if (cur)
{
if ((event.key.mod & SDL_KMOD_CTRL) != 0)
{
if (!cur->set_str(""))
throw;
}
else
{
if (!cur->remove_str(1))
throw;
}
}
}
break;
case SDLK_TAB:
m_currentActiveTextInput = next(m_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 ((event.key.mod & SDL_KMOD_CTRL) != 0)
{
auto cur = get(m_currentActiveTextInput);
if (cur)
{
auto text = SDL_GetClipboardText();
if (!cur->set_str(text))
throw;
}
}
break;
default:
break;
}
}
break;
case SDL_EVENT_TEXT_INPUT:
{
auto cur = get(m_currentActiveTextInput);
if (cur)
{
if (!cur->append_str(event.text.text))
throw;
}
}
break;
case SDL_EVENT_MOUSE_MOTION:
{
auto TextInputIndex = get_index(event.button);
for (auto& cur : m_list)
{
cur->set_mouseover(false);
}
if (TextInputIndex >= 0)
{
auto& cur = m_list.at(static_cast<size_t>(TextInputIndex));
cur->set_mouseover(true);
}
_buttons.set_mouseover(event.button.x, event.button.y);
}
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
{
auto val = get_index(event.button);
if (valid(val))
m_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_EVENT_QUIT:
res = INPUT_BUTTON_CANCEL;
running = false;
break;
default:
break;
}
} while (SDL_PollEvent(&event));
if (LastActiveTextInput != m_currentActiveTextInput)
{
LastActiveTextInput = m_currentActiveTextInput;
}
for (auto& cur : m_list)
{
if (!cur->set_highlight(false))
throw;
}
auto cur = get(m_currentActiveTextInput);
if (cur)
{
if (!cur->set_highlight(true))
throw;
}
auto rc = SDL_RenderPresent(_renderer.get());
if (!rc)
{
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[%s] SDL_RenderPresent failed with %s",
__func__, SDL_GetError());
}
}
for (auto& cur : m_list)
result.push_back(cur->value());
}
catch (...)
{
res = -2;
}
if (!SDL_StopTextInput(_window.get()))
return -4;
return res;
}

View File

@@ -0,0 +1,63 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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.
*/
#pragma once
#include <string>
#include <vector>
#include <SDL3/SDL.h>
#include "sdl_widget_list.hpp"
#include "sdl_input_widget_pair.hpp"
#include "sdl_buttons.hpp"
class SdlInputWidgetPairList : public SdlWidgetList
{
public:
SdlInputWidgetPairList(const std::string& title, const std::vector<std::string>& labels,
const std::vector<std::string>& initial,
const std::vector<Uint32>& flags, ssize_t selected = -1);
SdlInputWidgetPairList(const SdlInputWidgetPairList& other) = delete;
SdlInputWidgetPairList(SdlInputWidgetPairList&& other) = delete;
~SdlInputWidgetPairList() override;
SdlInputWidgetPairList& operator=(const SdlInputWidgetPairList& other) = delete;
SdlInputWidgetPairList& operator=(SdlInputWidgetPairList&& other) = delete;
[[nodiscard]] int run(std::vector<std::string>& result);
protected:
[[nodiscard]] bool updateInternal() override;
[[nodiscard]] ssize_t get_index(const SDL_MouseButtonEvent& button);
private:
enum
{
INPUT_BUTTON_ACCEPT = 1,
INPUT_BUTTON_CANCEL = -2
};
[[nodiscard]] ssize_t next(ssize_t current);
[[nodiscard]] bool valid(ssize_t current) const;
[[nodiscard]] std::shared_ptr<SdlInputWidgetPair> get(ssize_t index);
std::vector<std::shared_ptr<SdlInputWidgetPair>> m_list;
ssize_t m_currentActiveTextInput = -1;
};

View File

@@ -0,0 +1,45 @@
/**
* 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 <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include "sdl_select.hpp"
#include "sdl_widget.hpp"
#include "sdl_button.hpp"
#include "sdl_buttons.hpp"
#include "sdl_input_widget_pair_list.hpp"
SdlSelectWidget::SdlSelectWidget(std::shared_ptr<SDL_Renderer>& renderer, const std::string& label,
const SDL_FRect& rect)
: SdlSelectableWidget(renderer, rect)
{
_backgroundcolor = { 0x69, 0x66, 0x63, 0xff };
_fontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
std::ignore = update_text(label);
}
SdlSelectWidget::~SdlSelectWidget() = default;
SdlSelectWidget::SdlSelectWidget(SdlSelectWidget&& other) noexcept = default;

View File

@@ -0,0 +1,39 @@
/**
* 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 <SDL3/SDL.h>
#include "sdl_selectable_widget.hpp"
class SdlSelectWidget : public SdlSelectableWidget
{
public:
SdlSelectWidget(std::shared_ptr<SDL_Renderer>& renderer, const std::string& label,
const SDL_FRect& rect);
SdlSelectWidget(SdlSelectWidget&& other) noexcept;
SdlSelectWidget(const SdlSelectWidget& other) = delete;
~SdlSelectWidget() override;
SdlSelectWidget& operator=(const SdlSelectWidget& other) = delete;
SdlSelectWidget& operator=(SdlSelectWidget&& other) = delete;
};

View File

@@ -0,0 +1,200 @@
#include <cassert>
#include <winpr/cast.h>
#include "sdl_select_list.hpp"
#include "../sdl_utils.hpp"
static const Uint32 vpadding = 5;
SdlSelectList::SdlSelectList(const std::string& title, const std::vector<std::string>& labels)
{
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;
if (reset(title, widget_width, height))
{
SDL_FRect 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" };
std::ignore = _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() = default;
int SdlSelectList::run()
{
int res = -2;
ssize_t CurrentActiveTextInput = 0;
bool running = true;
if (!_window || !_renderer)
return -2;
try
{
while (running)
{
if (!update())
throw;
SDL_Event event = {};
if (!SDL_WaitEvent(&event))
throw;
do
{
switch (event.type)
{
case SDL_EVENT_KEY_DOWN:
switch (event.key.key)
{
case SDLK_UP:
case SDLK_BACKSPACE:
if (CurrentActiveTextInput > 0)
CurrentActiveTextInput--;
else if (_list.empty())
CurrentActiveTextInput = 0;
else
{
auto s = _list.size();
CurrentActiveTextInput =
WINPR_ASSERTING_INT_CAST(ssize_t, s) - 1;
}
break;
case SDLK_DOWN:
case SDLK_TAB:
if ((CurrentActiveTextInput < 0) || _list.empty())
CurrentActiveTextInput = 0;
else
{
auto s = _list.size();
CurrentActiveTextInput++;
if (s > 0)
{
CurrentActiveTextInput =
CurrentActiveTextInput %
WINPR_ASSERTING_INT_CAST(ssize_t, s);
}
}
break;
case SDLK_RETURN:
case SDLK_RETURN2:
case SDLK_KP_ENTER:
running = false;
res = static_cast<int>(CurrentActiveTextInput);
break;
case SDLK_ESCAPE:
running = false;
res = INPUT_BUTTON_CANCEL;
break;
default:
break;
}
break;
case SDL_EVENT_MOUSE_MOTION:
{
auto TextInputIndex = get_index(event.button);
reset_mouseover();
if (TextInputIndex >= 0)
{
auto& cur = _list.at(WINPR_ASSERTING_INT_CAST(size_t, TextInputIndex));
if (!cur.mouseover(true))
throw;
}
_buttons.set_mouseover(event.button.x, event.button.y);
}
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
{
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_EVENT_QUIT:
res = INPUT_BUTTON_CANCEL;
running = false;
break;
default:
break;
}
} while (SDL_PollEvent(&event));
reset_highlight();
if (CurrentActiveTextInput >= 0)
{
auto& cur = _list.at(WINPR_ASSERTING_INT_CAST(size_t, CurrentActiveTextInput));
if (!cur.highlight(true))
throw;
}
if (!update())
throw;
}
}
catch (...)
{
return -1;
}
return res;
}
bool SdlSelectList::updateInternal()
{
for (auto& cur : _list)
{
if (!cur.update())
return false;
}
return true;
}
ssize_t SdlSelectList::get_index(const SDL_MouseButtonEvent& button)
{
const auto x = button.x;
const auto 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;
}
void SdlSelectList::reset_mouseover()
{
for (auto& cur : _list)
{
cur.mouseover(false);
}
}
void SdlSelectList::reset_highlight()
{
for (auto& cur : _list)
{
cur.highlight(false);
}
}

View File

@@ -0,0 +1,41 @@
#pragma once
#include <string>
#include <vector>
#include <SDL3/SDL.h>
#include "sdl_widget_list.hpp"
#include "sdl_select.hpp"
#include "sdl_button.hpp"
#include "sdl_buttons.hpp"
class SdlSelectList : public SdlWidgetList
{
public:
SdlSelectList(const std::string& title, const std::vector<std::string>& labels);
SdlSelectList(const SdlSelectList& other) = delete;
SdlSelectList(SdlSelectList&& other) = delete;
~SdlSelectList() override;
SdlSelectList& operator=(const SdlSelectList& other) = delete;
SdlSelectList& operator=(SdlSelectList&& other) = delete;
[[nodiscard]] int run();
protected:
[[nodiscard]] bool updateInternal() override;
private:
enum
{
INPUT_BUTTON_ACCEPT = 0,
INPUT_BUTTON_CANCEL = -2
};
ssize_t get_index(const SDL_MouseButtonEvent& button);
void reset_mouseover();
void reset_highlight();
std::vector<SdlSelectWidget> _list;
};

View File

@@ -0,0 +1,68 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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 "sdl_selectable_widget.hpp"
#include "sdl_blend_mode_guard.hpp"
SdlSelectableWidget::SdlSelectableWidget(std::shared_ptr<SDL_Renderer>& renderer,
const SDL_FRect& rect)
: SdlWidget(renderer, rect)
{
}
#if defined(WITH_SDL_IMAGE_DIALOGS)
SdlSelectableWidget::SdlSelectableWidget(std::shared_ptr<SDL_Renderer>& renderer,
const SDL_FRect& rect, SDL_IOStream* ops)
: SdlWidget(renderer, rect, ops)
{
}
#endif
SdlSelectableWidget::SdlSelectableWidget(SdlSelectableWidget&& other) noexcept = default;
SdlSelectableWidget::~SdlSelectableWidget() = default;
bool SdlSelectableWidget::highlight(bool enable)
{
_highlight = enable;
return update();
}
bool SdlSelectableWidget::mouseover(bool enable)
{
_mouseover = enable;
return update();
}
bool SdlSelectableWidget::updateInternal()
{
SdlBlendModeGuard guard(_renderer, SDL_BLENDMODE_NONE);
std::vector<SDL_Color> colors;
colors.push_back(_backgroundcolor);
if (_highlight)
colors.push_back(_highlightcolor);
if (_mouseover)
colors.push_back(_mouseovercolor);
if (!fill(colors))
return false;
return SdlWidget::updateInternal();
}

View File

@@ -0,0 +1,50 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 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.
*/
#pragma once
#include "sdl_widget.hpp"
class SdlSelectableWidget : public SdlWidget
{
public:
SdlSelectableWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect);
#if defined(WITH_SDL_IMAGE_DIALOGS)
SdlSelectableWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect,
SDL_IOStream* ops);
#endif
SdlSelectableWidget(SdlSelectableWidget&& other) noexcept;
SdlSelectableWidget(const SdlSelectableWidget& other) = delete;
~SdlSelectableWidget() override;
SdlSelectableWidget& operator=(const SdlSelectableWidget& other) = delete;
SdlSelectableWidget& operator=(SdlSelectableWidget&& other) = delete;
bool highlight(bool enable);
bool mouseover(bool enable);
protected:
[[nodiscard]] bool updateInternal() override;
SDL_Color _highlightcolor = { 0xcd, 0xca, 0x35, 0x60 };
SDL_Color _mouseovercolor = { 0x66, 0xff, 0x66, 0x60 };
private:
bool _mouseover = false;
bool _highlight = false;
};

View File

@@ -0,0 +1,332 @@
/**
* 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 <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include "sdl_widget.hpp"
#include "sdl_blend_mode_guard.hpp"
#include "../sdl_utils.hpp"
#include "res/sdl3_resource_manager.hpp"
#include <freerdp/log.h>
#if defined(WITH_SDL_IMAGE_DIALOGS)
#include <SDL3_image/SDL_image.h>
#endif
#define TAG CLIENT_TAG("SDL.widget")
static const Uint32 hpadding = 10;
SdlWidget::SdlWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect)
: _renderer(renderer),
_engine(TTF_CreateRendererTextEngine(renderer.get()), TTF_DestroyRendererTextEngine),
_rect(rect)
{
assert(renderer);
auto ops = SDL3ResourceManager::get(SDLResourceManager::typeFonts(),
"OpenSans-VariableFont_wdth,wght.ttf");
if (!ops)
widget_log_error(false, "SDLResourceManager::get");
else
{
_font = std::shared_ptr<TTF_Font>(TTF_OpenFontIO(ops, true, 64), TTF_CloseFont);
if (!_font)
widget_log_error(false, "TTF_OpenFontRW");
}
}
#if defined(WITH_SDL_IMAGE_DIALOGS)
SdlWidget::SdlWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect,
SDL_IOStream* ops)
: _renderer(renderer),
_engine(TTF_CreateRendererTextEngine(renderer.get()), TTF_DestroySurfaceTextEngine),
_rect(rect)
{
if (ops)
{
_image = std::shared_ptr<SDL_Texture>(IMG_LoadTexture_IO(renderer.get(), ops, true),
SDL_DestroyTexture);
if (!_image)
widget_log_error(false, "IMG_LoadTexture_IO");
}
}
#endif
SdlWidget::SdlWidget(SdlWidget&& other) noexcept
: _renderer(std::move(other._renderer)), _backgroundcolor(other._backgroundcolor),
_fontcolor(other._fontcolor), _text(std::move(other._text)), _font(std::move(other._font)),
_image(std::move(other._image)), _engine(std::move(other._engine)), _rect(other._rect),
_wrap(other._wrap), _text_width(other._text_width)
{
other._font = nullptr;
other._image = nullptr;
other._engine = nullptr;
}
std::shared_ptr<SDL_Texture> SdlWidget::render_text(const std::string& text, SDL_Color fgcolor,
SDL_FRect& src, SDL_FRect& dst) const
{
auto surface = std::shared_ptr<SDL_Surface>(
TTF_RenderText_Blended(_font.get(), text.c_str(), 0, fgcolor), SDL_DestroySurface);
if (!surface)
{
widget_log_error(false, "TTF_RenderText_Blended");
return nullptr;
}
auto texture = std::shared_ptr<SDL_Texture>(
SDL_CreateTextureFromSurface(_renderer.get(), surface.get()), SDL_DestroyTexture);
if (!texture)
{
widget_log_error(false, "SDL_CreateTextureFromSurface");
return nullptr;
}
if (!_engine)
{
widget_log_error(false, "TTF_CreateRendererTextEngine");
return nullptr;
}
std::unique_ptr<TTF_Text, decltype(&TTF_DestroyText)> txt(
TTF_CreateText(_engine.get(), _font.get(), text.c_str(), text.size()), TTF_DestroyText);
if (!txt)
{
widget_log_error(false, "TTF_CreateText");
return nullptr;
}
int w = 0;
int h = 0;
if (!TTF_GetTextSize(txt.get(), &w, &h))
{
widget_log_error(false, "TTF_GetTextSize");
return nullptr;
}
src.w = static_cast<float>(w);
src.h = static_cast<float>(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 float scale = dst.h / src.h;
const float sws = (src.w) * scale;
const float dws = (dst.w) / scale;
dst.w = std::min(dst.w, sws);
if (src.w > dws)
{
src.x = src.w - dws;
src.w = dws;
}
return texture;
}
static float scale(float dw, float dh)
{
const auto scale = dh / dw;
const auto dr = dh * scale;
return dr;
}
std::shared_ptr<SDL_Texture> SdlWidget::render_text_wrapped(const std::string& text,
SDL_Color fgcolor, SDL_FRect& src,
SDL_FRect& dst) const
{
assert(_text_width < INT32_MAX);
auto surface = std::shared_ptr<SDL_Surface>(
TTF_RenderText_Blended_Wrapped(_font.get(), text.c_str(), 0, fgcolor,
static_cast<int>(_text_width)),
SDL_DestroySurface);
if (!surface)
{
widget_log_error(false, "TTF_RenderText_Blended");
return nullptr;
}
src.w = static_cast<float>(surface->w);
src.h = static_cast<float>(surface->h);
auto texture = std::shared_ptr<SDL_Texture>(
SDL_CreateTextureFromSurface(_renderer.get(), surface.get()), SDL_DestroyTexture);
if (!texture)
{
widget_log_error(false, "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<float>(dh, dst.h);
return texture;
}
SdlWidget::~SdlWidget() = default;
bool SdlWidget::error_ex(bool success, const char* what, const char* file, size_t line,
const char* fkt)
{
if (success)
{
// Flip SDL3 convention to existing code convention to minimize code changes
return false;
}
static wLog* log = nullptr;
if (!log)
log = WLog_Get(TAG);
// Use -1 as it indicates error similar to SDL2 conventions
// sdl_log_error_ex treats any value other than 0 as SDL error
return sdl_log_error_ex(-1, log, what, file, line, fkt);
}
bool SdlWidget::updateInternal()
{
return update_text(_text);
}
bool SdlWidget::draw_rect(const SDL_FRect& rect, SDL_Color color) const
{
const auto drc = SDL_SetRenderDrawColor(_renderer.get(), color.r, color.g, color.b, color.a);
if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
return false;
const auto rc = SDL_RenderFillRect(_renderer.get(), &rect);
return !widget_log_error(rc, "SDL_RenderFillRect");
}
bool SdlWidget::fill(SDL_Color color) const
{
std::vector<SDL_Color> colors = { color };
return fill(colors);
}
bool SdlWidget::fill(const std::vector<SDL_Color>& colors) const
{
SdlBlendModeGuard guard(_renderer, SDL_BLENDMODE_NONE);
for (auto color : colors)
{
if (!draw_rect(_rect, color))
return false;
if (!guard.update(SDL_BLENDMODE_ADD))
return false;
}
return true;
}
bool SdlWidget::wrap() const
{
return _wrap;
}
bool SdlWidget::set_wrap(bool wrap, size_t width)
{
_wrap = wrap;
_text_width = width;
return _wrap;
}
const SDL_FRect& SdlWidget::rect() const
{
return _rect;
}
bool SdlWidget::clear() const
{
if (!_renderer)
return false;
SdlBlendModeGuard guard(_renderer, SDL_BLENDMODE_NONE);
const auto drc = SDL_SetRenderDrawColor(_renderer.get(), _backgroundcolor.r, _backgroundcolor.g,
_backgroundcolor.b, _backgroundcolor.a);
if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
return false;
const auto rcls = SDL_RenderRect(_renderer.get(), &_rect);
return !widget_log_error(rcls, "SDL_RenderRect");
}
bool SdlWidget::update()
{
if (!clear())
return false;
// TODO: Draw widget specifics
return updateInternal();
}
bool SdlWidget::update_text(const std::string& text)
{
_text = text;
if (_text.empty())
return true;
SDL_FRect src{};
SDL_FRect dst{};
std::shared_ptr<SDL_Texture> texture;
if (_image)
{
texture = _image;
dst = _rect;
auto propId = SDL_GetTextureProperties(_image.get());
auto w = SDL_GetNumberProperty(propId, SDL_PROP_TEXTURE_WIDTH_NUMBER, -1);
auto h = SDL_GetNumberProperty(propId, SDL_PROP_TEXTURE_HEIGHT_NUMBER, -1);
if (w < 0 || h < 0)
{
if (!widget_log_error(false, "SDL_GetTextureProperties"))
return false;
}
src.w = static_cast<float>(w);
src.h = static_cast<float>(h);
}
else if (_wrap)
texture = render_text_wrapped(_text, _fontcolor, src, dst);
else
texture = render_text(_text, _fontcolor, src, dst);
if (!texture)
return false;
const auto rc = SDL_RenderTexture(_renderer.get(), texture.get(), &src, &dst);
return !widget_log_error(rc, "SDL_RenderCopy");
}

View File

@@ -0,0 +1,100 @@
/**
* 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 <memory>
#include <vector>
#include <SDL3/SDL.h>
#include <SDL3_ttf/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(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect);
#if defined(WITH_SDL_IMAGE_DIALOGS)
SdlWidget(std::shared_ptr<SDL_Renderer>& renderer, const SDL_FRect& rect, SDL_IOStream* ops);
#endif
SdlWidget(const SdlWidget& other) = delete;
SdlWidget(SdlWidget&& other) noexcept;
virtual ~SdlWidget();
SdlWidget& operator=(const SdlWidget& other) = delete;
SdlWidget& operator=(SdlWidget&& other) = delete;
[[nodiscard]] bool fill(SDL_Color color) const;
[[nodiscard]] bool fill(const std::vector<SDL_Color>& colors) const;
[[nodiscard]] bool update_text(const std::string& text);
[[nodiscard]] bool wrap() const;
[[nodiscard]] bool set_wrap(bool wrap = true, size_t width = 0);
[[nodiscard]] const SDL_FRect& rect() const;
[[nodiscard]] bool update();
#define widget_log_error(res, what) SdlWidget::error_ex(res, what, __FILE__, __LINE__, __func__)
static bool error_ex(bool success, const char* what, const char* file, size_t line,
const char* fkt);
protected:
std::shared_ptr<SDL_Renderer> _renderer;
SDL_Color _backgroundcolor = { 0x56, 0x56, 0x56, 0xff };
SDL_Color _fontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
mutable std::string _text;
virtual bool clear() const;
virtual bool updateInternal();
private:
[[nodiscard]] bool draw_rect(const SDL_FRect& rect, SDL_Color color) const;
[[nodiscard]] std::shared_ptr<SDL_Texture>
render_text(const std::string& text, SDL_Color fgcolor, SDL_FRect& src, SDL_FRect& dst) const;
[[nodiscard]] std::shared_ptr<SDL_Texture> render_text_wrapped(const std::string& text,
SDL_Color fgcolor,
SDL_FRect& src,
SDL_FRect& dst) const;
std::shared_ptr<TTF_Font> _font = nullptr;
std::shared_ptr<SDL_Texture> _image = nullptr;
std::shared_ptr<TTF_TextEngine> _engine = nullptr;
SDL_FRect _rect = {};
bool _wrap = false;
size_t _text_width = 0;
};

View File

@@ -0,0 +1,63 @@
#include "sdl_widget_list.hpp"
#include "sdl_blend_mode_guard.hpp"
SdlWidgetList::~SdlWidgetList() = default;
bool SdlWidgetList::reset(const std::string& title, size_t width, size_t height)
{
auto w = WINPR_ASSERTING_INT_CAST(int, width);
auto h = WINPR_ASSERTING_INT_CAST(int, height);
SDL_Renderer* renderer = nullptr;
SDL_Window* window = nullptr;
auto rc = SDL_CreateWindowAndRenderer(
title.c_str(), w, h, SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS, &window, &renderer);
_renderer = std::shared_ptr<SDL_Renderer>(renderer, SDL_DestroyRenderer);
_window = std::shared_ptr<SDL_Window>(window, SDL_DestroyWindow);
if (!rc)
widget_log_error(rc, "SDL_CreateWindowAndRenderer");
return rc;
}
bool SdlWidgetList::visible() const
{
if (!_window || !_renderer)
return false;
auto flags = SDL_GetWindowFlags(_window.get());
return (flags & (SDL_WINDOW_HIDDEN | SDL_WINDOW_MINIMIZED)) == 0;
}
bool SdlWidgetList::clearWindow()
{
if (!_renderer)
return false;
SdlBlendModeGuard guard(_renderer, SDL_BLENDMODE_NONE);
const auto drc = SDL_SetRenderDrawColor(_renderer.get(), _backgroundcolor.r, _backgroundcolor.g,
_backgroundcolor.b, _backgroundcolor.a);
if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
return false;
const auto rcls = SDL_RenderClear(_renderer.get());
return !widget_log_error(rcls, "SDL_RenderClear");
}
bool SdlWidgetList::update()
{
if (!visible())
return true;
if (!clearWindow())
return false;
if (!updateInternal())
return false;
if (!_buttons.update())
return false;
auto rc = SDL_RenderPresent(_renderer.get());
if (!rc)
{
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[%s] SDL_RenderPresent failed with %s", __func__,
SDL_GetError());
}
return rc;
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <memory>
#include <SDL3/SDL.h>
#include <winpr/assert.h>
#include "sdl_buttons.hpp"
class SdlWidgetList
{
public:
SdlWidgetList() = default;
SdlWidgetList(const SdlWidgetList& other) = delete;
SdlWidgetList(SdlWidgetList&& other) = delete;
SdlWidgetList& operator=(const SdlWidgetList& other) = delete;
SdlWidgetList& operator=(SdlWidgetList&& other) = delete;
virtual ~SdlWidgetList();
[[nodiscard]] virtual bool reset(const std::string& title, size_t width, size_t height);
[[nodiscard]] virtual bool visible() const;
protected:
[[nodiscard]] bool update();
[[nodiscard]] virtual bool clearWindow();
[[nodiscard]] virtual bool updateInternal() = 0;
std::shared_ptr<SDL_Window> _window;
std::shared_ptr<SDL_Renderer> _renderer;
SdlButtonList _buttons;
SDL_Color _backgroundcolor{ 0x38, 0x36, 0x35, 0xff };
};

View File

@@ -0,0 +1,5 @@
set(NAMES "TestSDLDialogInput;TestSDLDialogSelectList")
foreach(NAME ${NAMES})
add_executable(${NAME} ${NAME}.cpp)
target_link_libraries(${NAME} PRIVATE sdl3-dialogs freerdp-client freerdp)
endforeach()

View File

@@ -0,0 +1,59 @@
#include "../sdl_dialogs.hpp"
#include "../sdl_input_widget_pair_list.hpp"
#include <freerdp/api.h>
#include <winpr/wlog.h>
typedef int (*fkt_t)(void);
BOOL sdl_log_error_ex(Sint32 res, wLog* log, const char* what, const char* file, size_t line,
const char* fkt)
{
WINPR_UNUSED(file);
WLog_Print(log, WLOG_ERROR, "[%s:%" PRIuz "][%s]: %s", fkt, line, what, "xxx");
return TRUE;
}
static int auth_dialogs(void)
{
const std::string title = "sometitle";
std::vector<std::string> result;
std::vector<std::string> initial{ "Smartcard", "abc", "def" };
std::vector<Uint32> flags = { SdlInputWidgetPair::SDL_INPUT_READONLY,
SdlInputWidgetPair::SDL_INPUT_MASK, 0 };
const std::vector<std::string>& labels{ "foo", "bar", "gaga" };
SdlInputWidgetPairList ilist(title.c_str(), labels, initial, flags);
auto rc = ilist.run(result);
if ((result.size() < labels.size()))
rc = -1;
return rc;
}
static int runTest(fkt_t fkt)
{
int rc;
SDL_Init(SDL_INIT_VIDEO);
sdl_dialogs_init();
try
{
rc = fkt();
}
catch (int e)
{
rc = e;
}
sdl_dialogs_uninit();
SDL_Quit();
return rc;
}
int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
{
return runTest(auth_dialogs);
}

View File

@@ -0,0 +1,47 @@
#include "../sdl_dialogs.hpp"
#include "../sdl_select_list.hpp"
#include <freerdp/api.h>
#include <winpr/wlog.h>
typedef int (*fkt_t)(void);
BOOL sdl_log_error_ex(Sint32 res, wLog* log, const char* what, const char* file, size_t line,
const char* fkt)
{
WINPR_UNUSED(file);
WLog_Print(log, WLOG_ERROR, "[%s:%" PRIuz "][%s]: %s", fkt, line, what, "xxx");
return TRUE;
}
static int select_dialogs(void)
{
const std::vector<std::string> labels{ "foo", "bar", "gaga", "blabla" };
SdlSelectList list{ "title", labels };
return list.run();
}
static int runTest(fkt_t fkt)
{
int rc;
SDL_Init(SDL_INIT_VIDEO);
sdl_dialogs_init();
try
{
rc = fkt();
}
catch (int e)
{
rc = e;
}
sdl_dialogs_uninit();
SDL_Quit();
return rc;
}
int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
{
return runTest(select_dialogs);
}