[client,sdl] create a copy for SDL3

* Copy SDL2 client to SDL3 as the new version is not API compatible
* Move existing SDL2 client to SDL2 directory
* Move fonts from SDL client to resources folder in top level directory
This commit is contained in:
akallabeth 2024-05-07 17:29:12 +02:00
parent 45d57a8e65
commit 8281186a6d
No known key found for this signature in database
GPG Key ID: A49454A3FC909FD5
143 changed files with 12776 additions and 141 deletions

View File

@ -17,6 +17,8 @@
# Clients
include(CMakeDependentOption)
if(WITH_CLIENT_COMMON)
add_subdirectory(common)
endif()
@ -30,12 +32,24 @@ if(FREERDP_VENDOR AND WITH_CLIENT)
endif()
endif()
if(WITH_CLIENT_SDL)
cmake_dependent_option(WITH_CLIENT_SDL2 "[experimental] build experimental SDL2 client" ON WITH_CLIENT_SDL OFF)
cmake_dependent_option(WITH_CLIENT_SDL3 "[experimental] build experimental SDL3 client" OFF WITH_CLIENT_SDL OFF)
if (WITH_CLIENT_SDL2)
find_package(SDL2)
if (SDL2_FOUND)
add_subdirectory(SDL)
add_subdirectory(SDL2)
else()
message(WARNING "SDL2 requested but not found, continuing build without SDL client")
message(WARNING "SDL2 requested but not found, continuing build without SDL2 client")
endif()
endif()
if (WITH_CLIENT_SDL3)
find_package(SDL3)
if (SDL3_FOUND)
add_subdirectory(SDL3)
else()
message(WARNING "SDL3 requested but not found, continuing build without SDL3 client")
endif()
endif()

View File

@ -88,21 +88,21 @@ set(SRCS
sdl_window.cpp
)
add_library(sdl_prefs STATIC
add_library(sdl2-prefs STATIC
sdl_prefs.hpp
sdl_prefs.cpp
)
add_subdirectory(aad)
set(LIBS
list(APPEND LIBS
winpr
freerdp
freerdp-client
Threads::Threads
sdl_client_res
dialogs
aad-view
sdl_prefs
sdl2_client_res
sdl2-dialogs
sdl2-aad-view
sdl2-prefs
)
if (NOT WITH_SDL_LINK_SHARED)
@ -114,7 +114,7 @@ endif()
AddTargetWithResourceFile(${PROJECT_NAME} "${WIN32_GUI_FLAG}" "${PROJECT_VERSION}" SRCS)
target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBS})
target_link_libraries(sdl_prefs winpr)
target_link_libraries(sdl2-prefs winpr)
set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Client/SDL")
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client)

View File

@ -59,16 +59,16 @@ endif()
configure_file(sdl_config.hpp.in sdl_config.hpp @ONLY)
add_library(aad-view STATIC
add_library(sdl2-aad-view STATIC
${SRCS}
)
target_include_directories(aad-view PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(aad-view
target_include_directories(sdl2-aad-view PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(sdl2-aad-view
PRIVATE
${LIBS}
)
target_compile_definitions(
aad-view
sdl2-aad-view
PUBLIC
${DEFINITIONS}
)

View File

@ -601,9 +601,8 @@ namespace webview
m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
}
g_signal_connect(G_OBJECT(m_window), "destroy",
G_CALLBACK(+[](GtkWidget*, gpointer arg) {
static_cast<gtk_webkit_engine*>(arg)->terminate();
}),
G_CALLBACK(+[](GtkWidget*, gpointer arg)
{ static_cast<gtk_webkit_engine*>(arg)->terminate(); }),
this);
// Initialize webview widget
m_webview = webkit_web_view_new();
@ -611,12 +610,13 @@ namespace webview
webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
g_signal_connect(manager, "script-message-received::external",
G_CALLBACK(+[](WebKitUserContentManager*,
WebKitJavascriptResult* r, gpointer arg) {
auto* w = static_cast<gtk_webkit_engine*>(arg);
char* s = get_string_from_js_result(r);
w->on_message(s);
g_free(s);
}),
WebKitJavascriptResult* r, gpointer arg)
{
auto* w = static_cast<gtk_webkit_engine*>(arg);
char* s = get_string_from_js_result(r);
w->on_message(s);
g_free(s);
}),
this);
webkit_user_content_manager_register_script_message_handler(manager, "external");
init("window.external={invoke:function(s){window.webkit.messageHandlers."
@ -1039,11 +1039,13 @@ namespace webview
if (!m_parent_window)
{
class_addMethod(cls, "applicationDidFinishLaunching:"_sel,
(IMP)(+[](id self, SEL, id notification) {
auto app = objc::msg_send<id>(notification, "object"_sel);
auto w = get_associated_webview(self);
w->on_application_did_finish_launching(self, app);
}),
(IMP)(+[](id self, SEL, id notification)
{
auto app =
objc::msg_send<id>(notification, "object"_sel);
auto w = get_associated_webview(self);
w->on_application_did_finish_launching(self, app);
}),
"v@:@");
}
objc_registerClassPair(cls);
@ -1054,13 +1056,15 @@ namespace webview
auto cls = objc_allocateClassPair((Class) "NSResponder"_cls,
"WebkitScriptMessageHandler", 0);
class_addProtocol(cls, objc_getProtocol("WKScriptMessageHandler"));
class_addMethod(cls, "userContentController:didReceiveScriptMessage:"_sel,
(IMP)(+[](id self, SEL, id, id msg) {
auto w = get_associated_webview(self);
w->on_message(objc::msg_send<const char*>(
objc::msg_send<id>(msg, "body"_sel), "UTF8String"_sel));
}),
"v@:@@");
class_addMethod(
cls, "userContentController:didReceiveScriptMessage:"_sel,
(IMP)(+[](id self, SEL, id, id msg)
{
auto w = get_associated_webview(self);
w->on_message(objc::msg_send<const char*>(
objc::msg_send<id>(msg, "body"_sel), "UTF8String"_sel));
}),
"v@:@@");
objc_registerClassPair(cls);
auto instance = objc::msg_send<id>((id)cls, "new"_sel);
objc_setAssociatedObject(instance, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN);
@ -1073,38 +1077,41 @@ namespace webview
class_addMethod(
cls,
"webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:"_sel,
(IMP)(+[](id, SEL, id, id parameters, id, id completion_handler) {
auto allows_multiple_selection =
objc::msg_send<BOOL>(parameters, "allowsMultipleSelection"_sel);
auto allows_directories =
objc::msg_send<BOOL>(parameters, "allowsDirectories"_sel);
(IMP)(+[](id, SEL, id, id parameters, id, id completion_handler)
{
auto allows_multiple_selection =
objc::msg_send<BOOL>(parameters, "allowsMultipleSelection"_sel);
auto allows_directories =
objc::msg_send<BOOL>(parameters, "allowsDirectories"_sel);
// Show a panel for selecting files.
auto panel = objc::msg_send<id>("NSOpenPanel"_cls, "openPanel"_sel);
objc::msg_send<void>(panel, "setCanChooseFiles:"_sel, YES);
objc::msg_send<void>(panel, "setCanChooseDirectories:"_sel,
allows_directories);
objc::msg_send<void>(panel, "setAllowsMultipleSelection:"_sel,
allows_multiple_selection);
auto modal_response =
objc::msg_send<NSModalResponse>(panel, "runModal"_sel);
// Show a panel for selecting files.
auto panel = objc::msg_send<id>("NSOpenPanel"_cls, "openPanel"_sel);
objc::msg_send<void>(panel, "setCanChooseFiles:"_sel, YES);
objc::msg_send<void>(panel, "setCanChooseDirectories:"_sel,
allows_directories);
objc::msg_send<void>(panel, "setAllowsMultipleSelection:"_sel,
allows_multiple_selection);
auto modal_response =
objc::msg_send<NSModalResponse>(panel, "runModal"_sel);
// Get the URLs for the selected files. If the modal was canceled
// then we pass null to the completion handler to signify
// cancellation.
id urls = modal_response == NSModalResponseOK
? objc::msg_send<id>(panel, "URLs"_sel)
: nullptr;
// Get the URLs for the selected files. If the modal was canceled
// then we pass null to the completion handler to signify
// cancellation.
id urls = modal_response == NSModalResponseOK
? objc::msg_send<id>(panel, "URLs"_sel)
: nullptr;
// Invoke the completion handler block.
auto sig = objc::msg_send<id>("NSMethodSignature"_cls,
"signatureWithObjCTypes:"_sel, "v@?@");
auto invocation = objc::msg_send<id>(
"NSInvocation"_cls, "invocationWithMethodSignature:"_sel, sig);
objc::msg_send<void>(invocation, "setTarget:"_sel, completion_handler);
objc::msg_send<void>(invocation, "setArgument:atIndex:"_sel, &urls, 1);
objc::msg_send<void>(invocation, "invoke"_sel);
}),
// Invoke the completion handler block.
auto sig = objc::msg_send<id>("NSMethodSignature"_cls,
"signatureWithObjCTypes:"_sel, "v@?@");
auto invocation = objc::msg_send<id>(
"NSInvocation"_cls, "invocationWithMethodSignature:"_sel, sig);
objc::msg_send<void>(invocation, "setTarget:"_sel,
completion_handler);
objc::msg_send<void>(invocation, "setArgument:atIndex:"_sel, &urls,
1);
objc::msg_send<void>(invocation, "invoke"_sel);
}),
"v@:@@@@");
objc_registerClassPair(cls);
return objc::msg_send<id>((id)cls, "new"_sel);
@ -1115,13 +1122,14 @@ namespace webview
objc_allocateClassPair((Class) "NSObject"_cls, "WebkitNavigationDelegate", 0);
class_addProtocol(cls, objc_getProtocol("WKNavigationDelegate"));
class_addMethod(cls, "webView:didFinishNavigation:"_sel,
(IMP)(+[](id delegate, SEL sel, id webview, id navigation) {
auto w = get_associated_webview(delegate);
auto url = objc::msg_send<id>(webview, "URL"_sel);
auto nstr = objc::msg_send<id>(url, "absoluteString"_sel);
auto str = objc::msg_send<char*>(nstr, "UTF8String"_sel);
w->m_navigateCallback(str, w->m_navigateCallbackArg);
}),
(IMP)(+[](id delegate, SEL sel, id webview, id navigation)
{
auto w = get_associated_webview(delegate);
auto url = objc::msg_send<id>(webview, "URL"_sel);
auto nstr = objc::msg_send<id>(url, "absoluteString"_sel);
auto str = objc::msg_send<char*>(nstr, "UTF8String"_sel);
w->m_navigateCallback(str, w->m_navigateCallbackArg);
}),
"v@:@");
objc_registerClassPair(cls);
auto instance = objc::msg_send<id>((id)cls, "new"_sel);
@ -1360,7 +1368,8 @@ namespace webview
template <typename T>
std::array<unsigned int, 4> parse_version(const std::basic_string<T>& version) noexcept
{
auto parse_component = [](auto sb, auto se) -> unsigned int {
auto parse_component = [](auto sb, auto se) -> unsigned int
{
try
{
auto n = std::stol(std::basic_string<T>(sb, se));
@ -2256,42 +2265,44 @@ namespace webview
wc.lpszClassName = L"webview";
wc.hIcon = icon;
wc.lpfnWndProc =
(WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT {
auto w = (win32_edge_engine*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
switch (msg)
{
case WM_SIZE:
w->resize(hwnd);
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
w->terminate();
break;
case WM_GETMINMAXINFO:
{
auto lpmmi = (LPMINMAXINFO)lp;
if (w == nullptr)
{
return 0;
}
if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0)
{
lpmmi->ptMaxSize = w->m_maxsz;
lpmmi->ptMaxTrackSize = w->m_maxsz;
}
if (w->m_minsz.x > 0 && w->m_minsz.y > 0)
{
lpmmi->ptMinTrackSize = w->m_minsz;
}
}
break;
default:
return DefWindowProcW(hwnd, msg, wp, lp);
}
return 0;
});
(WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT
{
auto w =
(win32_edge_engine*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
switch (msg)
{
case WM_SIZE:
w->resize(hwnd);
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
w->terminate();
break;
case WM_GETMINMAXINFO:
{
auto lpmmi = (LPMINMAXINFO)lp;
if (w == nullptr)
{
return 0;
}
if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0)
{
lpmmi->ptMaxSize = w->m_maxsz;
lpmmi->ptMaxTrackSize = w->m_maxsz;
}
if (w->m_minsz.x > 0 && w->m_minsz.y > 0)
{
lpmmi->ptMinTrackSize = w->m_minsz;
}
}
break;
default:
return DefWindowProcW(hwnd, msg, wp, lp);
}
return 0;
});
RegisterClassExW(&wc);
m_window = CreateWindowW(L"webview", L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
CW_USEDEFAULT, 640, 480, nullptr, nullptr, hInstance,
@ -2469,7 +2480,9 @@ namespace webview
PathCombineW(userDataFolder, dataPath, currentExeName);
m_com_handler = new webview2_com_handler(
wnd, cb, [&](ICoreWebView2Controller* controller, ICoreWebView2* webview) {
wnd, cb,
[&](ICoreWebView2Controller* controller, ICoreWebView2* webview)
{
if (!controller || !webview)
{
flag.clear();
@ -2482,10 +2495,12 @@ namespace webview
flag.clear();
});
m_com_handler->set_attempt_handler([&] {
return m_webview2_loader.create_environment_with_options(
nullptr, userDataFolder, nullptr, m_com_handler);
});
m_com_handler->set_attempt_handler(
[&]
{
return m_webview2_loader.create_environment_with_options(
nullptr, userDataFolder, nullptr, m_com_handler);
});
m_com_handler->try_create_environment();
MSG msg = {};
@ -2602,8 +2617,8 @@ namespace webview
// Synchronous bind
void bind(const std::string& name, sync_binding_t fn)
{
auto wrapper = [this, fn](const std::string& seq, const std::string& req,
void* /*arg*/) { resolve(seq, 0, fn(req)); };
auto wrapper = [this, fn](const std::string& seq, const std::string& req, void* /*arg*/)
{ resolve(seq, 0, fn(req)); };
bind(name, wrapper, nullptr);
}
@ -2651,18 +2666,20 @@ namespace webview
void resolve(const std::string& seq, int status, const std::string& result)
{
dispatch([seq, status, result, this]() {
if (status == 0)
{
eval("window._rpc[" + seq + "].resolve(" + result + "); delete window._rpc[" +
seq + "]");
}
else
{
eval("window._rpc[" + seq + "].reject(" + result + "); delete window._rpc[" +
seq + "]");
}
});
dispatch(
[seq, status, result, this]()
{
if (status == 0)
{
eval("window._rpc[" + seq + "].resolve(" + result +
"); delete window._rpc[" + seq + "]");
}
else
{
eval("window._rpc[" + seq + "].reject(" + result +
"); delete window._rpc[" + seq + "]");
}
});
}
private:
@ -2755,9 +2772,8 @@ WEBVIEW_API void webview_bind(webview_t w, const char* name,
{
static_cast<webview::webview*>(w)->bind(
name,
[=](const std::string& seq, const std::string& req, void* arg) {
fn(seq.c_str(), req.c_str(), arg);
},
[=](const std::string& seq, const std::string& req, void* arg)
{ fn(seq.c_str(), req.c_str(), arg); },
arg);
}

View File

@ -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
sdl2_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(sdl2-dialogs STATIC
${SRCS}
)
target_link_libraries(sdl2-dialogs PRIVATE ${LIBS})
if(BUILD_TESTING)
# add_subdirectory(test)
endif()

View File

@ -1,5 +1,5 @@
add_executable(freerdp-res2bin
add_executable(sdl2-res2bin
convert_res_to_c.cpp
)
@ -16,7 +16,7 @@ set(RES_SVG_FILES
)
set(RES_FONT_FILES
${CMAKE_SOURCE_DIR}/client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf
${CMAKE_SOURCE_DIR}/resources/font/OpenSans-VariableFont_wdth,wght.ttf
)
macro(convert_to_bin FILE FILE_TYPE)
@ -33,9 +33,9 @@ macro(convert_to_bin FILE FILE_TYPE)
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}
COMMAND $<TARGET_FILE:sdl2-res2bin> ${FILE} ${FILE_TYPE} ${TARGET_NAME} ${FILE_BIN_DIR}
COMMENT "create image resources"
DEPENDS freerdp-res2bin
DEPENDS sdl2-res2bin
DEPENDS ${FILE}
)
endmacro()
@ -81,9 +81,9 @@ else()
)
endif()
add_library(sdl_client_res OBJECT
add_library(sdl2_client_res OBJECT
${RES_FILES}
${SRCS}
${FACTORY_SRCS}
)
set_property(TARGET sdl_client_res PROPERTY POSITION_INDEPENDENT_CODE ON)
set_property(TARGET sdl2_client_res PROPERTY POSITION_INDEPENDENT_CODE ON)

View File

@ -1,4 +1,4 @@
set(MODULE_NAME "TestSDL")
set(MODULE_NAME "TestSDL2")
set(MODULE_PREFIX "TEST_SDL")
set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.cpp)
@ -11,7 +11,7 @@ create_test_sourcelist(${MODULE_PREFIX}_SRCS
add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
set(${MODULE_PREFIX}_LIBS freerdp winpr sdl_prefs)
set(${MODULE_PREFIX}_LIBS freerdp winpr sdl2-prefs)
target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})

125
client/SDL3/CMakeLists.txt Normal file
View File

@ -0,0 +1,125 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP SDL Client
#
# Copyright 2022 Armin Novak <anovak@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.
cmake_minimum_required(VERSION 3.13)
if(POLICY CMP0091)
cmake_policy(SET CMP0091 NEW)
endif()
if (NOT FREERDP_DEFAULT_PROJECT_VERSION)
set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
endif()
project(sdl3-freerdp
LANGUAGES CXX
VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
)
message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/)
include(CommonConfigOptions)
include(ConfigureFreeRDP)
option(WITH_DEBUG_SDL_EVENTS "[dangerous, not for release builds!] Debug SDL events" ${DEFAULT_DEBUG_OPTION})
option(WITH_DEBUG_SDL_KBD_EVENTS "[dangerous, not for release builds!] Debug SDL keyboard events" ${DEFAULT_DEBUG_OPTION})
option(WITH_WIN_CONSOLE "Build ${PROJECT_NAME} with console support" ON)
option(WITH_SDL_LINK_SHARED "link SDL dynamic or static" ON)
if(WITH_WIN_CONSOLE)
set(WIN32_GUI_FLAG "TRUE")
else()
set(WIN32_GUI_FLAG "WIN32")
endif()
if (WITH_DEBUG_SDL_EVENTS)
add_definitions(-DWITH_DEBUG_SDL_EVENTS)
endif()
if (WITH_DEBUG_SDL_KBD_EVENTS)
add_definitions(-DWITH_DEBUG_SDL_KBD_EVENTS)
endif()
find_package(SDL2 REQUIRED COMPONENTS)
include_directories(${SDL2_INCLUDE_DIR})
include_directories(${SDL2_INCLUDE_DIRS})
find_package(Threads REQUIRED)
add_subdirectory(dialogs)
set(SRCS
sdl_types.hpp
sdl_utils.cpp
sdl_utils.hpp
sdl_kbd.cpp
sdl_kbd.hpp
sdl_touch.cpp
sdl_touch.hpp
sdl_pointer.cpp
sdl_pointer.hpp
sdl_disp.cpp
sdl_disp.hpp
sdl_monitor.cpp
sdl_monitor.hpp
sdl_freerdp.hpp
sdl_freerdp.cpp
sdl_channels.hpp
sdl_channels.cpp
sdl_window.hpp
sdl_window.cpp
)
add_library(sdl3-prefs STATIC
sdl_prefs.hpp
sdl_prefs.cpp
)
add_subdirectory(aad)
list(APPEND LIBS
winpr
freerdp
freerdp-client
Threads::Threads
sdl3_client_res
sdl3-dialogs
sdl3-aad-view
sdl3-prefs
)
if (NOT WITH_SDL_LINK_SHARED)
list(APPEND LIBS ${SDL2_STATIC_LIBRARIES})
else()
list(APPEND LIBS ${SDL2_LIBRARIES})
endif()
AddTargetWithResourceFile(${PROJECT_NAME} "${WIN32_GUI_FLAG}" "${PROJECT_VERSION}" SRCS)
target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBS})
target_link_libraries(sdl3-prefs winpr)
set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Client/SDL")
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client)
add_subdirectory(man)
if(BUILD_TESTING)
add_subdirectory(test)
endif()

View File

@ -0,0 +1,75 @@
set(WITH_WEBVIEW_DEFAULT OFF)
if (UNIX AND NOT APPLE)
set(WITH_WEBVIEW_DEFAULT ON)
endif()
option(WITH_WEBVIEW "Build with WebView support for AAD login popup browser" ${WITH_WEBVIEW_DEFAULT})
if (WITH_WEBVIEW)
option(WITH_WEBVIEW_QT "Build with QtWebEngine support for AAD login broweser popup" OFF)
set(SRCS
sdl_webview.hpp
webview_impl.hpp
sdl_webview.cpp
)
set(LIBS
winpr
)
if (WITH_WEBVIEW_QT)
find_package(Qt5 COMPONENTS WebEngineWidgets REQUIRED)
list(APPEND SRCS
qt/webview_impl.cpp
)
list(APPEND LIBS
Qt5::WebEngineWidgets
)
else()
list(APPEND SRCS
wrapper/webview.h
wrapper/webview_impl.cpp
)
if (WIN32)
find_package(unofficial-webview2 CONFIG REQUIRED)
list(APPEND LIBS
unofficial::webview2::webview2
)
elseif(APPLE)
find_library(WEBKIT Webkit REQUIRED)
list(APPEND LIBS
${WEBKIT}
)
else()
find_package(PkgConfig REQUIRED)
pkg_check_modules(WEBVIEW_GTK webkit2gtk-4.0 REQUIRED)
include_directories(${WEBVIEW_GTK_INCLUDE_DIRS})
list(APPEND LIBS
${WEBVIEW_GTK_LIBRARIES}
)
endif()
endif()
else()
set(SRCS
dummy.cpp
)
endif()
configure_file(sdl_config.hpp.in sdl_config.hpp @ONLY)
add_library(sdl3-aad-view STATIC
${SRCS}
)
target_include_directories(sdl3-aad-view PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(sdl3-aad-view
PRIVATE
${LIBS}
)
target_compile_definitions(
sdl3-aad-view
PUBLIC
${DEFINITIONS}
)

View File

View File

@ -0,0 +1,105 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Popup browser for AAD authentication
*
* Copyright 2023 Isaac Klein <fifthdegree@protonmail.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 <QApplication>
#include <QWebEngineView>
#include <QWebEngineProfile>
#include <QWebEngineUrlScheme>
#include <QWebEngineUrlSchemeHandler>
#include <QWebEngineUrlRequestJob>
#include <string>
#include <cstdlib>
#include <cstdarg>
#include <winpr/string.h>
#include <winpr/assert.h>
#include <freerdp/log.h>
#include <freerdp/build-config.h>
#include "../webview_impl.hpp"
#define TAG CLIENT_TAG("sdl.webview")
class SchemeHandler : public QWebEngineUrlSchemeHandler
{
public:
explicit SchemeHandler(QObject* parent = nullptr) : QWebEngineUrlSchemeHandler(parent)
{
}
void requestStarted(QWebEngineUrlRequestJob* request) override
{
QUrl url = request->requestUrl();
int rc = -1;
for (auto& param : url.query().split('&'))
{
QStringList pair = param.split('=');
if (pair.size() != 2 || pair[0] != QLatin1String("code"))
continue;
auto qc = pair[1];
m_code = qc.toStdString();
rc = 0;
break;
}
qApp->exit(rc);
}
[[nodiscard]] std::string code() const
{
return m_code;
}
private:
std::string m_code{};
};
bool webview_impl_run(const std::string& title, const std::string& url, std::string& code)
{
int argc = 1;
const auto vendor = QString::fromUtf8(FREERDP_VENDOR_STRING);
const auto product = QString::fromUtf8(FREERDP_PRODUCT_STRING);
QWebEngineUrlScheme::registerScheme(QWebEngineUrlScheme("ms-appx-web"));
std::string wtitle = title;
char* argv[] = { wtitle.data() };
QCoreApplication::setOrganizationName(vendor);
QCoreApplication::setApplicationName(product);
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
SchemeHandler handler;
QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("ms-appx-web", &handler);
QWebEngineView webview;
webview.load(QUrl(QString::fromStdString(url)));
webview.show();
if (app.exec() != 0)
return false;
auto val = handler.code();
if (val.empty())
return false;
code = val;
return !code.empty();
}

View File

@ -0,0 +1,3 @@
#pragma once
#cmakedefine WITH_WEBVIEW

View File

@ -0,0 +1,129 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Popup browser for AAD authentication
*
* Copyright 2023 Isaac Klein <fifthdegree@protonmail.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 <sstream>
#include <cstdlib>
#include <winpr/string.h>
#include <freerdp/log.h>
#include "sdl_webview.hpp"
#include "webview_impl.hpp"
#define TAG CLIENT_TAG("SDL.webview")
static BOOL sdl_webview_get_rdsaad_access_token(freerdp* instance, const char* scope,
const char* req_cnf, char** token)
{
WINPR_ASSERT(instance);
WINPR_ASSERT(scope);
WINPR_ASSERT(req_cnf);
WINPR_ASSERT(token);
WINPR_UNUSED(instance);
std::string client_id = "5177bc73-fd99-4c77-a90c-76844c9b6999";
std::string redirect_uri =
"ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2f5177bc73-fd99-4c77-a90c-76844c9b6999";
*token = nullptr;
auto url =
"https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + client_id +
"&response_type=code&scope=" + scope + "&redirect_uri=" + redirect_uri;
const std::string title = "FreeRDP WebView - AAD access token";
std::string code;
auto rc = webview_impl_run(title, url, code);
if (!rc || code.empty())
return FALSE;
auto token_request = "grant_type=authorization_code&code=" + code + "&client_id=" + client_id +
"&scope=" + scope + "&redirect_uri=" + redirect_uri +
"&req_cnf=" + req_cnf;
return client_common_get_access_token(instance, token_request.c_str(), token);
}
static BOOL sdl_webview_get_avd_access_token(freerdp* instance, char** token)
{
WINPR_ASSERT(token);
std::string client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c";
std::string redirect_uri =
"ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2fa85cf173-4192-42f8-81fa-777a763e6e2c";
std::string scope = "https%3A%2F%2Fwww.wvd.microsoft.com%2F.default";
*token = nullptr;
auto url =
"https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + client_id +
"&response_type=code&scope=" + scope + "&redirect_uri=" + redirect_uri;
const std::string title = "FreeRDP WebView - AVD access token";
std::string code;
auto rc = webview_impl_run(title, url, code);
if (!rc || code.empty())
return FALSE;
auto token_request = "grant_type=authorization_code&code=" + code + "&client_id=" + client_id +
"&scope=" + scope + "&redirect_uri=" + redirect_uri;
return client_common_get_access_token(instance, token_request.c_str(), token);
}
BOOL sdl_webview_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
size_t count, ...)
{
WINPR_ASSERT(instance);
WINPR_ASSERT(token);
switch (tokenType)
{
case ACCESS_TOKEN_TYPE_AAD:
{
if (count < 2)
{
WLog_ERR(TAG,
"ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
", aborting",
count);
return FALSE;
}
else if (count > 2)
WLog_WARN(TAG,
"ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
", ignoring",
count);
va_list ap;
va_start(ap, count);
const char* scope = va_arg(ap, const char*);
const char* req_cnf = va_arg(ap, const char*);
const BOOL rc = sdl_webview_get_rdsaad_access_token(instance, scope, req_cnf, token);
va_end(ap);
return rc;
}
case ACCESS_TOKEN_TYPE_AVD:
if (count != 0)
WLog_WARN(TAG,
"ACCESS_TOKEN_TYPE_AVD expected 0 additional arguments, but got %" PRIuz
", ignoring",
count);
return sdl_webview_get_avd_access_token(instance, token);
default:
WLog_ERR(TAG, "Unexpected value for AccessTokenType [%" PRIuz "], aborting", tokenType);
return FALSE;
}
}

View File

@ -0,0 +1,38 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Popup browser for AAD authentication
*
* Copyright 2023 Isaac Klein <fifthdegree@protonmail.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 <freerdp/freerdp.h>
#include <sdl_config.hpp>
#if defined(WITH_WEBVIEW)
#ifdef __cplusplus
extern "C"
{
#endif
BOOL sdl_webview_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
size_t count, ...);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,24 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Popup browser for AAD authentication
*
* Copyright 2023 Isaac Klein <fifthdegree@protonmail.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>
bool webview_impl_run(const std::string& title, const std::string& url, std::string& code);

View File

@ -0,0 +1 @@
upstream at https://github.com/webview/webview/

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,82 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Popup browser for AAD authentication
*
* Copyright 2023 Isaac Klein <fifthdegree@protonmail.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 "webview.h"
#include <assert.h>
#include <string>
#include <vector>
#include <map>
#include <regex>
#include <sstream>
#include "../webview_impl.hpp"
static std::vector<std::string> split(const std::string& input, const std::string& regex)
{
// passing -1 as the submatch index parameter performs splitting
std::regex re(regex);
std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 };
std::sregex_token_iterator last;
return { first, last };
}
static std::map<std::string, std::string> urlsplit(const std::string& url)
{
auto pos = url.find("?");
if (pos == std::string::npos)
return {};
auto surl = url.substr(pos);
auto args = split(surl, "&");
std::map<std::string, std::string> argmap;
for (const auto& arg : args)
{
auto kv = split(arg, "=");
if (kv.size() == 2)
argmap.insert({ kv[0], kv[1] });
}
return argmap;
}
static void fkt(const std::string& url, void* arg)
{
auto args = urlsplit(url);
auto val = args.find("code");
if (val == args.end())
return;
assert(arg);
auto rcode = static_cast<std::string*>(arg);
*rcode = val->second;
}
bool webview_impl_run(const std::string& title, const std::string& url, std::string& code)
{
webview::webview w(false, nullptr);
w.set_title(title);
w.set_size(640, 480, WEBVIEW_HINT_NONE);
std::string scheme;
w.add_scheme_handler("ms-appx-web", fkt, &scheme);
w.add_navigate_listener(fkt, &code);
w.navigate(url);
w.run();
return !code.empty();
}

View File

@ -20,7 +20,7 @@ set(SRCS
)
list(APPEND LIBS
sdl_client_res
sdl3_client_res
winpr
)
@ -64,11 +64,11 @@ endif()
add_subdirectory(res)
add_library(dialogs STATIC
add_library(sdl3-dialogs STATIC
${SRCS}
)
target_link_libraries(dialogs PRIVATE ${LIBS})
target_link_libraries(sdl3-dialogs PRIVATE ${LIBS})
if(BUILD_TESTING)
# add_subdirectory(test)

View File

@ -0,0 +1,89 @@
add_executable(sdl3-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}/resources/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:sdl3-res2bin> ${FILE} ${FILE_TYPE} ${TARGET_NAME} ${FILE_BIN_DIR}
COMMENT "create image resources"
DEPENDS sdl3-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(sdl3_client_res OBJECT
${RES_FILES}
${SRCS}
${FACTORY_SRCS}
)
set_property(TARGET sdl3_client_res PROPERTY POSITION_INDEPENDENT_CODE ON)

View File

@ -0,0 +1,190 @@
/**
* 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;
std::ios backup(nullptr);
backup.copyfmt(out);
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;
}
out.copyfmt(backup);
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());
}

View File

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

View File

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

View File

@ -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.u8string().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;
}

View File

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

View File

@ -0,0 +1,71 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client Channels
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
* Copyright 2023 Thincast Technologies GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cassert>
#include "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;
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,541 @@
/**
* 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
{
if (!_window || !_renderer)
return false;
auto flags = SDL_GetWindowFlags(_window);
return (flags & (SDL_WINDOW_HIDDEN | SDL_WINDOW_MINIMIZED)) == 0;
}
bool SDLConnectionDialog::setTitle(const char* fmt, ...)
{
std::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)
{
std::lock_guard lock(_mux);
if (!renderer)
return false;
if (!clearWindow(renderer))
return false;
for (auto& btn : _list)
{
if (!btn.widget.update_text(renderer, _msg, btn.fgcolor, btn.bgcolor))
return false;
}
if (!_buttons.update(renderer))
return false;
SDL_RenderPresent(renderer);
return true;
}
bool SDLConnectionDialog::wait(bool ignoreRdpContext)
{
while (running())
{
if (!ignoreRdpContext)
{
if (freerdp_shall_disconnect_context(_context))
return false;
}
std::this_thread::yield();
}
return true;
}
bool SDLConnectionDialog::handle(const SDL_Event& event)
{
Uint32 windowID = 0;
if (_window)
{
windowID = SDL_GetWindowID(_window);
}
switch (event.type)
{
case SDL_USEREVENT_RETRY_DIALOG:
return update();
case SDL_QUIT:
resetTimer();
destroyWindow();
return false;
case SDL_KEYDOWN:
case SDL_KEYUP:
if (visible())
{
auto& ev = reinterpret_cast<const SDL_KeyboardEvent&>(event);
update(_renderer);
switch (event.key.keysym.sym)
{
case SDLK_RETURN:
case SDLK_RETURN2:
case SDLK_ESCAPE:
case SDLK_KP_ENTER:
if (event.type == SDL_KEYUP)
{
freerdp_abort_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();
}

View File

@ -0,0 +1,129 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#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 = nullptr;
SDL_Window* _window = nullptr;
SDL_Renderer* _renderer = nullptr;
mutable std::mutex _mux;
std::string _title;
std::string _msg;
MsgType _type = MSG_NONE;
MsgType _type_active = MSG_NONE;
SDL_TimerID _timer = -1;
bool _running = false;
std::vector<widget_cfg_t> _list;
SdlButtonList _buttons;
};
class SDLConnectionDialogHider
{
public:
explicit SDLConnectionDialogHider(freerdp* instance);
explicit SDLConnectionDialogHider(rdpContext* context);
explicit SDLConnectionDialogHider(SDLConnectionDialog* dialog);
~SDLConnectionDialogHider();
private:
SDLConnectionDialog* get(freerdp* instance);
SDLConnectionDialog* get(rdpContext* context);
private:
SDLConnectionDialog* _dialog = nullptr;
bool _visible = false;
};

View File

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

View File

@ -0,0 +1,53 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <winpr/wtypes.h>
#include <freerdp/freerdp.h>
#include "../sdl_types.hpp"
#include "../sdl_utils.hpp"
BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
rdp_auth_reason reason);
BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
DWORD* choice, BOOL gateway);
SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current, void* userarg);
DWORD sdl_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
const char* common_name, const char* subject, const char* issuer,
const char* fingerprint, DWORD flags);
DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
const char* common_name, const char* subject,
const char* issuer, const char* new_fingerprint,
const char* old_subject, const char* old_issuer,
const char* old_fingerprint, DWORD flags);
int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type);
BOOL sdl_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
BOOL isConsentMandatory, size_t length, const WCHAR* message);
BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags);
BOOL sdl_cert_dialog_show(const char* title, const char* message);
BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list);
BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args);

View File

@ -0,0 +1,177 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "sdl_input.hpp"
#include <cassert>
#include <string>
#include <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);
}

View File

@ -0,0 +1,73 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Client helper dialogs
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <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;
};

Some files were not shown because too many files have changed in this diff Show More