summaryrefslogtreecommitdiffstats
path: root/client/SDL/dialogs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
commita9bcc81f821d7c66f623779fa5147e728eb3c388 (patch)
tree98676963bcdd537ae5908a067a8eb110b93486a6 /client/SDL/dialogs
parentInitial commit. (diff)
downloadfreerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz
freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'client/SDL/dialogs')
-rw-r--r--client/SDL/dialogs/CMakeLists.txt75
-rw-r--r--client/SDL/dialogs/font/OFL.txt93
-rw-r--r--client/SDL/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttfbin0 -> 580356 bytes
-rw-r--r--client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttfbin0 -> 529700 bytes
-rw-r--r--client/SDL/dialogs/font/README.txt100
-rw-r--r--client/SDL/dialogs/res/CMakeLists.txt89
-rw-r--r--client/SDL/dialogs/res/convert_res_to_c.cpp184
-rw-r--r--client/SDL/dialogs/res/sdl_resource_file.cpp25
-rw-r--r--client/SDL/dialogs/res/sdl_resource_file.hpp33
-rw-r--r--client/SDL/dialogs/res/sdl_resource_manager.cpp78
-rw-r--r--client/SDL/dialogs/res/sdl_resource_manager.hpp46
-rw-r--r--client/SDL/dialogs/sdl_button.cpp71
-rw-r--r--client/SDL/dialogs/sdl_button.hpp26
-rw-r--r--client/SDL/dialogs/sdl_buttons.cpp105
-rw-r--r--client/SDL/dialogs/sdl_buttons.hpp37
-rw-r--r--client/SDL/dialogs/sdl_connection_dialog.cpp536
-rw-r--r--client/SDL/dialogs/sdl_connection_dialog.hpp129
-rw-r--r--client/SDL/dialogs/sdl_dialogs.cpp621
-rw-r--r--client/SDL/dialogs/sdl_dialogs.hpp53
-rw-r--r--client/SDL/dialogs/sdl_input.cpp177
-rw-r--r--client/SDL/dialogs/sdl_input.hpp73
-rw-r--r--client/SDL/dialogs/sdl_input_widgets.cpp299
-rw-r--r--client/SDL/dialogs/sdl_input_widgets.hpp44
-rw-r--r--client/SDL/dialogs/sdl_select.cpp74
-rw-r--r--client/SDL/dialogs/sdl_select.hpp46
-rw-r--r--client/SDL/dialogs/sdl_selectlist.cpp208
-rw-r--r--client/SDL/dialogs/sdl_selectlist.hpp42
-rw-r--r--client/SDL/dialogs/sdl_widget.cpp280
-rw-r--r--client/SDL/dialogs/sdl_widget.hpp88
-rw-r--r--client/SDL/dialogs/test/CMakeLists.txt30
-rw-r--r--client/SDL/dialogs/test/TestSDLDialogs.cpp99
31 files changed, 3761 insertions, 0 deletions
diff --git a/client/SDL/dialogs/CMakeLists.txt b/client/SDL/dialogs/CMakeLists.txt
new file mode 100644
index 0000000..4cf2a16
--- /dev/null
+++ b/client/SDL/dialogs/CMakeLists.txt
@@ -0,0 +1,75 @@
+set(SRCS
+ sdl_button.hpp
+ sdl_button.cpp
+ sdl_buttons.hpp
+ sdl_buttons.cpp
+ sdl_dialogs.cpp
+ sdl_dialogs.hpp
+ sdl_widget.hpp
+ sdl_widget.cpp
+ sdl_input.hpp
+ sdl_input.cpp
+ sdl_input_widgets.cpp
+ sdl_input_widgets.hpp
+ sdl_select.hpp
+ sdl_select.cpp
+ sdl_selectlist.hpp
+ sdl_selectlist.cpp
+ sdl_connection_dialog.cpp
+ sdl_connection_dialog.hpp
+)
+
+list(APPEND LIBS
+ sdl_client_res
+ winpr
+)
+
+if (NOT WITH_SDL_LINK_SHARED)
+ list(APPEND LIBS ${SDL2_STATIC_LIBRARIES})
+else()
+ list(APPEND LIBS ${SDL2_LIBRARIES})
+endif()
+
+macro(find_sdl_component name)
+ find_package(${name})
+ if (NOT ${name}_FOUND)
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(${name} REQUIRED ${name})
+
+ if (BUILD_SHARED_LIBS)
+ list(APPEND LIBS ${${name}_LIBRARIES})
+ link_directories(${${name}_LIBRARY_DIRS})
+ include_directories(${${name}_INCLUDE_DIRS})
+ else()
+ list(APPEND LIBS ${${name}_STATIC_LIBRARIES})
+ link_directories(${${name}_STATIC_LIBRARY_DIRS})
+ include_directories(${${name}_STATIC_INCLUDE_DIRS})
+ endif()
+ else()
+ if (WITH_SDL_LINK_SHARED)
+ list(APPEND LIBS ${name}::${name})
+ else()
+ list(APPEND LIBS ${name}::${name}-static)
+ endif()
+ endif()
+endmacro()
+
+find_sdl_component(SDL2_ttf)
+
+option(WITH_SDL_IMAGE_DIALOGS "Build with SDL_image support (recommended)" OFF)
+if (WITH_SDL_IMAGE_DIALOGS)
+ find_sdl_component(SDL2_image)
+ add_definitions(-DWITH_SDL_IMAGE_DIALOGS)
+endif()
+
+add_subdirectory(res)
+
+add_library(dialogs STATIC
+ ${SRCS}
+)
+
+target_link_libraries(dialogs PRIVATE ${LIBS})
+
+if(BUILD_TESTING)
+# add_subdirectory(test)
+endif()
diff --git a/client/SDL/dialogs/font/OFL.txt b/client/SDL/dialogs/font/OFL.txt
new file mode 100644
index 0000000..9b448d4
--- /dev/null
+++ b/client/SDL/dialogs/font/OFL.txt
@@ -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.
diff --git a/client/SDL/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttf b/client/SDL/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttf
new file mode 100644
index 0000000..5bda9cc
--- /dev/null
+++ b/client/SDL/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttf
Binary files differ
diff --git a/client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf b/client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf
new file mode 100644
index 0000000..e4142bf
--- /dev/null
+++ b/client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf
Binary files differ
diff --git a/client/SDL/dialogs/font/README.txt b/client/SDL/dialogs/font/README.txt
new file mode 100644
index 0000000..2548322
--- /dev/null
+++ b/client/SDL/dialogs/font/README.txt
@@ -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 aren’t 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.
diff --git a/client/SDL/dialogs/res/CMakeLists.txt b/client/SDL/dialogs/res/CMakeLists.txt
new file mode 100644
index 0000000..5591e4a
--- /dev/null
+++ b/client/SDL/dialogs/res/CMakeLists.txt
@@ -0,0 +1,89 @@
+
+add_executable(freerdp-res2bin
+ convert_res_to_c.cpp
+)
+
+set(SRCS
+ sdl_resource_manager.cpp
+ sdl_resource_manager.hpp
+)
+
+set(RES_SVG_FILES
+ ${CMAKE_SOURCE_DIR}/resources/FreeRDP_Icon.svg
+ ${CMAKE_SOURCE_DIR}/resources/icon_info.svg
+ ${CMAKE_SOURCE_DIR}/resources/icon_warning.svg
+ ${CMAKE_SOURCE_DIR}/resources/icon_error.svg
+)
+
+set(RES_FONT_FILES
+ ${CMAKE_SOURCE_DIR}/client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf
+)
+
+macro(convert_to_bin FILE FILE_TYPE)
+ get_filename_component(FILE_NAME ${FILE} NAME)
+ string(REGEX REPLACE "[^a-zA-Z0-9]" "_" TARGET_NAME ${FILE_NAME})
+
+ set(FILE_BIN_DIR ${CMAKE_CURRENT_BINARY_DIR}/bin)
+ set(FILE_BYPRODUCTS ${FILE_BIN_DIR}/${TARGET_NAME}.hpp ${FILE_BIN_DIR}/${TARGET_NAME}.cpp)
+
+ list(APPEND FACTORY_SRCS
+ ${FILE_BYPRODUCTS}
+ )
+
+ add_custom_command(
+ OUTPUT ${FILE_BYPRODUCTS}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${FILE_BIN_DIR}
+ COMMAND $<TARGET_FILE:freerdp-res2bin> ${FILE} ${FILE_TYPE} ${TARGET_NAME} ${FILE_BIN_DIR}
+ COMMENT "create image resources"
+ DEPENDS freerdp-res2bin
+ DEPENDS ${FILE}
+ )
+endmacro()
+
+option(SDL_USE_COMPILED_RESOURCES "Compile in images/fonts" ON)
+
+if (SDL_USE_COMPILED_RESOURCES)
+ list(APPEND SRCS
+ sdl_resource_file.cpp
+ sdl_resource_file.hpp
+ )
+
+ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+
+ if (WITH_SDL_IMAGE_DIALOGS)
+ foreach(FILE ${RES_SVG_FILES})
+ convert_to_bin("${FILE}" "images")
+ endforeach()
+ endif()
+
+ foreach(FILE ${RES_FONT_FILES})
+ convert_to_bin("${FILE}" "fonts")
+ endforeach()
+ add_definitions(-DSDL_USE_COMPILED_RESOURCES)
+else()
+ set(SDL_RESOURCE_ROOT ${CMAKE_INSTALL_FULL_DATAROOTDIR}/FreeRDP)
+ if (WITH_BINARY_VERSIONING)
+ string(APPEND SDL_RESOURCE_ROOT "${PROJECT_VERSION_MAJOR}")
+ endif()
+
+ add_definitions(-DSDL_RESOURCE_ROOT="${SDL_RESOURCE_ROOT}")
+
+ if (WITH_SDL_IMAGE_DIALOGS)
+ install(
+ FILES ${RES_SVG_FILES}
+ DESTINATION ${SDL_RESOURCE_ROOT}/images
+ )
+ endif()
+
+ install(
+ FILES ${RES_FONT_FILES}
+ DESTINATION ${SDL_RESOURCE_ROOT}/fonts
+ )
+endif()
+
+add_library(sdl_client_res OBJECT
+ ${RES_FILES}
+ ${SRCS}
+ ${FACTORY_SRCS}
+)
+set_property(TARGET sdl_client_res PROPERTY POSITION_INDEPENDENT_CODE ON)
diff --git a/client/SDL/dialogs/res/convert_res_to_c.cpp b/client/SDL/dialogs/res/convert_res_to_c.cpp
new file mode 100644
index 0000000..07309d5
--- /dev/null
+++ b/client/SDL/dialogs/res/convert_res_to_c.cpp
@@ -0,0 +1,184 @@
+/**
+ * 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 <string>
+#include <fstream>
+#include <algorithm>
+#include <iomanip>
+#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
+
+static void usage(const char* prg)
+{
+ std::cerr << prg << " <file> <type> <class name> <dst path>" << std::endl;
+}
+
+static int write_comment_header(std::ostream& out, const fs::path& prg, const std::string& fname)
+{
+ out << "/* AUTOGENERATED file, do not edit" << std::endl
+ << " *" << std::endl
+ << " * generated by '" << prg.filename() << "'" << std::endl
+ << " *" << std::endl
+ << " * contains the converted file '" << fname << "'" << std::endl
+ << " */" << std::endl
+ << std::endl;
+ return 0;
+}
+
+static int write_cpp_header(std::ostream& out, const fs::path& prg, const fs::path& file,
+ const std::string& name, const std::string& type)
+{
+ auto fname = file.filename().string();
+ auto rc = write_comment_header(out, prg, fname);
+ if (rc != 0)
+ return rc;
+
+ out << "#include <vector>" << std::endl
+ << "#include \"" << name << ".hpp\"" << std::endl
+ << std::endl
+ << "std::string " << name << "::name() {" << std::endl
+ << "return \"" << fname << "\";" << std::endl
+ << "}" << std::endl
+ << "std::string " << name << "::type() {" << std::endl
+ << "return \"" << type << "\";" << std::endl
+ << "}" << std::endl
+ << std::endl
+ << "const SDLResourceFile " << name << "::_initializer(type(), name(), init());"
+ << std::endl
+ << std::endl
+ << "std::vector<unsigned char> " << name << "::init() {" << std::endl
+ << "static const unsigned char data[] = {" << std::endl;
+
+ return 0;
+}
+
+static int readwrite(std::ofstream& out, std::ifstream& ifs)
+{
+ size_t pos = 0;
+ char c = 0;
+ while (ifs.read(&c, 1) && ifs.good())
+ {
+ unsigned val = c & 0xff;
+ out << "0x" << std::hex << std::setfill('0') << std::setw(2) << val;
+ if (ifs.peek() != EOF)
+ out << ",";
+ if ((pos++ % 16) == 15)
+ out << std::endl;
+ }
+
+ return 0;
+}
+
+static int write_cpp_trailer(std::ostream& out)
+{
+ out << std::endl;
+ out << "};" << std::endl;
+ out << std::endl;
+ out << "return std::vector<unsigned char>(data, data + sizeof(data));" << std::endl;
+ out << "}" << std::endl;
+ return 0;
+}
+
+static int write_hpp_header(const fs::path& prg, const fs::path& file, const std::string& name,
+ const std::string& fname)
+{
+ std::ofstream out(file, std::ios::out);
+ if (!out.is_open())
+ {
+ std::cerr << "Failed to open output file '" << file << "'" << std::endl;
+ return -1;
+ }
+ auto rc = write_comment_header(out, prg, fname);
+ if (rc != 0)
+ return rc;
+
+ out << "#pragma once" << std::endl
+ << std::endl
+ << "#include <vector>" << std::endl
+ << "#include <string>" << std::endl
+ << "#include \"sdl_resource_file.hpp\"" << std::endl
+ << std::endl
+ << "class " << name << " {" << std::endl
+ << "public:" << std::endl
+ << name << "() = delete;" << std::endl
+ << std::endl
+ << "static std::string name();" << std::endl
+ << "static std::string type();" << std::endl
+ << std::endl
+ << "private:" << std::endl
+ << "static std::vector<unsigned char> init();" << std::endl
+ << "static const SDLResourceFile _initializer;" << std::endl
+ << std::endl
+ << "};" << std::endl;
+ return 0;
+}
+
+int main(int argc, char* argv[])
+{
+ fs::path prg(argv[0]);
+ if (argc != 5)
+ {
+ usage(argv[0]);
+ return -1;
+ }
+
+ fs::path file(argv[1]);
+ std::string etype = argv[2];
+ std::string cname = argv[3];
+ fs::path dst(argv[4]);
+ fs::path hdr(argv[4]);
+
+ dst /= cname + ".cpp";
+ hdr /= cname + ".hpp";
+
+ std::ofstream out;
+ out.open(dst);
+ if (!out.is_open())
+ {
+ std::cerr << "Failed to open output file '" << dst << "'" << std::endl;
+ return -2;
+ }
+
+ std::ifstream ifs(file, std::ios::in | std::ios::binary);
+ if (!ifs.is_open())
+ {
+ std::cerr << "Failed to open input file '" << file << "'" << std::endl;
+ return -3;
+ }
+
+ auto rc = write_cpp_header(out, prg, file, cname, etype);
+ if (rc != 0)
+ return -1;
+
+ rc = readwrite(out, ifs);
+ if (rc != 0)
+ return rc;
+
+ rc = write_cpp_trailer(out);
+ if (rc != 0)
+ return rc;
+ return write_hpp_header(prg, hdr, cname, file.filename().string());
+}
diff --git a/client/SDL/dialogs/res/sdl_resource_file.cpp b/client/SDL/dialogs/res/sdl_resource_file.cpp
new file mode 100644
index 0000000..c48612d
--- /dev/null
+++ b/client/SDL/dialogs/res/sdl_resource_file.cpp
@@ -0,0 +1,25 @@
+/**
+ * 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 "sdl_resource_file.hpp"
+#include "sdl_resource_manager.hpp"
+
+SDLResourceFile::SDLResourceFile(const std::string& type, const std::string& id,
+ const std::vector<unsigned char>& data)
+{
+ SDLResourceManager::insert(type, id, data);
+}
diff --git a/client/SDL/dialogs/res/sdl_resource_file.hpp b/client/SDL/dialogs/res/sdl_resource_file.hpp
new file mode 100644
index 0000000..5846921
--- /dev/null
+++ b/client/SDL/dialogs/res/sdl_resource_file.hpp
@@ -0,0 +1,33 @@
+/**
+ * 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 <vector>
+
+class SDLResourceFile
+{
+ public:
+ SDLResourceFile(const std::string& type, const std::string& id,
+ const std::vector<unsigned char>& data);
+ virtual ~SDLResourceFile() = default;
+
+ private:
+ SDLResourceFile(const SDLResourceFile& other) = delete;
+ SDLResourceFile(const SDLResourceFile&& other) = delete;
+};
diff --git a/client/SDL/dialogs/res/sdl_resource_manager.cpp b/client/SDL/dialogs/res/sdl_resource_manager.cpp
new file mode 100644
index 0000000..90ccf31
--- /dev/null
+++ b/client/SDL/dialogs/res/sdl_resource_manager.cpp
@@ -0,0 +1,78 @@
+/**
+ * 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 "sdl_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_RWops* SDLResourceManager::get(const std::string& type, const std::string& id)
+{
+ std::string uuid = type + "/" + id;
+
+#if defined(SDL_USE_COMPILED_RESOURCES)
+ auto val = resources().find(uuid);
+ if (val == resources().end())
+ return nullptr;
+
+ const auto& v = val->second;
+ return SDL_RWFromConstMem(v.data(), v.size());
+#else
+ fs::path path(SDL_RESOURCE_ROOT);
+ path /= type;
+ path /= id;
+
+ if (!fs::exists(path))
+ {
+ std::cerr << "sdl-freerdp expects resource '" << uuid << "' at location "
+ << fs::absolute(path) << std::endl;
+ std::cerr << "file not found, application will fail" << std::endl;
+ }
+ return SDL_RWFromFile(path.native().c_str(), "rb");
+#endif
+}
+
+const std::string SDLResourceManager::typeFonts()
+{
+ return "fonts";
+}
+
+const std::string SDLResourceManager::typeImages()
+{
+ return "images";
+}
+
+void SDLResourceManager::insert(const std::string& type, const std::string& id,
+ const std::vector<unsigned char>& data)
+{
+ std::string uuid = type + "/" + id;
+ resources().emplace(uuid, data);
+}
+
+std::map<std::string, std::vector<unsigned char>>& SDLResourceManager::resources()
+{
+
+ static std::map<std::string, std::vector<unsigned char>> resources = {};
+ return resources;
+}
diff --git a/client/SDL/dialogs/res/sdl_resource_manager.hpp b/client/SDL/dialogs/res/sdl_resource_manager.hpp
new file mode 100644
index 0000000..b4f463c
--- /dev/null
+++ b/client/SDL/dialogs/res/sdl_resource_manager.hpp
@@ -0,0 +1,46 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <string>
+#include <map>
+#include <vector>
+#include <SDL.h>
+
+class SDLResourceManager
+{
+ friend class SDLResourceFile;
+
+ public:
+ static SDL_RWops* get(const std::string& type, const std::string& id);
+
+ static const std::string typeFonts();
+ static const std::string typeImages();
+
+ protected:
+ static void insert(const std::string& type, const std::string& id,
+ const std::vector<unsigned char>& data);
+
+ private:
+ SDLResourceManager() = delete;
+ SDLResourceManager(const SDLResourceManager& other) = delete;
+ SDLResourceManager(const SDLResourceManager&& other) = delete;
+ ~SDLResourceManager() = delete;
+
+ static std::map<std::string, std::vector<unsigned char>>& resources();
+};
diff --git a/client/SDL/dialogs/sdl_button.cpp b/client/SDL/dialogs/sdl_button.cpp
new file mode 100644
index 0000000..cfa2107
--- /dev/null
+++ b/client/SDL/dialogs/sdl_button.cpp
@@ -0,0 +1,71 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client Channels
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2023 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cassert>
+
+#include "sdl_button.hpp"
+
+static const SDL_Color buttonbackgroundcolor = { 0x69, 0x66, 0x63, 0xff };
+static const SDL_Color buttonhighlightcolor = { 0xcd, 0xca, 0x35, 0x60 };
+static const SDL_Color buttonmouseovercolor = { 0x66, 0xff, 0x66, 0x60 };
+static const SDL_Color buttonfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+
+SdlButton::SdlButton(SDL_Renderer* renderer, const std::string& label, int id, const SDL_Rect& rect)
+ : SdlWidget(renderer, rect, false), _name(label), _id(id)
+{
+ assert(renderer);
+
+ update_text(renderer, _name, buttonfontcolor, buttonbackgroundcolor);
+}
+
+SdlButton::SdlButton(SdlButton&& other) noexcept
+ : SdlWidget(std::move(other)), _name(std::move(other._name)), _id(std::move(other._id))
+{
+}
+
+bool SdlButton::highlight(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ std::vector<SDL_Color> colors = { buttonbackgroundcolor, buttonhighlightcolor };
+ if (!fill(renderer, colors))
+ return false;
+ return update_text(renderer, _name, buttonfontcolor);
+}
+
+bool SdlButton::mouseover(SDL_Renderer* renderer)
+{
+ std::vector<SDL_Color> colors = { buttonbackgroundcolor, buttonmouseovercolor };
+ if (!fill(renderer, colors))
+ return false;
+ return update_text(renderer, _name, buttonfontcolor);
+}
+
+bool SdlButton::update(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ return update_text(renderer, _name, buttonfontcolor, buttonbackgroundcolor);
+}
+
+int SdlButton::id() const
+{
+ return _id;
+}
diff --git a/client/SDL/dialogs/sdl_button.hpp b/client/SDL/dialogs/sdl_button.hpp
new file mode 100644
index 0000000..350e7db
--- /dev/null
+++ b/client/SDL/dialogs/sdl_button.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <string>
+
+#include "sdl_widget.hpp"
+
+class SdlButton : public SdlWidget
+{
+ public:
+ SdlButton(SDL_Renderer* renderer, const std::string& label, int id, const SDL_Rect& rect);
+ SdlButton(SdlButton&& other) noexcept;
+ ~SdlButton() override = default;
+
+ bool highlight(SDL_Renderer* renderer);
+ bool mouseover(SDL_Renderer* renderer);
+ bool update(SDL_Renderer* renderer);
+
+ [[nodiscard]] int id() const;
+
+ private:
+ SdlButton(const SdlButton& other) = delete;
+
+ private:
+ std::string _name;
+ int _id;
+};
diff --git a/client/SDL/dialogs/sdl_buttons.cpp b/client/SDL/dialogs/sdl_buttons.cpp
new file mode 100644
index 0000000..8190cbe
--- /dev/null
+++ b/client/SDL/dialogs/sdl_buttons.cpp
@@ -0,0 +1,105 @@
+#include <cassert>
+#include <algorithm>
+
+#include "sdl_buttons.hpp"
+
+static const Uint32 hpadding = 10;
+
+bool SdlButtonList::populate(SDL_Renderer* renderer, const std::vector<std::string>& labels,
+ const std::vector<int>& ids, Sint32 total_width, Sint32 offsetY,
+ Sint32 width, Sint32 height)
+{
+ assert(renderer);
+ assert(width >= 0);
+ assert(height >= 0);
+ assert(labels.size() == ids.size());
+
+ _list.clear();
+ size_t button_width = ids.size() * (width + hpadding) + hpadding;
+ size_t offsetX = total_width - std::min<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_Rect rect = { static_cast<int>(curOffsetX), offsetY, width, height };
+ _list.emplace_back(renderer, labels[x], ids[x], rect);
+ }
+ return true;
+}
+
+SdlButton* SdlButtonList::get_selected(const SDL_MouseButtonEvent& button)
+{
+ const Sint32 x = button.x;
+ const Sint32 y = button.y;
+
+ return get_selected(x, y);
+}
+
+SdlButton* SdlButtonList::get_selected(Sint32 x, Sint32 y)
+{
+ for (auto& btn : _list)
+ {
+ auto r = btn.rect();
+ if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
+ return &btn;
+ }
+ return nullptr;
+}
+
+bool SdlButtonList::set_highlight_next(bool reset)
+{
+ if (reset)
+ _highlighted = nullptr;
+ else
+ {
+ auto next = _highlight_index++;
+ _highlight_index %= _list.size();
+ auto& element = _list[next];
+ _highlighted = &element;
+ }
+ return true;
+}
+
+bool SdlButtonList::set_highlight(size_t index)
+{
+ if (index >= _list.size())
+ {
+ _highlighted = nullptr;
+ return false;
+ }
+ auto& element = _list[index];
+ _highlighted = &element;
+ _highlight_index = ++index % _list.size();
+ return true;
+}
+
+bool SdlButtonList::set_mouseover(Sint32 x, Sint32 y)
+{
+ _mouseover = get_selected(x, y);
+ return _mouseover != nullptr;
+}
+
+void SdlButtonList::clear()
+{
+ _list.clear();
+ _mouseover = nullptr;
+ _highlighted = nullptr;
+ _highlight_index = 0;
+}
+
+bool SdlButtonList::update(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ for (auto& btn : _list)
+ {
+ if (!btn.update(renderer))
+ return false;
+ }
+
+ if (_highlighted)
+ _highlighted->highlight(renderer);
+
+ if (_mouseover)
+ _mouseover->mouseover(renderer);
+ return true;
+}
diff --git a/client/SDL/dialogs/sdl_buttons.hpp b/client/SDL/dialogs/sdl_buttons.hpp
new file mode 100644
index 0000000..7f82903
--- /dev/null
+++ b/client/SDL/dialogs/sdl_buttons.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <vector>
+#include <cstdint>
+
+#include "sdl_button.hpp"
+
+class SdlButtonList
+{
+ public:
+ SdlButtonList() = default;
+ virtual ~SdlButtonList() = default;
+
+ bool populate(SDL_Renderer* renderer, const std::vector<std::string>& labels,
+ const std::vector<int>& ids, Sint32 total_width, Sint32 offsetY, Sint32 width,
+ Sint32 heigth);
+
+ bool update(SDL_Renderer* renderer);
+ SdlButton* get_selected(const SDL_MouseButtonEvent& button);
+ SdlButton* get_selected(Sint32 x, Sint32 y);
+
+ bool set_highlight_next(bool reset = false);
+ bool set_highlight(size_t index);
+ bool set_mouseover(Sint32 x, Sint32 y);
+
+ void clear();
+
+ private:
+ SdlButtonList(const SdlButtonList& other) = delete;
+ SdlButtonList(SdlButtonList&& other) = delete;
+
+ private:
+ std::vector<SdlButton> _list;
+ SdlButton* _highlighted = nullptr;
+ size_t _highlight_index = 0;
+ SdlButton* _mouseover = nullptr;
+};
diff --git a/client/SDL/dialogs/sdl_connection_dialog.cpp b/client/SDL/dialogs/sdl_connection_dialog.cpp
new file mode 100644
index 0000000..cbb6349
--- /dev/null
+++ b/client/SDL/dialogs/sdl_connection_dialog.cpp
@@ -0,0 +1,536 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <cassert>
+#include <thread>
+
+#include "sdl_connection_dialog.hpp"
+#include "../sdl_utils.hpp"
+#include "../sdl_freerdp.hpp"
+#include "res/sdl_resource_manager.hpp"
+
+static const SDL_Color backgroundcolor = { 0x38, 0x36, 0x35, 0xff };
+static const SDL_Color textcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+static const SDL_Color infocolor = { 0x43, 0xe0, 0x0f, 0x60 };
+static const SDL_Color warncolor = { 0xcd, 0xca, 0x35, 0x60 };
+static const SDL_Color errorcolor = { 0xf7, 0x22, 0x30, 0x60 };
+
+static const Uint32 vpadding = 5;
+static const Uint32 hpadding = 5;
+
+SDLConnectionDialog::SDLConnectionDialog(rdpContext* context)
+ : _context(context), _window(nullptr), _renderer(nullptr)
+{
+ SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);
+ hide();
+}
+
+SDLConnectionDialog::~SDLConnectionDialog()
+{
+ resetTimer();
+ destroyWindow();
+ SDL_Quit();
+}
+
+bool SDLConnectionDialog::visible() const
+{
+ return _window && _renderer;
+}
+
+bool SDLConnectionDialog::setTitle(const char* fmt, ...)
+{
+ std::lock_guard lock(_mux);
+ va_list ap;
+ va_start(ap, fmt);
+ _title = print(fmt, ap);
+ va_end(ap);
+
+ return show(MSG_NONE);
+}
+
+bool SDLConnectionDialog::showInfo(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ auto rc = show(MSG_INFO, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+bool SDLConnectionDialog::showWarn(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ auto rc = show(MSG_WARN, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+bool SDLConnectionDialog::showError(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ auto rc = show(MSG_ERROR, fmt, ap);
+ va_end(ap);
+ return setTimer();
+}
+
+bool SDLConnectionDialog::show()
+{
+ std::lock_guard lock(_mux);
+ return show(_type_active);
+}
+
+bool SDLConnectionDialog::hide()
+{
+ std::lock_guard lock(_mux);
+ return show(MSG_DISCARD);
+}
+
+bool SDLConnectionDialog::running() const
+{
+ std::lock_guard lock(_mux);
+ return _running;
+}
+
+bool SDLConnectionDialog::update()
+{
+ std::lock_guard lock(_mux);
+ switch (_type)
+ {
+ case MSG_INFO:
+ case MSG_WARN:
+ case MSG_ERROR:
+ _type_active = _type;
+ createWindow();
+ break;
+ case MSG_DISCARD:
+ resetTimer();
+ destroyWindow();
+ break;
+ default:
+ if (_window)
+ {
+ SDL_SetWindowTitle(_window, _title.c_str());
+ }
+ break;
+ }
+ _type = MSG_NONE;
+ return true;
+}
+
+bool SDLConnectionDialog::setModal()
+{
+ if (_window)
+ {
+ auto sdl = get_context(_context);
+ if (sdl->windows.empty())
+ return true;
+
+ auto parent = sdl->windows.begin()->second.window();
+ SDL_SetWindowModalFor(_window, parent);
+ SDL_RaiseWindow(_window);
+ }
+ return true;
+}
+
+bool SDLConnectionDialog::clearWindow(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ const int drc = SDL_SetRenderDrawColor(renderer, backgroundcolor.r, backgroundcolor.g,
+ backgroundcolor.b, backgroundcolor.a);
+ if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
+ return false;
+
+ const int rcls = SDL_RenderClear(renderer);
+ return !widget_log_error(rcls, "SDL_RenderClear");
+}
+
+bool SDLConnectionDialog::update(SDL_Renderer* renderer)
+{
+ if (!renderer)
+ return false;
+
+ if (!clearWindow(renderer))
+ return false;
+
+ for (auto& btn : _list)
+ {
+ if (!btn.widget.update_text(renderer, _msg, btn.fgcolor, btn.bgcolor))
+ return false;
+ }
+
+ if (!_buttons.update(renderer))
+ return false;
+
+ SDL_RenderPresent(renderer);
+ return true;
+}
+
+bool SDLConnectionDialog::wait(bool ignoreRdpContext)
+{
+ while (running())
+ {
+ if (!ignoreRdpContext)
+ {
+ if (freerdp_shall_disconnect_context(_context))
+ return false;
+ }
+ std::this_thread::yield();
+ }
+ return true;
+}
+
+bool SDLConnectionDialog::handle(const SDL_Event& event)
+{
+ Uint32 windowID = 0;
+ if (_window)
+ {
+ windowID = SDL_GetWindowID(_window);
+ }
+
+ switch (event.type)
+ {
+ case SDL_USEREVENT_RETRY_DIALOG:
+ return update();
+ case SDL_QUIT:
+ resetTimer();
+ destroyWindow();
+ return false;
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_KeyboardEvent&>(event);
+ update(_renderer);
+ switch (event.key.keysym.sym)
+ {
+ case SDLK_RETURN:
+ case SDLK_RETURN2:
+ case SDLK_ESCAPE:
+ case SDLK_KP_ENTER:
+ if (event.type == SDL_KEYUP)
+ {
+ freerdp_abort_event(_context);
+ sdl_push_quit();
+ }
+ break;
+ case SDLK_TAB:
+ _buttons.set_highlight_next();
+ break;
+ default:
+ break;
+ }
+
+ return windowID == ev.windowID;
+ }
+ return false;
+ case SDL_MOUSEMOTION:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_MouseMotionEvent&>(event);
+
+ _buttons.set_mouseover(event.button.x, event.button.y);
+ update(_renderer);
+ return windowID == ev.windowID;
+ }
+ return false;
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_MouseButtonEvent&>(event);
+ update(_renderer);
+
+ auto button = _buttons.get_selected(event.button);
+ if (button)
+ {
+ if (event.type == SDL_MOUSEBUTTONUP)
+ {
+ freerdp_abort_event(_context);
+ sdl_push_quit();
+ }
+ }
+
+ return windowID == ev.windowID;
+ }
+ return false;
+ case SDL_MOUSEWHEEL:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_MouseWheelEvent&>(event);
+ update(_renderer);
+ return windowID == ev.windowID;
+ }
+ return false;
+ case SDL_FINGERUP:
+ case SDL_FINGERDOWN:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_TouchFingerEvent&>(event);
+ update(_renderer);
+#if SDL_VERSION_ATLEAST(2, 0, 18)
+ return windowID == ev.windowID;
+#else
+ return false;
+#endif
+ }
+ return false;
+ case SDL_WINDOWEVENT:
+ {
+ auto ev = reinterpret_cast<const SDL_WindowEvent&>(event);
+ switch (ev.event)
+ {
+ case SDL_WINDOWEVENT_CLOSE:
+ if (windowID == ev.windowID)
+ {
+ freerdp_abort_event(_context);
+ sdl_push_quit();
+ }
+ break;
+ default:
+ update(_renderer);
+ setModal();
+ break;
+ }
+
+ return windowID == ev.windowID;
+ }
+ default:
+ return false;
+ }
+}
+
+bool SDLConnectionDialog::createWindow()
+{
+ destroyWindow();
+
+ const size_t widget_height = 50;
+ const size_t widget_width = 600;
+ const size_t total_height = 300;
+
+ _window = SDL_CreateWindow(
+ _title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, widget_width,
+ total_height, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS);
+ if (_window == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateWindow");
+ return false;
+ }
+ setModal();
+
+ _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
+ if (_renderer == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateRenderer");
+ return false;
+ }
+
+ SDL_Color res_bgcolor;
+ switch (_type_active)
+ {
+ case MSG_INFO:
+ res_bgcolor = infocolor;
+ break;
+ case MSG_WARN:
+ res_bgcolor = warncolor;
+ break;
+ case MSG_ERROR:
+ res_bgcolor = errorcolor;
+ break;
+ case MSG_DISCARD:
+ default:
+ res_bgcolor = backgroundcolor;
+ break;
+ }
+
+#if defined(WITH_SDL_IMAGE_DIALOGS)
+ std::string res_name;
+ switch (_type_active)
+ {
+ case MSG_INFO:
+ res_name = "icon_info.svg";
+ break;
+ case MSG_WARN:
+ res_name = "icon_warning.svg";
+ break;
+ case MSG_ERROR:
+ res_name = "icon_error.svg";
+ break;
+ case MSG_DISCARD:
+ default:
+ res_name = "";
+ break;
+ }
+
+ int height = (total_height - 3ul * vpadding) / 2ul;
+ SDL_Rect iconRect{ hpadding, vpadding, widget_width / 4ul - 2ul * hpadding, height };
+ widget_cfg_t icon{ textcolor,
+ res_bgcolor,
+ { _renderer, iconRect,
+ SDLResourceManager::get(SDLResourceManager::typeImages(), res_name) } };
+ _list.emplace_back(std::move(icon));
+
+ iconRect.y += height;
+
+ widget_cfg_t logo{ textcolor,
+ backgroundcolor,
+ { _renderer, iconRect,
+ SDLResourceManager::get(SDLResourceManager::typeImages(),
+ "FreeRDP_Icon.svg") } };
+ _list.emplace_back(std::move(logo));
+
+ SDL_Rect rect = { widget_width / 4ul, vpadding, widget_width * 3ul / 4ul,
+ total_height - 3ul * vpadding - widget_height };
+#else
+ SDL_Rect rect = { hpadding, vpadding, widget_width - 2ul * hpadding,
+ total_height - 2ul * vpadding };
+#endif
+
+ widget_cfg_t w{ textcolor, backgroundcolor, { _renderer, rect, false } };
+ w.widget.set_wrap(true, widget_width);
+ _list.emplace_back(std::move(w));
+ rect.y += widget_height + vpadding;
+
+ const std::vector<int> buttonids = { 1 };
+ const std::vector<std::string> buttonlabels = { "cancel" };
+ _buttons.populate(_renderer, buttonlabels, buttonids, widget_width,
+ total_height - widget_height - vpadding,
+ static_cast<Sint32>(widget_width / 2), static_cast<Sint32>(widget_height));
+ _buttons.set_highlight(0);
+
+ SDL_ShowWindow(_window);
+ SDL_RaiseWindow(_window);
+
+ return true;
+}
+
+void SDLConnectionDialog::destroyWindow()
+{
+ _buttons.clear();
+ _list.clear();
+ SDL_DestroyRenderer(_renderer);
+ SDL_DestroyWindow(_window);
+ _renderer = nullptr;
+ _window = nullptr;
+}
+
+bool SDLConnectionDialog::show(MsgType type, const char* fmt, va_list ap)
+{
+ std::lock_guard lock(_mux);
+ _msg = print(fmt, ap);
+ return show(type);
+}
+
+bool SDLConnectionDialog::show(MsgType type)
+{
+ _type = type;
+ return sdl_push_user_event(SDL_USEREVENT_RETRY_DIALOG);
+}
+
+std::string SDLConnectionDialog::print(const char* fmt, va_list ap)
+{
+ int size = -1;
+ std::string res;
+
+ do
+ {
+ res.resize(128);
+ if (size > 0)
+ res.resize(size);
+
+ va_list copy;
+ va_copy(copy, ap);
+ size = vsnprintf(res.data(), res.size(), fmt, copy);
+ va_end(copy);
+
+ } while ((size > 0) && (size > res.size()));
+
+ return res;
+}
+
+bool SDLConnectionDialog::setTimer(Uint32 timeoutMS)
+{
+ std::lock_guard lock(_mux);
+ resetTimer();
+
+ _timer = SDL_AddTimer(timeoutMS, &SDLConnectionDialog::timeout, this);
+ _running = true;
+ return true;
+}
+
+void SDLConnectionDialog::resetTimer()
+{
+ if (_running)
+ SDL_RemoveTimer(_timer);
+ _running = false;
+}
+
+Uint32 SDLConnectionDialog::timeout(Uint32 intervalMS, void* pvthis)
+{
+ auto ths = static_cast<SDLConnectionDialog*>(pvthis);
+ ths->hide();
+ ths->_running = false;
+ return 0;
+}
+
+SDLConnectionDialogHider::SDLConnectionDialogHider(freerdp* instance)
+ : SDLConnectionDialogHider(get(instance))
+{
+}
+
+SDLConnectionDialogHider::SDLConnectionDialogHider(rdpContext* context)
+ : SDLConnectionDialogHider(get(context))
+{
+}
+
+SDLConnectionDialogHider::SDLConnectionDialogHider(SDLConnectionDialog* dialog) : _dialog(dialog)
+{
+ if (_dialog)
+ {
+ _visible = _dialog->visible();
+ if (_visible)
+ {
+ _dialog->hide();
+ }
+ }
+}
+
+SDLConnectionDialogHider::~SDLConnectionDialogHider()
+{
+ if (_dialog && _visible)
+ {
+ _dialog->show();
+ }
+}
+
+SDLConnectionDialog* SDLConnectionDialogHider::get(freerdp* instance)
+{
+ if (!instance)
+ return nullptr;
+ return get(instance->context);
+}
+
+SDLConnectionDialog* SDLConnectionDialogHider::get(rdpContext* context)
+{
+ auto sdl = get_context(context);
+ if (!sdl)
+ return nullptr;
+ return sdl->connection_dialog.get();
+}
diff --git a/client/SDL/dialogs/sdl_connection_dialog.hpp b/client/SDL/dialogs/sdl_connection_dialog.hpp
new file mode 100644
index 0000000..f21f538
--- /dev/null
+++ b/client/SDL/dialogs/sdl_connection_dialog.hpp
@@ -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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+
+#include <SDL.h>
+
+#include <freerdp/freerdp.h>
+
+#include "sdl_widget.hpp"
+#include "sdl_buttons.hpp"
+
+class SDLConnectionDialog
+{
+ public:
+ explicit SDLConnectionDialog(rdpContext* context);
+ SDLConnectionDialog(const SDLConnectionDialog& other) = delete;
+ SDLConnectionDialog(const SDLConnectionDialog&& other) = delete;
+ virtual ~SDLConnectionDialog();
+
+ bool visible() const;
+
+ bool setTitle(const char* fmt, ...);
+ bool showInfo(const char* fmt, ...);
+ bool showWarn(const char* fmt, ...);
+ bool showError(const char* fmt, ...);
+
+ bool show();
+ bool hide();
+
+ bool running() const;
+ bool wait(bool ignoreRdpContextQuit = false);
+
+ bool handle(const SDL_Event& event);
+
+ private:
+ enum MsgType
+ {
+ MSG_NONE,
+ MSG_INFO,
+ MSG_WARN,
+ MSG_ERROR,
+ MSG_DISCARD
+ };
+
+ private:
+ bool createWindow();
+ void destroyWindow();
+
+ bool update();
+
+ bool setModal();
+
+ bool clearWindow(SDL_Renderer* renderer);
+
+ bool update(SDL_Renderer* renderer);
+
+ bool show(MsgType type, const char* fmt, va_list ap);
+ bool show(MsgType type);
+
+ std::string print(const char* fmt, va_list ap);
+ bool setTimer(Uint32 timeoutMS = 15000);
+ void resetTimer();
+
+ private:
+ static Uint32 timeout(Uint32 intervalMS, void* _this);
+
+ private:
+ struct widget_cfg_t
+ {
+ SDL_Color fgcolor;
+ SDL_Color bgcolor;
+ SdlWidget widget;
+ };
+
+ private:
+ rdpContext* _context;
+ SDL_Window* _window;
+ SDL_Renderer* _renderer;
+ mutable std::mutex _mux;
+ std::string _title;
+ std::string _msg;
+ MsgType _type = MSG_NONE;
+ MsgType _type_active = MSG_NONE;
+ SDL_TimerID _timer = -1;
+ bool _running = false;
+ std::vector<widget_cfg_t> _list;
+ SdlButtonList _buttons;
+};
+
+class SDLConnectionDialogHider
+{
+ public:
+ explicit SDLConnectionDialogHider(freerdp* instance);
+ explicit SDLConnectionDialogHider(rdpContext* context);
+
+ explicit SDLConnectionDialogHider(SDLConnectionDialog* dialog);
+
+ ~SDLConnectionDialogHider();
+
+ private:
+ SDLConnectionDialog* get(freerdp* instance);
+ SDLConnectionDialog* get(rdpContext* context);
+
+ private:
+ SDLConnectionDialog* _dialog;
+ bool _visible;
+};
diff --git a/client/SDL/dialogs/sdl_dialogs.cpp b/client/SDL/dialogs/sdl_dialogs.cpp
new file mode 100644
index 0000000..32f8457
--- /dev/null
+++ b/client/SDL/dialogs/sdl_dialogs.cpp
@@ -0,0 +1,621 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vector>
+#include <string>
+#include <cassert>
+
+#include <freerdp/log.h>
+#include <freerdp/utils/smartcardlogon.h>
+
+#include <SDL.h>
+
+#include "../sdl_freerdp.hpp"
+#include "sdl_dialogs.hpp"
+#include "sdl_input.hpp"
+#include "sdl_input_widgets.hpp"
+#include "sdl_select.hpp"
+#include "sdl_selectlist.hpp"
+
+#define TAG CLIENT_TAG("SDL.dialogs")
+
+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 = { 0 };
+
+ 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 = { 0 };
+
+ if (!sdl_push_user_event(SDL_USEREVENT_SHOW_DIALOG, title, message, flags))
+ return 0;
+
+ if (!sdl_wait_for_result(context, SDL_USEREVENT_SHOW_RESULT, &event))
+ return 0;
+
+ return event.user.code;
+}
+
+BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
+ rdp_auth_reason reason)
+{
+ SDL_Event event = { 0 };
+ BOOL res = FALSE;
+
+ SDLConnectionDialogHider hider(instance);
+
+ const char* target = freerdp_settings_get_server_name(instance->context->settings);
+ switch (reason)
+ {
+ case AUTH_NLA:
+ break;
+
+ case AUTH_TLS:
+ case AUTH_RDP:
+ case AUTH_SMARTCARD_PIN: /* in this case password is pin code */
+ if ((*username) && (*password))
+ return TRUE;
+ break;
+ case GW_AUTH_HTTP:
+ case GW_AUTH_RDG:
+ case GW_AUTH_RPC:
+ target =
+ freerdp_settings_get_string(instance->context->settings, FreeRDP_GatewayHostname);
+ break;
+ default:
+ break;
+ }
+
+ char* title = nullptr;
+ size_t titlesize = 0;
+ winpr_asprintf(&title, &titlesize, "Credentials required for %s", target);
+
+ char* u = nullptr;
+ char* d = nullptr;
+ char* p = nullptr;
+
+ assert(username);
+ assert(domain);
+ assert(password);
+
+ u = *username;
+ d = *domain;
+ p = *password;
+
+ if (!sdl_push_user_event(SDL_USEREVENT_AUTH_DIALOG, title, u, d, p, reason))
+ goto fail;
+
+ if (!sdl_wait_for_result(instance->context, SDL_USEREVENT_AUTH_RESULT, &event))
+ goto fail;
+ else
+ {
+ auto arg = reinterpret_cast<SDL_UserAuthArg*>(event.padding);
+
+ res = arg->result > 0 ? TRUE : FALSE;
+
+ free(*username);
+ free(*domain);
+ free(*password);
+ *username = arg->user;
+ *domain = arg->domain;
+ *password = arg->password;
+ }
+
+fail:
+ free(title);
+ return res;
+}
+
+BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
+ DWORD* choice, BOOL gateway)
+{
+ BOOL res = FALSE;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(cert_list);
+ WINPR_ASSERT(choice);
+
+ SDLConnectionDialogHider hider(instance);
+ std::vector<std::string> strlist;
+ std::vector<const char*> list;
+ for (DWORD i = 0; i < count; i++)
+ {
+ const SmartcardCertInfo* cert = cert_list[i];
+ char* reader = ConvertWCharToUtf8Alloc(cert->reader, nullptr);
+ char* container_name = ConvertWCharToUtf8Alloc(cert->containerName, nullptr);
+
+ char* msg = nullptr;
+ size_t len = 0;
+
+ winpr_asprintf(&msg, &len,
+ "%s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s",
+ container_name, reader, cert->userHint, cert->domainHint, cert->subject,
+ cert->issuer, cert->upn);
+
+ strlist.emplace_back(msg);
+ free(msg);
+ free(reader);
+ free(container_name);
+
+ auto& m = strlist.back();
+ list.push_back(m.c_str());
+ }
+
+ SDL_Event event = { 0 };
+ const char* title = "Select a logon smartcard certificate";
+ if (gateway)
+ title = "Select a gateway logon smartcard certificate";
+ if (!sdl_push_user_event(SDL_USEREVENT_SCARD_DIALOG, title, list.data(), count))
+ goto fail;
+
+ if (!sdl_wait_for_result(instance->context, SDL_USEREVENT_SCARD_RESULT, &event))
+ goto fail;
+
+ res = (event.user.code >= 0) ? TRUE : FALSE;
+ *choice = static_cast<DWORD>(event.user.code);
+
+fail:
+ return res;
+}
+
+SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current, void* userarg)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(what);
+
+ auto sdl = get_context(instance->context);
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ WINPR_ASSERT(sdl->connection_dialog);
+
+ sdl->connection_dialog->setTitle("Retry connection to %s",
+ freerdp_settings_get_server_name(instance->context->settings));
+
+ if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0))
+ {
+ sdl->connection_dialog->showError("Unknown module %s, aborting", what);
+ return -1;
+ }
+
+ if (current == 0)
+ {
+ if (strcmp(what, "arm-transport") == 0)
+ sdl->connection_dialog->showWarn("[%s] Starting your VM. It may take up to 5 minutes",
+ what);
+ }
+
+ auto settings = instance->context->settings;
+ const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
+
+ if (!enabled)
+ {
+ sdl->connection_dialog->showError(
+ "Automatic reconnection disabled, terminating. Try to connect again later");
+ return -1;
+ }
+
+ const size_t max = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
+ const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
+ if (current >= max)
+ {
+ sdl->connection_dialog->showError(
+ "[%s] retries exceeded. Your VM failed to start. Try again later or contact your "
+ "tech support for help if this keeps happening.",
+ what);
+ return -1;
+ }
+
+ sdl->connection_dialog->showInfo("[%s] retry %" PRIuz "/%" PRIuz ", delaying %" PRIuz
+ "ms before next attempt",
+ what, current, max, delay);
+ return delay;
+}
+
+BOOL sdl_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
+ BOOL isConsentMandatory, size_t length, const WCHAR* wmessage)
+{
+ if (!isDisplayMandatory)
+ return TRUE;
+
+ char* title = nullptr;
+ size_t len = 0;
+ winpr_asprintf(&title, &len, "[gateway]");
+
+ Sint32 flags = 0;
+ if (isConsentMandatory)
+ flags = SHOW_DIALOG_ACCEPT_REJECT;
+ else if (isDisplayMandatory)
+ flags = SHOW_DIALOG_TIMED_ACCEPT;
+ char* message = ConvertWCharNToUtf8Alloc(wmessage, length, nullptr);
+
+ SDLConnectionDialogHider hider(instance);
+ const int rc = sdl_show_dialog(instance->context, title, message, flags);
+ free(title);
+ free(message);
+ return rc > 0 ? TRUE : FALSE;
+}
+
+int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
+{
+ int rc = -1;
+ const char* str_data = freerdp_get_logon_error_info_data(data);
+ const char* str_type = freerdp_get_logon_error_info_type(type);
+
+ if (!instance || !instance->context)
+ return -1;
+
+ /* ignore LOGON_MSG_SESSION_CONTINUE message */
+ if (type == LOGON_MSG_SESSION_CONTINUE)
+ return 0;
+
+ SDLConnectionDialogHider hider(instance);
+
+ char* title = nullptr;
+ size_t tlen = 0;
+ winpr_asprintf(&title, &tlen, "[%s] info",
+ freerdp_settings_get_server_name(instance->context->settings));
+
+ char* message = nullptr;
+ size_t mlen = 0;
+ winpr_asprintf(&message, &mlen, "Logon Error Info %s [%s]", str_data, str_type);
+
+ rc = sdl_show_dialog(instance->context, title, message, SHOW_DIALOG_ACCEPT_REJECT);
+ free(title);
+ free(message);
+ return rc;
+}
+
+static DWORD sdl_show_ceritifcate_dialog(rdpContext* context, const char* title,
+ const char* message)
+{
+ SDLConnectionDialogHider hider(context);
+ if (!sdl_push_user_event(SDL_USEREVENT_CERT_DIALOG, title, message))
+ return 0;
+
+ SDL_Event event = { 0 };
+ if (!sdl_wait_for_result(context, SDL_USEREVENT_CERT_RESULT, &event))
+ return 0;
+ return static_cast<DWORD>(event.user.code);
+}
+
+DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject,
+ const char* issuer, const char* new_fingerprint,
+ const char* old_subject, const char* old_issuer,
+ const char* old_fingerprint, DWORD flags)
+{
+ const char* type = type_str_for_flags(flags);
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ SDLConnectionDialogHider hider(instance);
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ char* new_fp_str = nullptr;
+ size_t len = 0;
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ winpr_asprintf(&new_fp_str, &len,
+ "----------- Certificate --------------\n"
+ "%s\n"
+ "--------------------------------------\n",
+ 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)
+ {
+ winpr_asprintf(&old_fp_str, &olen,
+ "----------- Certificate --------------\n"
+ "%s\n"
+ "--------------------------------------\n",
+ 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)
+ {
+ winpr_asprintf(&fp_str, &len,
+ "----------- Certificate --------------\n"
+ "%s\n"
+ "--------------------------------------\n",
+ fingerprint);
+ }
+ else
+ winpr_asprintf(&fp_str, &len, "Thumbprint: %s\n", fingerprint);
+
+ char* title = nullptr;
+ size_t tlen = 0;
+ winpr_asprintf(&title, &tlen, "New certificate for %s:%" PRIu16 " (%s)", host, port, type);
+
+ char* message = nullptr;
+ size_t mlen = 0;
+ winpr_asprintf(
+ &message, &mlen,
+ "Common Name: %s\n"
+ "Subject: %s\n"
+ "Issuer: %s\n"
+ "%s\n"
+ "The above X.509 certificate could not be verified, possibly because you do not have\n"
+ "the CA certificate in your certificate store, or the certificate has expired.\n"
+ "Please look at the OpenSSL documentation on how to add a private CA to the store.\n",
+ common_name, subject, issuer, fp_str);
+
+ SDLConnectionDialogHider hider(instance);
+ const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
+ free(fp_str);
+ free(title);
+ free(message);
+ return rc;
+}
+
+BOOL sdl_cert_dialog_show(const char* title, const char* message)
+{
+ int buttonid = -1;
+ enum
+ {
+ BUTTONID_CERT_ACCEPT_PERMANENT = 23,
+ BUTTONID_CERT_ACCEPT_TEMPORARY = 24,
+ BUTTONID_CERT_DENY = 25
+ };
+ const SDL_MessageBoxButtonData buttons[] = {
+ { 0, BUTTONID_CERT_ACCEPT_PERMANENT, "permanent" },
+ { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_CERT_ACCEPT_TEMPORARY, "temporary" },
+ { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_CERT_DENY, "cancel" }
+ };
+
+ const SDL_MessageBoxData data = { SDL_MESSAGEBOX_WARNING, nullptr, title, message,
+ ARRAYSIZE(buttons), buttons, nullptr };
+ const int rc = SDL_ShowMessageBox(&data, &buttonid);
+
+ Sint32 value = -1;
+ if (rc < 0)
+ value = 0;
+ else
+ {
+ switch (buttonid)
+ {
+ case BUTTONID_CERT_ACCEPT_PERMANENT:
+ value = 1;
+ break;
+ case BUTTONID_CERT_ACCEPT_TEMPORARY:
+ value = 2;
+ break;
+ default:
+ value = 0;
+ break;
+ }
+ }
+
+ return sdl_push_user_event(SDL_USEREVENT_CERT_RESULT, value);
+}
+
+BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags)
+{
+ int buttonid = -1;
+ enum
+ {
+ BUTTONID_SHOW_ACCEPT = 24,
+ BUTTONID_SHOW_DENY = 25
+ };
+ const SDL_MessageBoxButtonData buttons[] = {
+ { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_SHOW_ACCEPT, "accept" },
+ { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_SHOW_DENY, "cancel" }
+ };
+
+ const int button_cnt = (flags & SHOW_DIALOG_ACCEPT_REJECT) ? 2 : 1;
+ const SDL_MessageBoxData data = {
+ SDL_MESSAGEBOX_WARNING, nullptr, title, message, button_cnt, buttons, nullptr
+ };
+ const int rc = SDL_ShowMessageBox(&data, &buttonid);
+
+ Sint32 value = -1;
+ if (rc < 0)
+ value = 0;
+ else
+ {
+ switch (buttonid)
+ {
+ case BUTTONID_SHOW_ACCEPT:
+ value = 1;
+ break;
+ default:
+ value = 0;
+ break;
+ }
+ }
+
+ return sdl_push_user_event(SDL_USEREVENT_SHOW_RESULT, value);
+}
+
+BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args)
+{
+ const std::vector<std::string> auth = { "Username: ", "Domain: ",
+ "Password: " };
+ const std::vector<std::string> authPin = { "Device: ", "PIN: " };
+ const std::vector<std::string> gw = { "GatewayUsername: ", "GatewayDomain: ",
+ "GatewayPassword: " };
+ std::vector<std::string> prompt;
+ Sint32 rc = -1;
+
+ switch (args->result)
+ {
+ case AUTH_SMARTCARD_PIN:
+ prompt = authPin;
+ break;
+ case AUTH_TLS:
+ case AUTH_RDP:
+ case AUTH_NLA:
+ prompt = auth;
+ break;
+ case GW_AUTH_HTTP:
+ case GW_AUTH_RDG:
+ case GW_AUTH_RPC:
+ prompt = gw;
+ break;
+ default:
+ break;
+ }
+
+ std::vector<std::string> result;
+
+ if (!prompt.empty())
+ {
+ std::vector<std::string> initial{ args->user ? args->user : "Smartcard", "" };
+ std::vector<Uint32> flags = { SdlInputWidget::SDL_INPUT_READONLY,
+ SdlInputWidget::SDL_INPUT_MASK };
+ if (args->result != AUTH_SMARTCARD_PIN)
+ {
+ initial = { args->user ? args->user : "", args->domain ? args->domain : "",
+ args->password ? args->password : "" };
+ flags = { 0, 0, SdlInputWidget::SDL_INPUT_MASK };
+ }
+ SdlInputWidgetList ilist(args->title, prompt, initial, flags);
+ rc = ilist.run(result);
+ }
+
+ if ((result.size() < prompt.size()))
+ rc = -1;
+
+ char* user = nullptr;
+ char* domain = nullptr;
+ char* pwd = nullptr;
+ if (rc > 0)
+ {
+ user = _strdup(result[0].c_str());
+ if (args->result == AUTH_SMARTCARD_PIN)
+ pwd = _strdup(result[1].c_str());
+ else
+ {
+ domain = _strdup(result[1].c_str());
+ pwd = _strdup(result[2].c_str());
+ }
+ }
+ return sdl_push_user_event(SDL_USEREVENT_AUTH_RESULT, user, domain, pwd, rc);
+}
+
+BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list)
+{
+ std::vector<std::string> vlist;
+ vlist.reserve(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_USEREVENT_SCARD_RESULT, value);
+}
diff --git a/client/SDL/dialogs/sdl_dialogs.hpp b/client/SDL/dialogs/sdl_dialogs.hpp
new file mode 100644
index 0000000..ae9bbe6
--- /dev/null
+++ b/client/SDL/dialogs/sdl_dialogs.hpp
@@ -0,0 +1,53 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <winpr/wtypes.h>
+#include <freerdp/freerdp.h>
+
+#include "../sdl_types.hpp"
+#include "../sdl_utils.hpp"
+
+BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
+ rdp_auth_reason reason);
+BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
+ DWORD* choice, BOOL gateway);
+
+SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current, void* userarg);
+
+DWORD sdl_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject, const char* issuer,
+ const char* fingerprint, DWORD flags);
+
+DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject,
+ const char* issuer, const char* new_fingerprint,
+ const char* old_subject, const char* old_issuer,
+ const char* old_fingerprint, DWORD flags);
+
+int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type);
+
+BOOL sdl_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
+ BOOL isConsentMandatory, size_t length, const WCHAR* message);
+
+BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags);
+BOOL sdl_cert_dialog_show(const char* title, const char* message);
+BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list);
+BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args);
diff --git a/client/SDL/dialogs/sdl_input.cpp b/client/SDL/dialogs/sdl_input.cpp
new file mode 100644
index 0000000..6e7bf12
--- /dev/null
+++ b/client/SDL/dialogs/sdl_input.cpp
@@ -0,0 +1,177 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "sdl_input.hpp"
+
+#include <cassert>
+
+#include <string>
+
+#include <SDL.h>
+#include <SDL_ttf.h>
+
+#include "sdl_widget.hpp"
+#include "sdl_button.hpp"
+#include "sdl_buttons.hpp"
+
+static const SDL_Color inputbackgroundcolor = { 0x56, 0x56, 0x56, 0xff };
+static const SDL_Color inputhighlightcolor = { 0x80, 0, 0, 0x60 };
+static const SDL_Color inputmouseovercolor = { 0, 0x80, 0, 0x60 };
+static const SDL_Color inputfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+static const SDL_Color labelbackgroundcolor = { 0x56, 0x56, 0x56, 0xff };
+static const SDL_Color labelfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+static const Uint32 vpadding = 5;
+static const Uint32 hpadding = 10;
+
+SdlInputWidget::SdlInputWidget(SDL_Renderer* renderer, const std::string& label,
+ const std::string& initial, Uint32 flags, size_t offset,
+ size_t width, size_t height)
+ : _flags(flags), _text(initial), _text_label(label),
+ _label(renderer,
+ { 0, static_cast<int>(offset * (height + vpadding)), static_cast<int>(width),
+ static_cast<int>(height) },
+ false),
+ _input(renderer,
+ { static_cast<int>(width + hpadding), static_cast<int>(offset * (height + vpadding)),
+ static_cast<int>(width), static_cast<int>(height) },
+ true),
+ _highlight(false), _mouseover(false)
+{
+}
+
+SdlInputWidget::SdlInputWidget(SdlInputWidget&& other) noexcept
+ : _flags(std::move(other._flags)), _text(std::move(other._text)),
+ _text_label(std::move(other._text_label)), _label(std::move(other._label)),
+ _input(std::move(other._input)), _highlight(other._highlight), _mouseover(other._mouseover)
+{
+}
+
+bool SdlInputWidget::fill_label(SDL_Renderer* renderer, SDL_Color color)
+{
+ if (!_label.fill(renderer, color))
+ return false;
+ return _label.update_text(renderer, _text_label, labelfontcolor);
+}
+
+bool SdlInputWidget::update_label(SDL_Renderer* renderer)
+{
+ return _label.update_text(renderer, _text_label, labelfontcolor, labelbackgroundcolor);
+}
+
+bool SdlInputWidget::set_mouseover(SDL_Renderer* renderer, bool mouseOver)
+{
+ if (readonly())
+ return true;
+ _mouseover = mouseOver;
+ return update_input(renderer);
+}
+
+bool SdlInputWidget::set_highlight(SDL_Renderer* renderer, bool highlight)
+{
+ if (readonly())
+ return true;
+ _highlight = highlight;
+ return update_input(renderer);
+}
+
+bool SdlInputWidget::update_input(SDL_Renderer* renderer)
+{
+ std::vector<SDL_Color> colors = { inputbackgroundcolor };
+ if (_highlight)
+ colors.push_back(inputhighlightcolor);
+ if (_mouseover)
+ colors.push_back(inputmouseovercolor);
+
+ if (!_input.fill(renderer, colors))
+ return false;
+ return update_input(renderer, inputfontcolor);
+}
+
+bool SdlInputWidget::resize_input(size_t size)
+{
+ _text.resize(size);
+
+ return true;
+}
+
+bool SdlInputWidget::set_str(SDL_Renderer* renderer, const std::string& text)
+{
+ if (readonly())
+ return true;
+ _text = text;
+ if (!resize_input(_text.size()))
+ return false;
+ return update_input(renderer);
+}
+
+bool SdlInputWidget::remove_str(SDL_Renderer* renderer, size_t count)
+{
+ if (readonly())
+ return true;
+
+ assert(renderer);
+ if (_text.empty())
+ return true;
+
+ if (!resize_input(_text.size() - count))
+ return false;
+ return update_input(renderer);
+}
+
+bool SdlInputWidget::append_str(SDL_Renderer* renderer, const std::string& str)
+{
+ assert(renderer);
+ if (readonly())
+ return true;
+
+ _text.append(str);
+ if (!resize_input(_text.size()))
+ return false;
+ return update_input(renderer);
+}
+
+const SDL_Rect& SdlInputWidget::input_rect() const
+{
+ return _input.rect();
+}
+
+std::string SdlInputWidget::value() const
+{
+ return _text;
+}
+
+bool SdlInputWidget::readonly() const
+{
+ return (_flags & SDL_INPUT_READONLY) != 0;
+}
+
+bool SdlInputWidget::update_input(SDL_Renderer* renderer, SDL_Color fgcolor)
+{
+ std::string text = _text;
+ if (!text.empty())
+ {
+ if (_flags & SDL_INPUT_MASK)
+ {
+ for (char& x : text)
+ x = '*';
+ }
+ }
+
+ return _input.update_text(renderer, text, fgcolor);
+}
diff --git a/client/SDL/dialogs/sdl_input.hpp b/client/SDL/dialogs/sdl_input.hpp
new file mode 100644
index 0000000..11492d1
--- /dev/null
+++ b/client/SDL/dialogs/sdl_input.hpp
@@ -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 <vector>
+#include <string>
+
+#include <SDL.h>
+#include "sdl_widget.hpp"
+
+class SdlInputWidget
+{
+ public:
+ enum
+ {
+ SDL_INPUT_MASK = 1,
+ SDL_INPUT_READONLY = 2
+ };
+
+ public:
+ SdlInputWidget(SDL_Renderer* renderer, const std::string& label, const std::string& initial,
+ Uint32 flags, size_t offset, size_t width, size_t height);
+ SdlInputWidget(SdlInputWidget&& other) noexcept;
+
+ bool fill_label(SDL_Renderer* renderer, SDL_Color color);
+ bool update_label(SDL_Renderer* renderer);
+
+ bool set_mouseover(SDL_Renderer* renderer, bool mouseOver);
+ bool set_highlight(SDL_Renderer* renderer, bool hightlight);
+ bool update_input(SDL_Renderer* renderer);
+ bool resize_input(size_t size);
+
+ bool set_str(SDL_Renderer* renderer, const std::string& text);
+ bool remove_str(SDL_Renderer* renderer, size_t count);
+ bool append_str(SDL_Renderer* renderer, const std::string& text);
+
+ [[nodiscard]] const SDL_Rect& input_rect() const;
+ [[nodiscard]] std::string value() const;
+
+ [[nodiscard]] bool readonly() const;
+
+ protected:
+ bool update_input(SDL_Renderer* renderer, SDL_Color fgclor);
+
+ private:
+ SdlInputWidget(const SdlInputWidget& other) = delete;
+
+ private:
+ Uint32 _flags;
+ std::string _text;
+ std::string _text_label;
+ SdlWidget _label;
+ SdlWidget _input;
+ bool _highlight;
+ bool _mouseover;
+};
diff --git a/client/SDL/dialogs/sdl_input_widgets.cpp b/client/SDL/dialogs/sdl_input_widgets.cpp
new file mode 100644
index 0000000..5846308
--- /dev/null
+++ b/client/SDL/dialogs/sdl_input_widgets.cpp
@@ -0,0 +1,299 @@
+#include <cassert>
+#include <algorithm>
+
+#include "sdl_input_widgets.hpp"
+
+static const Uint32 vpadding = 5;
+
+SdlInputWidgetList::SdlInputWidgetList(const std::string& title,
+ const std::vector<std::string>& labels,
+ const std::vector<std::string>& initial,
+ const std::vector<Uint32>& flags)
+ : _window(nullptr), _renderer(nullptr)
+{
+ assert(labels.size() == initial.size());
+ assert(labels.size() == flags.size());
+ const std::vector<int> buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL };
+ const std::vector<std::string> buttonlabels = { "accept", "cancel" };
+
+ const size_t widget_width = 300;
+ const size_t widget_heigth = 50;
+
+ const size_t total_width = widget_width + widget_width;
+ const size_t input_height = labels.size() * (widget_heigth + vpadding) + vpadding;
+ const size_t total_height = input_height + widget_heigth;
+ _window = SDL_CreateWindow(
+ title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, total_width, total_height,
+ SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS);
+ if (_window == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateWindow");
+ }
+ else
+ {
+
+ _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
+ if (_renderer == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateRenderer");
+ }
+ else
+ {
+ for (size_t x = 0; x < labels.size(); x++)
+ _list.emplace_back(_renderer, labels[x], initial[x], flags[x], x, widget_width,
+ widget_heigth);
+
+ _buttons.populate(_renderer, buttonlabels, buttonids, total_width,
+ static_cast<Sint32>(input_height), static_cast<Sint32>(widget_width),
+ static_cast<Sint32>(widget_heigth));
+ _buttons.set_highlight(0);
+ }
+ }
+}
+
+ssize_t SdlInputWidgetList::next(ssize_t current)
+{
+ size_t iteration = 0;
+ auto val = static_cast<size_t>(current);
+
+ do
+ {
+ if (iteration >= _list.size())
+ return -1;
+
+ if (iteration == 0)
+ {
+ if (current < 0)
+ val = 0;
+ else
+ val++;
+ }
+ else
+ val++;
+ iteration++;
+ val %= _list.size();
+ } while (!valid(static_cast<ssize_t>(val)));
+ return static_cast<ssize_t>(val);
+}
+
+bool SdlInputWidgetList::valid(ssize_t current) const
+{
+ if (current < 0)
+ return false;
+ auto s = static_cast<size_t>(current);
+ if (s >= _list.size())
+ return false;
+ return !_list[s].readonly();
+}
+
+SdlInputWidget* SdlInputWidgetList::get(ssize_t index)
+{
+ if (index < 0)
+ return nullptr;
+ auto s = static_cast<size_t>(index);
+ if (s >= _list.size())
+ return nullptr;
+ return &_list[s];
+}
+
+SdlInputWidgetList::~SdlInputWidgetList()
+{
+ _list.clear();
+ _buttons.clear();
+ SDL_DestroyRenderer(_renderer);
+ SDL_DestroyWindow(_window);
+}
+
+bool SdlInputWidgetList::update(SDL_Renderer* renderer)
+{
+ for (auto& btn : _list)
+ {
+ if (!btn.update_label(renderer))
+ return false;
+ if (!btn.update_input(renderer))
+ return false;
+ }
+
+ return _buttons.update(renderer);
+}
+
+ssize_t SdlInputWidgetList::get_index(const SDL_MouseButtonEvent& button)
+{
+ const Sint32 x = button.x;
+ const Sint32 y = button.y;
+ for (size_t i = 0; i < _list.size(); i++)
+ {
+ auto& cur = _list[i];
+ auto r = cur.input_rect();
+
+ if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
+ return i;
+ }
+ return -1;
+}
+
+int SdlInputWidgetList::run(std::vector<std::string>& result)
+{
+ int res = -1;
+ ssize_t LastActiveTextInput = -1;
+ ssize_t CurrentActiveTextInput = next(-1);
+
+ if (!_window || !_renderer)
+ return -2;
+
+ try
+ {
+ bool running = true;
+ std::vector<SDL_Keycode> pressed;
+ while (running)
+ {
+ if (!clear_window(_renderer))
+ throw;
+
+ if (!update(_renderer))
+ throw;
+
+ if (!_buttons.update(_renderer))
+ throw;
+
+ SDL_Event event = {};
+ SDL_WaitEvent(&event);
+ switch (event.type)
+ {
+ case SDL_KEYUP:
+ {
+ auto it = std::remove(pressed.begin(), pressed.end(), event.key.keysym.sym);
+ pressed.erase(it, pressed.end());
+ }
+ break;
+ case SDL_KEYDOWN:
+ pressed.push_back(event.key.keysym.sym);
+ switch (event.key.keysym.sym)
+ {
+ case SDLK_BACKSPACE:
+ {
+ auto cur = get(CurrentActiveTextInput);
+ if (cur)
+ {
+ if (!cur->remove_str(_renderer, 1))
+ throw;
+ }
+ }
+ break;
+ case SDLK_TAB:
+ CurrentActiveTextInput = next(CurrentActiveTextInput);
+ break;
+ case SDLK_RETURN:
+ case SDLK_RETURN2:
+ case SDLK_KP_ENTER:
+ running = false;
+ res = INPUT_BUTTON_ACCEPT;
+ break;
+ case SDLK_ESCAPE:
+ running = false;
+ res = INPUT_BUTTON_CANCEL;
+ break;
+ case SDLK_v:
+ if (pressed.size() == 2)
+ {
+ if ((pressed[0] == SDLK_LCTRL) || (pressed[0] == SDLK_RCTRL))
+ {
+ auto cur = get(CurrentActiveTextInput);
+ if (cur)
+ {
+ auto text = SDL_GetClipboardText();
+ cur->set_str(_renderer, text);
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case SDL_TEXTINPUT:
+ {
+ auto cur = get(CurrentActiveTextInput);
+ if (cur)
+ {
+ if (!cur->append_str(_renderer, event.text.text))
+ throw;
+ }
+ }
+ break;
+ case SDL_MOUSEMOTION:
+ {
+ auto TextInputIndex = get_index(event.button);
+ for (auto& cur : _list)
+ {
+ if (!cur.set_mouseover(_renderer, false))
+ throw;
+ }
+ if (TextInputIndex >= 0)
+ {
+ auto& cur = _list[static_cast<size_t>(TextInputIndex)];
+ if (!cur.set_mouseover(_renderer, true))
+ throw;
+ }
+
+ _buttons.set_mouseover(event.button.x, event.button.y);
+ }
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ {
+ auto val = get_index(event.button);
+ if (valid(val))
+ CurrentActiveTextInput = val;
+
+ auto button = _buttons.get_selected(event.button);
+ if (button)
+ {
+ running = false;
+ if (button->id() == INPUT_BUTTON_CANCEL)
+ res = INPUT_BUTTON_CANCEL;
+ else
+ res = INPUT_BUTTON_ACCEPT;
+ }
+ }
+ break;
+ case SDL_QUIT:
+ res = INPUT_BUTTON_CANCEL;
+ running = false;
+ break;
+ default:
+ break;
+ }
+
+ if (LastActiveTextInput != CurrentActiveTextInput)
+ {
+ if (CurrentActiveTextInput < 0)
+ SDL_StopTextInput();
+ else
+ SDL_StartTextInput();
+ LastActiveTextInput = CurrentActiveTextInput;
+ }
+
+ for (auto& cur : _list)
+ {
+ if (!cur.set_highlight(_renderer, false))
+ throw;
+ }
+ auto cur = get(CurrentActiveTextInput);
+ if (cur)
+ {
+ if (!cur->set_highlight(_renderer, true))
+ throw;
+ }
+
+ SDL_RenderPresent(_renderer);
+ }
+
+ for (auto& cur : _list)
+ result.push_back(cur.value());
+ }
+ catch (...)
+ {
+ }
+
+ return res;
+}
diff --git a/client/SDL/dialogs/sdl_input_widgets.hpp b/client/SDL/dialogs/sdl_input_widgets.hpp
new file mode 100644
index 0000000..83568ba
--- /dev/null
+++ b/client/SDL/dialogs/sdl_input_widgets.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include <SDL.h>
+
+#include "sdl_input.hpp"
+#include "sdl_buttons.hpp"
+
+class SdlInputWidgetList
+{
+ public:
+ SdlInputWidgetList(const std::string& title, const std::vector<std::string>& labels,
+ const std::vector<std::string>& initial, const std::vector<Uint32>& flags);
+ virtual ~SdlInputWidgetList();
+
+ int run(std::vector<std::string>& result);
+
+ protected:
+ bool update(SDL_Renderer* renderer);
+ ssize_t get_index(const SDL_MouseButtonEvent& button);
+
+ private:
+ SdlInputWidgetList(const SdlInputWidgetList& other) = delete;
+ SdlInputWidgetList(SdlInputWidgetList&& other) = delete;
+
+ private:
+ enum
+ {
+ INPUT_BUTTON_ACCEPT = 1,
+ INPUT_BUTTON_CANCEL = -2
+ };
+
+ private:
+ ssize_t next(ssize_t current);
+ [[nodiscard]] bool valid(ssize_t current) const;
+ SdlInputWidget* get(ssize_t index);
+
+ private:
+ SDL_Window* _window;
+ SDL_Renderer* _renderer;
+ std::vector<SdlInputWidget> _list;
+ SdlButtonList _buttons;
+};
diff --git a/client/SDL/dialogs/sdl_select.cpp b/client/SDL/dialogs/sdl_select.cpp
new file mode 100644
index 0000000..f0e0327
--- /dev/null
+++ b/client/SDL/dialogs/sdl_select.cpp
@@ -0,0 +1,74 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cassert>
+
+#include <string>
+
+#include <SDL.h>
+#include <SDL_ttf.h>
+
+#include "sdl_select.hpp"
+#include "sdl_widget.hpp"
+#include "sdl_button.hpp"
+#include "sdl_buttons.hpp"
+#include "sdl_input_widgets.hpp"
+
+static const SDL_Color labelmouseovercolor = { 0, 0x80, 0, 0x60 };
+static const SDL_Color labelbackgroundcolor = { 0x69, 0x66, 0x63, 0xff };
+static const SDL_Color labelhighlightcolor = { 0xcd, 0xca, 0x35, 0x60 };
+static const SDL_Color labelfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+
+SdlSelectWidget::SdlSelectWidget(SDL_Renderer* renderer, const std::string& label,
+ const SDL_Rect& rect)
+ : SdlWidget(renderer, rect, true), _text(label), _mouseover(false), _highlight(false)
+{
+ update_text(renderer);
+}
+
+SdlSelectWidget::SdlSelectWidget(SdlSelectWidget&& other) noexcept
+ : SdlWidget(std::move(other)), _text(std::move(other._text)), _mouseover(other._mouseover),
+ _highlight(other._highlight)
+{
+}
+
+bool SdlSelectWidget::set_mouseover(SDL_Renderer* renderer, bool mouseOver)
+{
+ _mouseover = mouseOver;
+ return update_text(renderer);
+}
+
+bool SdlSelectWidget::set_highlight(SDL_Renderer* renderer, bool highlight)
+{
+ _highlight = highlight;
+ return update_text(renderer);
+}
+
+bool SdlSelectWidget::update_text(SDL_Renderer* renderer)
+{
+ assert(renderer);
+ std::vector<SDL_Color> colors = { labelbackgroundcolor };
+ if (_highlight)
+ colors.push_back(labelhighlightcolor);
+ if (_mouseover)
+ colors.push_back(labelmouseovercolor);
+ if (!fill(renderer, colors))
+ return false;
+ return SdlWidget::update_text(renderer, _text, labelfontcolor);
+}
diff --git a/client/SDL/dialogs/sdl_select.hpp b/client/SDL/dialogs/sdl_select.hpp
new file mode 100644
index 0000000..af67b74
--- /dev/null
+++ b/client/SDL/dialogs/sdl_select.hpp
@@ -0,0 +1,46 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <SDL.h>
+#include "sdl_widget.hpp"
+
+class SdlSelectWidget : public SdlWidget
+{
+ public:
+ SdlSelectWidget(SDL_Renderer* renderer, const std::string& label, const SDL_Rect& rect);
+ SdlSelectWidget(SdlSelectWidget&& other) noexcept;
+ ~SdlSelectWidget() override = default;
+
+ bool set_mouseover(SDL_Renderer* renderer, bool mouseOver);
+ bool set_highlight(SDL_Renderer* renderer, bool highlight);
+ bool update_text(SDL_Renderer* renderer);
+
+ private:
+ SdlSelectWidget(const SdlSelectWidget& other) = delete;
+
+ private:
+ std::string _text;
+ bool _mouseover;
+ bool _highlight;
+};
diff --git a/client/SDL/dialogs/sdl_selectlist.cpp b/client/SDL/dialogs/sdl_selectlist.cpp
new file mode 100644
index 0000000..20437cc
--- /dev/null
+++ b/client/SDL/dialogs/sdl_selectlist.cpp
@@ -0,0 +1,208 @@
+#include "sdl_selectlist.hpp"
+
+static const Uint32 vpadding = 5;
+
+SdlSelectList::SdlSelectList(const std::string& title, const std::vector<std::string>& labels)
+ : _window(nullptr), _renderer(nullptr)
+{
+ const size_t widget_height = 50;
+ const size_t widget_width = 600;
+
+ const size_t total_height = labels.size() * (widget_height + vpadding) + vpadding;
+ _window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
+ widget_width, total_height + widget_height,
+ SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS |
+ SDL_WINDOW_INPUT_FOCUS);
+ if (_window == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateWindow");
+ }
+ else
+ {
+ _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
+ if (_renderer == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateRenderer");
+ }
+ else
+ {
+ SDL_Rect rect = { 0, 0, widget_width, widget_height };
+ for (auto& label : labels)
+ {
+ _list.emplace_back(_renderer, label, rect);
+ rect.y += widget_height + vpadding;
+ }
+
+ const std::vector<int> buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL };
+ const std::vector<std::string> buttonlabels = { "accept", "cancel" };
+ _buttons.populate(
+ _renderer, buttonlabels, buttonids, widget_width, static_cast<Sint32>(total_height),
+ static_cast<Sint32>(widget_width / 2), static_cast<Sint32>(widget_height));
+ _buttons.set_highlight(0);
+ }
+ }
+}
+
+SdlSelectList::~SdlSelectList()
+{
+ _list.clear();
+ _buttons.clear();
+ SDL_DestroyRenderer(_renderer);
+ SDL_DestroyWindow(_window);
+}
+
+int SdlSelectList::run()
+{
+ int res = -2;
+ ssize_t CurrentActiveTextInput = 0;
+ bool running = true;
+
+ if (!_window || !_renderer)
+ return -2;
+ try
+ {
+ while (running)
+ {
+ if (!clear_window(_renderer))
+ throw;
+
+ if (!update_text())
+ throw;
+
+ if (!_buttons.update(_renderer))
+ throw;
+
+ SDL_Event event = { 0 };
+ SDL_WaitEvent(&event);
+ switch (event.type)
+ {
+ case SDL_KEYDOWN:
+ switch (event.key.keysym.sym)
+ {
+ case SDLK_UP:
+ case SDLK_BACKSPACE:
+ if (CurrentActiveTextInput > 0)
+ CurrentActiveTextInput--;
+ else
+ CurrentActiveTextInput = _list.size() - 1;
+ break;
+ case SDLK_DOWN:
+ case SDLK_TAB:
+ if (CurrentActiveTextInput < 0)
+ CurrentActiveTextInput = 0;
+ else
+ CurrentActiveTextInput++;
+ CurrentActiveTextInput = CurrentActiveTextInput % _list.size();
+ break;
+ case SDLK_RETURN:
+ case SDLK_RETURN2:
+ case SDLK_KP_ENTER:
+ running = false;
+ res = CurrentActiveTextInput;
+ break;
+ case SDLK_ESCAPE:
+ running = false;
+ res = INPUT_BUTTON_CANCEL;
+ break;
+ default:
+ break;
+ }
+ break;
+ case SDL_MOUSEMOTION:
+ {
+ ssize_t TextInputIndex = get_index(event.button);
+ reset_mouseover();
+ if (TextInputIndex >= 0)
+ {
+ auto& cur = _list[TextInputIndex];
+ if (!cur.set_mouseover(_renderer, true))
+ throw;
+ }
+
+ _buttons.set_mouseover(event.button.x, event.button.y);
+ }
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ {
+ auto button = _buttons.get_selected(event.button);
+ if (button)
+ {
+ running = false;
+ if (button->id() == INPUT_BUTTON_CANCEL)
+ res = INPUT_BUTTON_CANCEL;
+ else
+ res = CurrentActiveTextInput;
+ }
+ else
+ {
+ CurrentActiveTextInput = get_index(event.button);
+ }
+ }
+ break;
+ case SDL_QUIT:
+ res = INPUT_BUTTON_CANCEL;
+ running = false;
+ break;
+ default:
+ break;
+ }
+
+ reset_highlight();
+ if (CurrentActiveTextInput >= 0)
+ {
+ auto& cur = _list[CurrentActiveTextInput];
+ if (!cur.set_highlight(_renderer, true))
+ throw;
+ }
+
+ SDL_RenderPresent(_renderer);
+ }
+ }
+ catch (...)
+ {
+ return -1;
+ }
+ return res;
+}
+
+ssize_t SdlSelectList::get_index(const SDL_MouseButtonEvent& button)
+{
+ const Sint32 x = button.x;
+ const Sint32 y = button.y;
+ for (size_t i = 0; i < _list.size(); i++)
+ {
+ auto& cur = _list[i];
+ auto r = cur.rect();
+
+ if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
+ return i;
+ }
+ return -1;
+}
+
+bool SdlSelectList::update_text()
+{
+ for (auto& cur : _list)
+ {
+ if (!cur.update_text(_renderer))
+ return false;
+ }
+
+ return true;
+}
+
+void SdlSelectList::reset_mouseover()
+{
+ for (auto& cur : _list)
+ {
+ cur.set_mouseover(_renderer, false);
+ }
+}
+
+void SdlSelectList::reset_highlight()
+{
+ for (auto& cur : _list)
+ {
+ cur.set_highlight(_renderer, false);
+ }
+}
diff --git a/client/SDL/dialogs/sdl_selectlist.hpp b/client/SDL/dialogs/sdl_selectlist.hpp
new file mode 100644
index 0000000..3da0e14
--- /dev/null
+++ b/client/SDL/dialogs/sdl_selectlist.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <SDL.h>
+
+#include "sdl_select.hpp"
+#include "sdl_button.hpp"
+#include "sdl_buttons.hpp"
+
+class SdlSelectList
+{
+ public:
+ SdlSelectList(const std::string& title, const std::vector<std::string>& labels);
+ virtual ~SdlSelectList();
+
+ int run();
+
+ private:
+ SdlSelectList(const SdlSelectList& other) = delete;
+ SdlSelectList(SdlSelectList&& other) = delete;
+
+ private:
+ enum
+ {
+ INPUT_BUTTON_ACCEPT = 0,
+ INPUT_BUTTON_CANCEL = -2
+ };
+
+ private:
+ ssize_t get_index(const SDL_MouseButtonEvent& button);
+ bool update_text();
+ void reset_mouseover();
+ void reset_highlight();
+
+ private:
+ SDL_Window* _window;
+ SDL_Renderer* _renderer;
+ std::vector<SdlSelectWidget> _list;
+ SdlButtonList _buttons;
+};
diff --git a/client/SDL/dialogs/sdl_widget.cpp b/client/SDL/dialogs/sdl_widget.cpp
new file mode 100644
index 0000000..6e11b5a
--- /dev/null
+++ b/client/SDL/dialogs/sdl_widget.cpp
@@ -0,0 +1,280 @@
+/**
+ * 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 <SDL.h>
+#include <SDL_ttf.h>
+
+#include "sdl_widget.hpp"
+#include "../sdl_utils.hpp"
+
+#include "res/sdl_resource_manager.hpp"
+
+#include <freerdp/log.h>
+
+#if defined(WITH_SDL_IMAGE_DIALOGS)
+#include <SDL_image.h>
+#endif
+
+#define TAG CLIENT_TAG("SDL.widget")
+
+static const SDL_Color backgroundcolor = { 0x38, 0x36, 0x35, 0xff };
+
+static const Uint32 hpadding = 10;
+
+SdlWidget::SdlWidget(SDL_Renderer* renderer, const SDL_Rect& rect, bool input)
+ : _rect(rect), _input(input)
+{
+ assert(renderer);
+
+ auto ops = SDLResourceManager::get(SDLResourceManager::typeFonts(),
+ "OpenSans-VariableFont_wdth,wght.ttf");
+ if (!ops)
+ widget_log_error(-1, "SDLResourceManager::get");
+ else
+ {
+ _font = TTF_OpenFontRW(ops, 1, 64);
+ if (!_font)
+ widget_log_error(-1, "TTF_OpenFontRW");
+ }
+}
+
+#if defined(WITH_SDL_IMAGE_DIALOGS)
+SdlWidget::SdlWidget(SDL_Renderer* renderer, const SDL_Rect& rect, SDL_RWops* ops) : _rect(rect)
+{
+ if (ops)
+ {
+ _image = IMG_LoadTexture_RW(renderer, ops, 1);
+ if (!_image)
+ widget_log_error(-1, "IMG_LoadTextureTyped_RW");
+ }
+}
+#endif
+
+SdlWidget::SdlWidget(SdlWidget&& other) noexcept
+ : _font(std::move(other._font)), _image(other._image), _rect(std::move(other._rect)),
+ _input(other._input), _wrap(other._wrap), _text_width(other._text_width)
+{
+ other._font = nullptr;
+ other._image = nullptr;
+}
+
+SDL_Texture* SdlWidget::render_text(SDL_Renderer* renderer, const std::string& text,
+ SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst)
+{
+ auto surface = TTF_RenderUTF8_Blended(_font, text.c_str(), fgcolor);
+ if (!surface)
+ {
+ widget_log_error(-1, "TTF_RenderText_Blended");
+ return nullptr;
+ }
+
+ auto texture = SDL_CreateTextureFromSurface(renderer, surface);
+ SDL_FreeSurface(surface);
+ if (!texture)
+ {
+ widget_log_error(-1, "SDL_CreateTextureFromSurface");
+ return nullptr;
+ }
+
+ TTF_SizeUTF8(_font, text.c_str(), &src.w, &src.h);
+
+ /* Do some magic:
+ * - Add padding before and after text
+ * - if text is too long only show the last elements
+ * - if text is too short only update used space
+ */
+ dst = _rect;
+ dst.x += hpadding;
+ dst.w -= 2 * hpadding;
+ const float scale = static_cast<float>(dst.h) / static_cast<float>(src.h);
+ const float sws = static_cast<float>(src.w) * scale;
+ const float dws = static_cast<float>(dst.w) / scale;
+ if (static_cast<float>(dst.w) > sws)
+ dst.w = static_cast<int>(sws);
+ if (static_cast<float>(src.w) > dws)
+ {
+ src.x = src.w - static_cast<int>(dws);
+ src.w = static_cast<int>(dws);
+ }
+ return texture;
+}
+
+SDL_Texture* SdlWidget::render_text_wrapped(SDL_Renderer* renderer, const std::string& text,
+ SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst)
+{
+ Sint32 w = 0;
+ Sint32 h = 0;
+ TTF_SizeUTF8(_font, " ", &w, &h);
+ auto surface = TTF_RenderUTF8_Blended_Wrapped(_font, text.c_str(), fgcolor, _text_width);
+ if (!surface)
+ {
+ widget_log_error(-1, "TTF_RenderText_Blended");
+ return nullptr;
+ }
+
+ src.w = surface->w;
+ src.h = surface->h;
+
+ auto texture = SDL_CreateTextureFromSurface(renderer, surface);
+ SDL_FreeSurface(surface);
+ if (!texture)
+ {
+ widget_log_error(-1, "SDL_CreateTextureFromSurface");
+ return nullptr;
+ }
+
+ /* Do some magic:
+ * - Add padding before and after text
+ * - if text is too long only show the last elements
+ * - if text is too short only update used space
+ */
+ dst = _rect;
+ dst.x += hpadding;
+ dst.w -= 2 * hpadding;
+ const float scale = static_cast<float>(src.h) / static_cast<float>(src.w);
+ auto dh = src.h * scale;
+ if (dh < dst.h)
+ dst.h = dh;
+
+ return texture;
+}
+
+SdlWidget::~SdlWidget()
+{
+ TTF_CloseFont(_font);
+ if (_image)
+ SDL_DestroyTexture(_image);
+}
+
+bool SdlWidget::error_ex(Uint32 res, const char* what, const char* file, size_t line,
+ const char* fkt)
+{
+ static wLog* log = nullptr;
+ if (!log)
+ log = WLog_Get(TAG);
+ return sdl_log_error_ex(res, log, what, file, line, fkt);
+}
+
+static bool draw_rect(SDL_Renderer* renderer, const SDL_Rect* rect, SDL_Color color)
+{
+ const int drc = SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
+ if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
+ return false;
+
+ const int rc = SDL_RenderFillRect(renderer, rect);
+ return !widget_log_error(rc, "SDL_RenderFillRect");
+}
+
+bool SdlWidget::fill(SDL_Renderer* renderer, SDL_Color color)
+{
+ std::vector<SDL_Color> colors = { color };
+ return fill(renderer, colors);
+}
+
+bool SdlWidget::fill(SDL_Renderer* renderer, const std::vector<SDL_Color>& colors)
+{
+ assert(renderer);
+ SDL_BlendMode mode = SDL_BLENDMODE_INVALID;
+ SDL_GetRenderDrawBlendMode(renderer, &mode);
+ SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
+ for (auto color : colors)
+ {
+ draw_rect(renderer, &_rect, color);
+ SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD);
+ }
+ SDL_SetRenderDrawBlendMode(renderer, mode);
+ return true;
+}
+
+bool SdlWidget::update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
+ SDL_Color bgcolor)
+{
+ assert(renderer);
+
+ if (!fill(renderer, bgcolor))
+ return false;
+ return update_text(renderer, text, fgcolor);
+}
+
+bool SdlWidget::wrap() const
+{
+ return _wrap;
+}
+
+bool SdlWidget::set_wrap(bool wrap, size_t width)
+{
+ _wrap = wrap;
+ _text_width = width;
+ return _wrap;
+}
+
+const SDL_Rect& SdlWidget::rect() const
+{
+ return _rect;
+}
+
+bool SdlWidget::update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor)
+{
+
+ if (text.empty())
+ return true;
+
+ SDL_Rect src{};
+ SDL_Rect dst{};
+
+ SDL_Texture* texture = nullptr;
+ if (_image)
+ {
+ texture = _image;
+ dst = _rect;
+ auto rc = SDL_QueryTexture(_image, nullptr, nullptr, &src.w, &src.h);
+ if (rc < 0)
+ widget_log_error(rc, "SDL_QueryTexture");
+ }
+ else if (_wrap)
+ texture = render_text_wrapped(renderer, text, fgcolor, src, dst);
+ else
+ texture = render_text(renderer, text, fgcolor, src, dst);
+ if (!texture)
+ return false;
+
+ const int rc = SDL_RenderCopy(renderer, texture, &src, &dst);
+ if (!_image)
+ SDL_DestroyTexture(texture);
+ if (rc < 0)
+ return !widget_log_error(rc, "SDL_RenderCopy");
+ return true;
+}
+
+bool clear_window(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ const int drc = SDL_SetRenderDrawColor(renderer, backgroundcolor.r, backgroundcolor.g,
+ backgroundcolor.b, backgroundcolor.a);
+ if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
+ return false;
+
+ const int rcls = SDL_RenderClear(renderer);
+ return !widget_log_error(rcls, "SDL_RenderClear");
+}
diff --git a/client/SDL/dialogs/sdl_widget.hpp b/client/SDL/dialogs/sdl_widget.hpp
new file mode 100644
index 0000000..ebc7dbd
--- /dev/null
+++ b/client/SDL/dialogs/sdl_widget.hpp
@@ -0,0 +1,88 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <vector>
+#include <SDL.h>
+#include <SDL_ttf.h>
+
+#if defined(_MSC_VER)
+#include <BaseTsd.h>
+typedef SSIZE_T ssize_t;
+#endif
+
+#if !defined(HAS_NOEXCEPT)
+#if defined(__clang__)
+#if __has_feature(cxx_noexcept)
+#define HAS_NOEXCEPT
+#endif
+#elif defined(__GXX_EXPERIMENTAL_CXX0X__) && __GNUC__ * 10 + __GNUC_MINOR__ >= 46 || \
+ defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023026
+#define HAS_NOEXCEPT
+#endif
+#endif
+
+#ifndef HAS_NOEXCEPT
+#define noexcept
+#endif
+
+class SdlWidget
+{
+ public:
+ SdlWidget(SDL_Renderer* renderer, const SDL_Rect& rect, bool input);
+ SdlWidget(SDL_Renderer* renderer, const SDL_Rect& rect, SDL_RWops* ops);
+ SdlWidget(SdlWidget&& other) noexcept;
+ virtual ~SdlWidget();
+
+ bool fill(SDL_Renderer* renderer, SDL_Color color);
+ bool fill(SDL_Renderer* renderer, const std::vector<SDL_Color>& colors);
+ bool update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor);
+ bool update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
+ SDL_Color bgcolor);
+
+ bool wrap() const;
+ bool set_wrap(bool wrap = true, size_t width = 0);
+ const SDL_Rect& rect() const;
+
+ public:
+#define widget_log_error(res, what) SdlWidget::error_ex(res, what, __FILE__, __LINE__, __func__)
+ static bool error_ex(Uint32 res, const char* what, const char* file, size_t line,
+ const char* fkt);
+
+ private:
+ SdlWidget(const SdlWidget& other) = delete;
+
+ SDL_Texture* render_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
+ SDL_Rect& src, SDL_Rect& dst);
+ SDL_Texture* render_text_wrapped(SDL_Renderer* renderer, const std::string& text,
+ SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst);
+
+ private:
+ TTF_Font* _font = nullptr;
+ SDL_Texture* _image = nullptr;
+ SDL_Rect _rect;
+ bool _input = false;
+ bool _wrap = false;
+ size_t _text_width = 0;
+};
+
+bool clear_window(SDL_Renderer* renderer);
diff --git a/client/SDL/dialogs/test/CMakeLists.txt b/client/SDL/dialogs/test/CMakeLists.txt
new file mode 100644
index 0000000..c1003d4
--- /dev/null
+++ b/client/SDL/dialogs/test/CMakeLists.txt
@@ -0,0 +1,30 @@
+set(MODULE_NAME "TestSDL")
+set(MODULE_PREFIX "TEST_SDL")
+
+set(DRIVER ${MODULE_NAME}.cpp)
+
+set(TEST_SRCS
+ TestSDLDialogs.cpp
+)
+
+create_test_sourcelist(SRCS
+ ${DRIVER}
+ ${TEST_SRCS})
+
+add_library(${MODULE_NAME} ${SRCS})
+
+list(APPEND LIBS
+ dialogs
+)
+
+target_link_libraries(${MODULE_NAME} ${LIBS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Client/Test")
+
diff --git a/client/SDL/dialogs/test/TestSDLDialogs.cpp b/client/SDL/dialogs/test/TestSDLDialogs.cpp
new file mode 100644
index 0000000..558fb4c
--- /dev/null
+++ b/client/SDL/dialogs/test/TestSDLDialogs.cpp
@@ -0,0 +1,99 @@
+#include "../sdl_selectlist.hpp"
+#include "../sdl_input_widgets.hpp"
+
+#include <freerdp/api.h>
+#include <winpr/wlog.h>
+
+BOOL sdl_log_error_ex(Uint32 res, wLog* log, const char* what, const char* file, size_t line,
+ const char* fkt)
+{
+ return FALSE;
+}
+
+static bool test_input_dialog()
+{
+ const auto title = "sometitle";
+ std::vector<std::string> labels;
+ std::vector<std::string> initial;
+ std::vector<Uint32> flags;
+ for (size_t x = 0; x < 12; x++)
+ {
+ labels.push_back("label" + std::to_string(x));
+ initial.push_back(std::to_string(x));
+
+ Uint32 flag = 0;
+ if ((x % 2) != 0)
+ flag |= SdlInputWidget::SDL_INPUT_MASK;
+ if ((x % 3) == 0)
+ flag |= SdlInputWidget::SDL_INPUT_READONLY;
+
+ flags.push_back(flag);
+ }
+ SdlInputWidgetList list{ title, labels, initial, flags };
+ std::vector<std::string> result;
+ auto rc = list.run(result);
+ if (rc < 0)
+ {
+ return false;
+ }
+ if (result.size() != labels.size())
+ {
+ return false;
+ }
+ return true;
+}
+
+static bool test_select_dialog()
+{
+ const auto title = "sometitle";
+ std::vector<std::string> labels;
+ for (size_t x = 0; x < 12; x++)
+ {
+ labels.push_back("label" + std::to_string(x));
+ }
+ SdlSelectList list{ title, labels };
+ auto rc = list.run();
+ if (rc < 0)
+ {
+ return false;
+ }
+ if (static_cast<size_t>(rc) >= labels.size())
+ return false;
+
+ return true;
+}
+
+extern "C"
+{
+ FREERDP_API int TestSDLDialogs(int argc, char* argv[]);
+}
+
+int TestSDLDialogs(int argc, char* argv[])
+{
+ int rc = 0;
+
+ (void)argc;
+ (void)argv;
+
+#if 0
+ SDL_Init(SDL_INIT_VIDEO);
+ try
+ {
+#if 1
+ if (!test_input_dialog())
+ throw -1;
+#endif
+#if 1
+ if (!test_select_dialog())
+ throw -2;
+#endif
+ }
+ catch (int e)
+ {
+ rc = e;
+ }
+ SDL_Quit();
+
+#endif
+ return rc;
+}