Milestone 5: deliver embedded RDP sessions and lifecycle hardening

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

View File

@@ -0,0 +1,30 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP SDL Client
#
# Copyright 2024 Armin Novak <anovak@thincast.com>
# Copyright 2024 Thincast Technologies GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
add_subdirectory(aad)
add_subdirectory(res)
add_library(
sdl-common-prefs STATIC sdl_prefs.hpp sdl_prefs.cpp scoped_guard.hpp sdl_common_utils.hpp sdl_common_utils.cpp
)
target_link_libraries(sdl-common-prefs winpr freerdp)
set_property(TARGET sdl-common-prefs PROPERTY FOLDER "Client/Common")
if(BUILD_TESTING_INTERNAL OR BUILD_TESTING)
add_subdirectory(test)
endif()

View File

@@ -0,0 +1,55 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP SDL Client
#
# Copyright 2024 Armin Novak <anovak@thincast.com>
# Copyright 2024 Thincast Technologies GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
option(WITH_WEBVIEW "Build with WebView support for AAD login popup browser" OFF)
if(WITH_WEBVIEW)
set(SRCS sdl_webview.hpp webview_impl.hpp sdl_webview.cpp)
set(LIBS winpr)
include(FetchContent)
set(FETCHCONTENT_SOURCE_DIR_WEBVIEW "${CMAKE_CURRENT_SOURCE_DIR}/../../../../external/webview")
if(IS_DIRECTORY "${FETCHCONTENT_SOURCE_DIR_WEBVIEW}")
message("Using existing source from ${FETCHCONTENT_SOURCE_DIR_WEBVIEW}")
else()
unset(FETCHCONTENT_SOURCE_DIR_WEBVIEW)
endif()
set(WEBVIEW_BUILD_DOCS OFF CACHE INTERNAL "fetchcontent default")
set(WEBVIEW_BUILD_SHARED_LIBRARY OFF CACHE INTERNAL "fetchcontent default")
set(WEBVIEW_BUILD_STATIC_LIBRARY ON CACHE INTERNAL "fetchcontent default")
set(WEBVIEW_BUILD_TESTS OFF CACHE INTERNAL "fetchcontent default")
set(WEBVIEW_BUILD_EXAMPLES OFF CACHE INTERNAL "fetchcontent default")
FetchContent_Declare(webview GIT_REPOSITORY https://github.com/akallabeth/webview GIT_TAG navigation-listener SYSTEM)
FetchContent_MakeAvailable(webview)
list(APPEND SRCS webview_impl.cpp)
list(APPEND LIBS webview::core)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
add_library(sdl-common-aad-view STATIC ${SRCS})
set_property(TARGET sdl-common-aad-view PROPERTY FOLDER "Client/SDL/Common")
target_include_directories(sdl-common-aad-view PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(sdl-common-aad-view PRIVATE ${LIBS})
target_compile_definitions(sdl-common-aad-view PUBLIC ${DEFINITIONS})
else()
add_library(sdl-common-aad-view STATIC dummy.cpp)
endif()

View File

View File

@@ -0,0 +1,155 @@
/**
* 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 <memory>
#include <winpr/string.h>
#include <freerdp/log.h>
#include <freerdp/utils/aad.h>
#include "sdl_webview.hpp"
#include "webview_impl.hpp"
#define TAG CLIENT_TAG("SDL.webview")
static std::string from_settings(const rdpSettings* settings, FreeRDP_Settings_Keys_String id)
{
auto val = freerdp_settings_get_string(settings, id);
if (!val)
{
WLog_WARN(TAG, "Settings key %s is nullptr", freerdp_settings_get_name_for_key(id));
return "";
}
return val;
}
static std::string from_aad_wellknown(rdpContext* context, AAD_WELLKNOWN_VALUES which)
{
auto val = freerdp_utils_aad_get_wellknown_string(context, which);
if (!val)
{
WLog_WARN(TAG, "[wellknown] key %s is nullptr",
freerdp_utils_aad_wellknwon_value_name(which));
return "";
}
return val;
}
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);
auto context = instance->context;
WINPR_UNUSED(context);
auto settings = context->settings;
WINPR_ASSERT(settings);
std::shared_ptr<char> request(freerdp_client_get_aad_url((rdpClientContext*)instance->context,
FREERDP_CLIENT_AAD_AUTH_REQUEST,
scope),
free);
const std::string title = "FreeRDP WebView - AAD access token";
std::string code;
auto rc = webview_impl_run(title, request.get(), code);
if (!rc || code.empty())
return FALSE;
std::shared_ptr<char> token_request(
freerdp_client_get_aad_url((rdpClientContext*)instance->context,
FREERDP_CLIENT_AAD_TOKEN_REQUEST, scope, code.c_str(), req_cnf),
free);
return client_common_get_access_token(instance, token_request.get(), token);
}
static BOOL sdl_webview_get_avd_access_token(freerdp* instance, char** token)
{
WINPR_ASSERT(token);
WINPR_ASSERT(instance);
WINPR_ASSERT(instance->context);
std::shared_ptr<char> request(freerdp_client_get_aad_url((rdpClientContext*)instance->context,
FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST),
free);
const std::string title = "FreeRDP WebView - AVD access token";
std::string code;
auto rc = webview_impl_run(title, request.get(), code);
if (!rc || code.empty())
return FALSE;
std::shared_ptr<char> token_request(
freerdp_client_get_aad_url((rdpClientContext*)instance->context,
FREERDP_CLIENT_AAD_AVD_TOKEN_REQUEST, code.c_str()),
free);
return client_common_get_access_token(instance, token_request.get(), 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,34 @@
/**
* 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>
#ifdef __cplusplus
extern "C"
{
#endif
[[nodiscard]] BOOL sdl_webview_get_access_token(freerdp* instance, AccessTokenType tokenType,
char** token, size_t count, ...);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,179 @@
/**
* 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 "webview_impl.hpp"
#include <algorithm>
#include <cassert>
#include <cctype>
#include <map>
#include <regex>
#include <sstream>
#include <string>
#include <vector>
#include <freerdp/log.h>
#include <winpr/string.h>
#define TAG FREERDP_TAG("client.SDL.common.aad")
class fkt_arg
{
public:
fkt_arg(const std::string& url)
{
auto args = urlsplit(url);
auto redir = args.find("redirect_uri");
if (redir == args.end())
{
WLog_ERR(TAG,
"[Webview] url %s does not contain a redirect_uri parameter, "
"aborting.",
url.c_str());
}
else
{
_redirect_uri = from_url_encoded_str(redir->second);
}
}
bool valid() const
{
return !_redirect_uri.empty();
}
bool getCode(std::string& c) const
{
c = _code;
return !c.empty();
}
bool handle(const std::string& uri) const
{
std::string duri = from_url_encoded_str(uri);
if (duri.length() < _redirect_uri.length())
return false;
auto rc = _strnicmp(duri.c_str(), _redirect_uri.c_str(), _redirect_uri.length());
return rc == 0;
}
bool parse(const std::string& uri)
{
_args = urlsplit(uri);
auto err = _args.find("error");
if (err != _args.end())
{
auto suberr = _args.find("error_subcode");
WLog_ERR(TAG, "[Webview] %s: %s, %s: %s", err->first.c_str(), err->second.c_str(),
suberr->first.c_str(), suberr->second.c_str());
return false;
}
auto val = _args.find("code");
if (val == _args.end())
{
WLog_ERR(TAG, "[Webview] no code parameter detected in redirect URI %s", uri.c_str());
return false;
}
_code = val->second;
return true;
}
protected:
static std::string from_url_encoded_str(const std::string& str)
{
std::string cxxstr;
auto cstr = winpr_str_url_decode(str.c_str(), str.length());
if (cstr)
{
cxxstr = std::string(cstr);
free(cstr);
}
return cxxstr;
}
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 {};
pos++; // skip '?'
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;
}
private:
std::string _redirect_uri;
std::string _code;
std::map<std::string, std::string> _args;
};
static void fkt(webview_t webview, const char* uri, webview_navigation_event_t type, void* arg)
{
assert(arg);
auto rcode = static_cast<fkt_arg*>(arg);
if (type != WEBVIEW_LOAD_FINISHED)
return;
if (!rcode->handle(uri))
return;
(void)rcode->parse(uri);
webview_terminate(webview);
}
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(800, 600, WEBVIEW_HINT_NONE);
fkt_arg arg(url);
if (!arg.valid())
{
return false;
}
w.add_navigation_listener(fkt, &arg);
w.navigate(url);
w.run();
return arg.getCode(code);
}

View File

@@ -0,0 +1,25 @@
/**
* 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>
[[nodiscard]] bool webview_impl_run(const std::string& title, const std::string& url,
std::string& code);

View File

@@ -0,0 +1,130 @@
.SH "CONFIGURATION FILE"
.PP
Format and Location:
.RS 4
The configuration file is stored per user\&.
.br
The
\fIXDG_CONFIG_HOME\fR
environment variable can be used to override the base directory\&.
.br
This defaults to
\fI~/\&.config\fR
The location relative to
\fIXDG_CONFIG_HOME\fR
is
\fI$XDG_CONFIG_HOME/@VENDOR_PRODUCT@/sdl\-freerdp\&.json\fR
.br
The configuration is stored in JSON format
.RE
.PP
Supported options:
.RS 4
.PP
\fISDL_KeyModMask\fR
.RS 4
.PP
.RS 4
Defines the key combination required for SDL client shortcuts\&.
.br
Default
\fIKMOD_RSHIFT\fR
.br
An array of
\fISDL_Keymod\fR
strings as defined at
\fI@SDL_WIKI_BASE_URL@/SDL_Keymod\fR
.br
use \fRKEYMOD_NONE\fR to disable hotkeys
.RE
.RE
.PP
\fISDL_Fullscreen\fR
.RS 4
.PP
.RS 4
Toggles client fullscreen state\&.
.br
Default
\fISDL_SCANCODE_RETURN\fR\&.
.br
A string as defined at
\fI@SDL_WIKI_BASE_URL@/SDL_Scancode\fR
.RE
.RE
.PP
\fISDL_Minimize\fR
.RS 4
.PP
.RS 4
Minimizes the client window
.br
Default
\fISDL_SCANCODE_M\fR\&.
.br
A string as defined at
\fI@SDL_WIKI_BASE_URL@/SDL_Scancode\fR
.RE
.RE
.PP
\fISDL_Resizeable\fR
.RS 4
.PP
.RS 4
Toggles local window resizeable state\&.
.br
Default
\fISDL_SCANCODE_R\fR\&.
.br
A string as defined at
\fI@SDL_WIKI_BASE_URL@/SDL_Scancode\fR
.RE
.RE
.PP
\fISDL_Grab\fR
.RS 4
.PP
.RS 4
Toggles keyboard and mouse grab state.
.br
If keyboard is grabbed the local system shortcuts do no longer work and are sent to the remote system.
.br
If the Mouse is grabbed (optional) local gesture detection does not work and the mouse might not be able to leave the RDP window. Mouse events are not altered.\&.
.br
Default
\fISDL_SCANCODE_G\fR\&.
.br
A string as defined at
\fI@SDL_WIKI_BASE_URL@/SDL_Scancode\fR
.RE
.RE
.PP
\fISDL_Disconnect\fR
.RS 4
.PP
.RS 4
Disconnects from the RDP session\&.
.br
Default
\fISDL_SCANCODE_D\fR\&.
.br
A string as defined at
\fI@SDL_WIKI_BASE_URL@/SDL_Scancode\fR
.RE
.RE
.RE

View File

@@ -0,0 +1,116 @@
.SH "EXAMPLES"
.PP
\fB@MANPAGE_NAME@ connection\&.rdp /p:Pwd123! /f\fR
.RS 4
Connect in fullscreen mode using a stored configuration
\fIconnection\&.rdp\fR
and the password
\fIPwd123!\fR
.RE
.PP
\fB@MANPAGE_NAME@ /u:USER /size:50%h /v:rdp\&.contoso\&.com\fR
.RS 4
Connect to host
\fIrdp\&.contoso\&.com\fR
with user
\fIUSER\fR
and a size of
\fI50 percent of the height\fR\&. If width (w) is set instead of height (h) like /size:50%w\&. 50 percent of the width is used\&.
.RE
.PP
\fB@MANPAGE_NAME@ /u:CONTOSO\e\eJohnDoe /p:Pwd123! /v:rdp\&.contoso\&.com\fR
.RS 4
Connect to host
\fIrdp\&.contoso\&.com\fR
with user
\fICONTOSO\e\eJohnDoe\fR
and password
\fIPwd123!\fR
.RE
.PP
\fB@MANPAGE_NAME@ /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192\&.168\&.1\&.100:4489\fR
.RS 4
Connect to host
\fI192\&.168\&.1\&.100\fR
on port
\fI4489\fR
with user
\fIJohnDoe\fR, password
\fIPwd123!\fR\&. The screen width is set to
\fI1366\fR
and the height to
\fI768\fR
.RE
.PP
\fB@MANPAGE_NAME@ /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E\-95D2\-46C6\-9A18\-23A5BB403532 /v:192\&.168\&.1\&.100\fR
.RS 4
Establish a connection to host
\fI192\&.168\&.1\&.100\fR
with user
\fIJohnDoe\fR, password
\fIPwd123!\fR
and connect to Hyper\-V console (use port 2179, disable negotiation) with VMID
\fIC824F53E\-95D2\-46C6\-9A18\-23A5BB403532\fR
.RE
.PP
\fB+clipboard\fR
.RS 4
Activate clipboard redirection
.RE
.PP
\fB/drive:home,/home/user\fR
.RS 4
Activate drive redirection of
\fI/home/user\fR
as home drive
.RE
.PP
\fB/smartcard:<device>\fR
.RS 4
Activate smartcard redirection for device
\fIdevice\fR
.RE
.PP
\fB/printer:<device>,<driver>\fR
.RS 4
Activate printer redirection for printer
\fIdevice\fR
using driver
\fIdriver\fR
.RE
.PP
\fB/serial:<device>\fR
.RS 4
Activate serial port redirection for port
\fIdevice\fR
.RE
.PP
\fB/parallel:<device>\fR
.RS 4
Activate parallel port redirection for port
\fIdevice\fR
.RE
.PP
\fB/sound:sys:alsa\fR
.RS 4
Activate audio output redirection using device
\fIsys:alsa\fR
.RE
.PP
\fB/microphone:sys:alsa\fR
.RS 4
Activate audio input redirection using device
\fIsys:alsa\fR
.RE
.PP
\fB/multimedia:sys:alsa\fR
.RS 4
Activate multimedia redirection using device
\fIsys:alsa\fR
.RE
.PP
\fB/usb:id,dev:054c:0268\fR
.RS 4
Activate USB device redirection for the device identified by
\fI054c:0268\fR
.RE

View File

@@ -0,0 +1,24 @@
.SH "GLOBAL CONFIGURATION (SDL3 client)"
.PP
The SDL3 client configuration location is
\fI@SYSCONF_DIR@/sdl-freerdp\&.json\fR
.br
File format is JSON
.RE
.PP
Supported options:
.RS 4
.PP
\fIisUserConfigEnabled\fR
.RS 4
.PP
.RS 4
\fIJSON boolean\fR
.br
Allow or block per user configuration file support.
.RE
.RE
.PP
All user level options from \fBCONFIGURATION FILE\fR can be set here too

View File

@@ -0,0 +1,108 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP SDL Client
#
# Copyright 2024 Armin Novak <anovak@thincast.com>
# Copyright 2024 Thincast Technologies GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set(FACTORY_SRCS "")
set(FACTORY_HDR "")
set(FACTORY_CLASSES "")
include(ConvertFileToHexArray)
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(FILENAME ${FILE_NAME})
set(CLASSNAME ${TARGET_NAME})
set(CLASSTYPE ${FILE_TYPE})
file_to_hex_array("${FILE}" FILEDATA)
cleaning_configure_file(resource.hpp.in ${FILE_BIN_DIR}/${TARGET_NAME}.hpp @ONLY)
cleaning_configure_file(resource.cpp.in ${FILE_BIN_DIR}/${TARGET_NAME}.cpp @ONLY)
list(APPEND FACTORY_HDR ${FILE_BIN_DIR}/${TARGET_NAME}.hpp)
list(APPEND FACTORY_SRCS ${FILE_BIN_DIR}/${TARGET_NAME}.cpp)
list(APPEND FACTORY_CLASSES ${TARGET_NAME})
endmacro()
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)
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_compile_definitions(SDL_USE_COMPILED_RESOURCES)
set(FINIT ${CMAKE_CURRENT_BINARY_DIR}/resource-init.cpp)
list(APPEND FACTORY_SRCS ${FINIT})
write_file(${FINIT} "#include <sdl_resource_manager.hpp>")
foreach(HDR ${FACTORY_HDR})
write_file(${FINIT} "#include <${HDR}>" APPEND)
endforeach()
write_file(${FINIT} "void SDLResourceManager::init() {" APPEND)
foreach(CLASS ${FACTORY_CLASSES})
write_file(${FINIT} "\t${CLASS}::init();" APPEND)
endforeach()
write_file(${FINIT} "}" APPEND)
else()
option(SDL_USE_VENDOR_PRODUCT_CONFIG_DIR "Use <vendor>/<product> path for resources" OFF)
set(SDL_RESOURCE_ROOT ${CMAKE_INSTALL_FULL_DATAROOTDIR})
if(SDL_USE_VENDOR_PRODUCT_CONFIG_DIR)
string(APPEND SDL_RESOURCE_ROOT "/${VENDOR}")
endif()
string(APPEND SDL_RESOURCE_ROOT "/${PRODUCT}")
if(WITH_BINARY_VERSIONING)
string(APPEND SDL_RESOURCE_ROOT "${FREERDP_VERSION_MAJOR}")
endif()
add_compile_definitions(SDL_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-common-client-res STATIC ${RES_FILES} ${SRCS} ${FACTORY_HDR} ${FACTORY_SRCS})
set_property(TARGET sdl-common-client-res PROPERTY POSITION_INDEPENDENT_CODE ON)
set_property(TARGET sdl-common-client-res PROPERTY FOLDER "Client/Common")

View File

@@ -0,0 +1,28 @@
/* AUTOGENERATED file, do not edit
*
* part of @PROJECT_NAME@
* generated by 'CMake' from common/res/resource.cpp.in
*
* contains the converted file '@FILENAME@'
*/
#include <vector>
#include "@CLASSNAME@.hpp"
std::string @CLASSNAME@::name() noexcept {
return "@FILENAME@";
}
std::string @CLASSNAME@::type() noexcept {
return "@CLASSTYPE@";
}
// NOLINTNEXTLINE(clang-diagnostic-global-constructors)
const SDLResourceFile @CLASSNAME@::_initializer(type(), name(), init());
std::vector<unsigned char> @CLASSNAME@::init() noexcept {
static const unsigned char data[] = {
@FILEDATA@
};
return std::vector<unsigned char>(data, data + sizeof(data));
}

View File

@@ -0,0 +1,34 @@
/* AUTOGENERATED file, do not edit
*
* part of @PROJECT_NAME@
* generated by 'CMake' from common/res/resource.hpp.in
*
* contains the converted file '@FILENAME@'
*/
#pragma once
#include <vector>
#include <string>
#include "sdl_resource_file.hpp"
class @CLASSNAME@
{
friend class SDLResourceManager;
public:
@CLASSNAME@() = delete;
~@CLASSNAME@() = delete;
@CLASSNAME@(const @CLASSNAME@&) = delete;
@CLASSNAME@(@CLASSNAME@&&) = delete;
@CLASSNAME@& operator=(const @CLASSNAME@&) = delete;
@CLASSNAME@& operator=(@CLASSNAME@&&) = delete;
static std::string name() noexcept;
static std::string type() noexcept;
protected:
static std::vector<unsigned char> init() noexcept;
static const SDLResourceFile _initializer;
};

View File

@@ -0,0 +1,27 @@
/**
* 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) noexcept
{
SDLResourceManager::insert(type, id, data);
}
SDLResourceFile::~SDLResourceFile() = default;

View File

@@ -0,0 +1,34 @@
/**
* 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) noexcept;
virtual ~SDLResourceFile();
SDLResourceFile(const SDLResourceFile& other) = delete;
SDLResourceFile(SDLResourceFile&& other) = delete;
SDLResourceFile& operator=(const SDLResourceFile& other) = delete;
SDLResourceFile& operator=(SDLResourceFile&& other) = delete;
};

View File

@@ -0,0 +1,102 @@
/**
* 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
std::string SDLResourceManager::typeFonts()
{
return "fonts";
}
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);
}
bool SDLResourceManager::useCompiledResources()
{
#if defined(SDL_USE_COMPILED_RESOURCES)
return true;
#else
return false;
#endif
}
const std::vector<unsigned char>* SDLResourceManager::data(const std::string& type,
const std::string& id)
{
#if defined(SDL_USE_COMPILED_RESOURCES)
std::string uuid = type + "/" + id;
auto val = resources().find(uuid);
if (val == resources().end())
return nullptr;
return &val->second;
#else
return nullptr;
#endif
}
std::string SDLResourceManager::filename([[maybe_unused]] const std::string& type,
[[maybe_unused]] const std::string& id)
{
#if defined(SDL_RESOURCE_ROOT)
std::string uuid = type + "/" + id;
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 "";
}
return path.u8string();
#else
return "";
#endif
}
std::map<std::string, std::vector<unsigned char>>& SDLResourceManager::resources()
{
static std::map<std::string, std::vector<unsigned char>> resources = {};
#if defined(SDL_USE_COMPILED_RESOURCES)
if (resources.empty())
init();
#endif
return resources;
}

View File

@@ -0,0 +1,54 @@
/**
* 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>
class SDLResourceManager
{
friend class SDLResourceFile;
public:
SDLResourceManager() = delete;
SDLResourceManager(const SDLResourceManager& other) = delete;
SDLResourceManager(const SDLResourceManager&& other) = delete;
~SDLResourceManager() = delete;
SDLResourceManager operator=(const SDLResourceManager& other) = delete;
SDLResourceManager& operator=(SDLResourceManager&& other) = delete;
[[nodiscard]] static std::string typeFonts();
[[nodiscard]] static std::string typeImages();
protected:
static void insert(const std::string& type, const std::string& id,
const std::vector<unsigned char>& data);
[[nodiscard]] static const std::vector<unsigned char>* data(const std::string& type,
const std::string& id);
[[nodiscard]] static std::string filename(const std::string& type, const std::string& id);
[[nodiscard]] static bool useCompiledResources();
private:
[[nodiscard]] static std::map<std::string, std::vector<unsigned char>>& resources();
#if defined(SDL_USE_COMPILED_RESOURCES)
static void init(); // implemented in generated file
#endif
};

View File

@@ -0,0 +1,56 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL ScopeGuard
*
* Copyright 2024 Armin Novak <armin.novak@thincast.com>
* Copyright 2024 Thincast Technologies GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <functional>
class ScopeGuard
{
public:
template <class Callable> explicit ScopeGuard(Callable&& cleanupFunction)
try : f(std::forward<Callable>(cleanupFunction))
{
}
catch (...)
{
cleanupFunction();
throw;
}
~ScopeGuard()
{
if (f)
f();
}
void dismiss() noexcept
{
f = nullptr;
}
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&& other) noexcept = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&&) = delete;
private:
std::function<void()> f;
};

View File

@@ -0,0 +1,103 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL common utilitis
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 Thincast Technologies GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "sdl_common_utils.hpp"
CriticalSection::CriticalSection()
{
InitializeCriticalSection(&_section);
}
CriticalSection::~CriticalSection()
{
DeleteCriticalSection(&_section);
}
void CriticalSection::lock()
{
EnterCriticalSection(&_section);
}
void CriticalSection::unlock()
{
LeaveCriticalSection(&_section);
}
WinPREvent::WinPREvent(bool initial) : _handle(CreateEventA(nullptr, TRUE, initial, nullptr))
{
}
WinPREvent::~WinPREvent()
{
(void)CloseHandle(_handle);
}
void WinPREvent::set()
{
(void)SetEvent(_handle);
}
void WinPREvent::clear()
{
(void)ResetEvent(_handle);
}
bool WinPREvent::isSet() const
{
return WaitForSingleObject(_handle, 0) == WAIT_OBJECT_0;
}
HANDLE WinPREvent::handle() const
{
return _handle;
}
bool operator==(const rdpMonitor& l, const rdpMonitor& r)
{
if (l.x != r.x)
return false;
if (l.y != r.y)
return false;
if (l.width != r.width)
return false;
if (l.height != r.height)
return false;
if (l.is_primary != r.is_primary)
return false;
if (l.orig_screen != r.orig_screen)
return false;
return l.attributes == r.attributes;
}
bool operator==(const MONITOR_ATTRIBUTES& l, const MONITOR_ATTRIBUTES& r)
{
if (l.physicalWidth != r.physicalWidth)
return false;
if (l.physicalHeight != r.physicalHeight)
return false;
if (l.orientation != r.orientation)
return false;
if (l.desktopScaleFactor != r.desktopScaleFactor)
return false;
if (l.deviceScaleFactor != r.deviceScaleFactor)
return false;
return true;
}

View File

@@ -0,0 +1,69 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL common utilitis
*
* Copyright 2025 Armin Novak <armin.novak@thincast.com>
* Copyright 2025 Thincast Technologies GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <memory>
#include <freerdp/settings_types.h>
using CStringPtr = std::unique_ptr<char, decltype(std::free)*>;
bool operator==(const MONITOR_ATTRIBUTES& l, const MONITOR_ATTRIBUTES& r);
bool operator==(const rdpMonitor& l, const rdpMonitor& r);
class WinPREvent
{
public:
explicit WinPREvent(bool initial = false);
WinPREvent(const WinPREvent& other) = delete;
WinPREvent(WinPREvent&& other) = delete;
WinPREvent& operator=(const WinPREvent& other) = delete;
WinPREvent& operator=(WinPREvent&& other) = delete;
~WinPREvent();
void set();
void clear();
[[nodiscard]] bool isSet() const;
[[nodiscard]] HANDLE handle() const;
private:
HANDLE _handle;
};
class CriticalSection
{
public:
CriticalSection();
CriticalSection(const CriticalSection& other) = delete;
CriticalSection(CriticalSection&& other) = delete;
~CriticalSection();
CriticalSection& operator=(const CriticalSection& other) = delete;
CriticalSection& operator=(const CriticalSection&& other) = delete;
void lock();
void unlock();
private:
CRITICAL_SECTION _section{};
};

View File

@@ -0,0 +1,243 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Prefs
*
* Copyright 2022 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 <iostream>
#include <fstream>
#if __has_include(<filesystem>)
#include <filesystem>
#include <utility>
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
#include "sdl_prefs.hpp"
#include "sdl_common_utils.hpp"
#include <winpr/path.h>
#include <winpr/config.h>
#include <winpr/json.h>
#include <freerdp/version.h>
#include <freerdp/settings.h>
#include <freerdp/utils/helpers.h>
SdlPref::WINPR_JSONPtr SdlPref::get(bool systemConfigOnly) const
{
auto config = get_pref_file(systemConfigOnly);
return { WINPR_JSON_ParseFromFile(config.c_str()), WINPR_JSON_Delete };
}
WINPR_JSON* SdlPref::get_item(const std::string& key, bool systemConfigOnly) const
{
/* If we request a system setting or user settings are disabled */
if (systemConfigOnly || !is_user_config_enabled())
return get_item(_system_config, key);
/* Get the user setting */
auto res = get_item(_config, key);
/* User setting does not exist, fall back to system setting */
if (!res)
res = get_item(_system_config, key);
return res;
}
WINPR_JSON* SdlPref::get_item(const WINPR_JSONPtr& config, const std::string& key) const
{
if (!config)
return nullptr;
return WINPR_JSON_GetObjectItemCaseSensitive(config.get(), key.c_str());
}
bool SdlPref::get_bool(const WINPR_JSONPtr& config, const std::string& key, bool fallback) const
{
auto item = get_item(config, key);
if (!item || !WINPR_JSON_IsBool(item))
return fallback;
return WINPR_JSON_IsTrue(item);
}
bool SdlPref::is_user_config_enabled() const
{
auto& config = _system_config;
if (!config)
return true;
return get_bool(config, "isUserConfigEnabled", true);
}
std::string SdlPref::item_to_str(WINPR_JSON* item, const std::string& fallback)
{
if (!item || !WINPR_JSON_IsString(item))
return fallback;
auto str = WINPR_JSON_GetStringValue(item);
if (!str)
return {};
return str;
}
std::string SdlPref::get_string(const std::string& key, const std::string& fallback,
bool systemConfigOnly) const
{
auto item = get_item(key, systemConfigOnly);
return item_to_str(item, fallback);
}
bool SdlPref::get_bool(const std::string& key, bool fallback, bool systemConfigOnly) const
{
auto& config = systemConfigOnly ? _system_config : _config;
return get_bool(config, key, fallback);
}
int64_t SdlPref::get_int(const std::string& key, int64_t fallback, bool systemConfigOnly) const
{
auto item = get_item(key, systemConfigOnly);
if (!item || !WINPR_JSON_IsNumber(item))
return fallback;
auto val = WINPR_JSON_GetNumberValue(item);
return static_cast<int64_t>(val);
}
std::vector<std::string> SdlPref::get_array(const std::string& key,
const std::vector<std::string>& fallback,
bool systemConfigOnly) const
{
auto item = get_item(key, systemConfigOnly);
if (!item || !WINPR_JSON_IsArray(item))
return fallback;
std::vector<std::string> values;
for (size_t x = 0; x < WINPR_JSON_GetArraySize(item); x++)
{
auto cur = WINPR_JSON_GetArrayItem(item, x);
values.push_back(item_to_str(cur));
}
return values;
}
void SdlPref::print_config_file_help(int version)
{
#if defined(WITH_WINPR_JSON)
const std::string url = "https://wiki.libsdl.org/SDL" + std::to_string(version);
std::cout << "GLOBAL CONFIGURATION FILE" << std::endl;
std::cout << std::endl;
std::cout << " The SDL client supports some system defined configuration options."
<< std::endl;
std::cout << " Settings are stored in JSON format" << std::endl;
std::cout << " The location is a system configuration file. Location for current machine is "
<< SdlPref::instance()->get_pref_file(true) << std::endl;
std::cout << std::endl;
std::cout << " The following configuration options are supported:" << std::endl;
std::cout << std::endl;
std::cout << " isUserConfigEnabled" << std::endl;
std::cout << " Allows to enable/disable user specific configuration files." << std::endl;
std::cout << " Default enabled" << std::endl;
std::cout << std::endl;
std::cout << " All options of the following user configuration file are also supported here."
<< std::endl;
std::cout << std::endl;
std::cout << "CONFIGURATION FILE" << std::endl;
std::cout << std::endl;
std::cout << " The SDL client supports some user defined configuration options." << std::endl;
std::cout << " Settings are stored in JSON format" << std::endl;
std::cout << " The location is a per user file. Location for current user is "
<< SdlPref::instance()->get_pref_file() << std::endl;
std::cout
<< " The XDG_CONFIG_HOME environment variable can be used to override the base directory."
<< std::endl;
std::cout << std::endl;
std::cout << " The following configuration options are supported:" << std::endl;
std::cout << std::endl;
std::cout << " SDL_KeyModMask" << std::endl;
std::cout << " Defines the key combination required for SDL client shortcuts."
<< std::endl;
std::cout << " Default KMOD_RSHIFT" << std::endl;
std::cout << " An array of SDL_Keymod strings as defined at "
""
<< url << "/SDL_Keymod" << std::endl;
std::cout << std::endl;
std::cout << " SDL_Fullscreen" << std::endl;
std::cout << " Toggles client fullscreen state." << std::endl;
std::cout << " Default SDL_SCANCODE_RETURN." << std::endl;
std::cout << " A string as "
"defined at "
<< url << "/SDLScancodeLookup" << std::endl;
std::cout << std::endl;
std::cout << " SDL_Minimize" << std::endl;
std::cout << " Minimizes client windows." << std::endl;
std::cout << " Default SDL_SCANCODE_M." << std::endl;
std::cout << " A string as "
"defined at "
<< url << "/SDLScancodeLookup" << std::endl;
std::cout << std::endl;
std::cout << " SDL_Resizeable" << std::endl;
std::cout << " Toggles local window resizeable state." << std::endl;
std::cout << " Default SDL_SCANCODE_R." << std::endl;
std::cout << " A string as "
"defined at "
<< url << "/SDLScancodeLookup" << std::endl;
std::cout << std::endl;
std::cout << " SDL_Grab" << std::endl;
std::cout << " Toggles keyboard and mouse grab state." << std::endl;
std::cout << " Default SDL_SCANCODE_G." << std::endl;
std::cout << " A string as "
"defined at "
<< url << "/SDLScancodeLookup" << std::endl;
std::cout << std::endl;
std::cout << " SDL_Disconnect" << std::endl;
std::cout << " Disconnects from the RDP session." << std::endl;
std::cout << " Default SDL_SCANCODE_D." << std::endl;
std::cout << " A string as defined at " << url << "/SDLScancodeLookup" << std::endl;
#endif
}
SdlPref::SdlPref(std::string file)
: _name(std::move(file)), _system_name(get_default_file(true)), _config(get(false)),
_system_config(get(true))
{
}
std::string SdlPref::get_default_file(bool systemConfigOnly)
{
CStringPtr name(freerdp_GetConfigFilePath(systemConfigOnly, "sdl-freerdp.json"), free);
fs::path config{ name.get() };
return config.string();
}
std::shared_ptr<SdlPref> SdlPref::instance(const std::string& name)
{
static std::shared_ptr<SdlPref> _instance;
if (!_instance || (_instance->get_pref_file() != name))
_instance.reset(new SdlPref(name));
return _instance;
}
std::string SdlPref::get_pref_file(bool systemConfigOnly) const
{
if (systemConfigOnly)
return _system_name;
return _name;
}

View File

@@ -0,0 +1,69 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* SDL Prefs
*
* Copyright 2022 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 <memory>
#include <winpr/json.h>
class SdlPref
{
public:
[[nodiscard]] static std::shared_ptr<SdlPref>
instance(const std::string& name = SdlPref::get_default_file(false));
[[nodiscard]] std::string get_pref_file(bool systemConfigOnly = false) const;
[[nodiscard]] std::string get_string(const std::string& key, const std::string& fallback = "",
bool systemConfigOnly = false) const;
[[nodiscard]] int64_t get_int(const std::string& key, int64_t fallback = 0,
bool systemConfigOnly = false) const;
[[nodiscard]] bool get_bool(const std::string& key, bool fallback = false,
bool systemConfigOnly = false) const;
[[nodiscard]] std::vector<std::string> get_array(const std::string& key,
const std::vector<std::string>& fallback = {},
bool systemConfigOnly = false) const;
static void print_config_file_help(int version);
private:
using WINPR_JSONPtr = std::unique_ptr<WINPR_JSON, decltype(&WINPR_JSON_Delete)>;
std::string _name;
std::string _system_name;
WINPR_JSONPtr _config;
WINPR_JSONPtr _system_config;
explicit SdlPref(std::string file);
[[nodiscard]] WINPR_JSON* get_item(const std::string& key, bool systemConfigOnly) const;
[[nodiscard]] WINPR_JSON* get_item(const WINPR_JSONPtr& config, const std::string& key) const;
[[nodiscard]] WINPR_JSONPtr get(bool systemConfigOnly) const;
[[nodiscard]] bool get_bool(const WINPR_JSONPtr& config, const std::string& key,
bool fallback = false) const;
[[nodiscard]] bool is_user_config_enabled() const;
[[nodiscard]] static std::string get_default_file(bool systemConfigOnly);
[[nodiscard]] static std::string item_to_str(WINPR_JSON* item,
const std::string& fallback = "");
};

View File

@@ -0,0 +1,39 @@
set(MODULE_NAME "TestSDL2")
set(MODULE_PREFIX "TEST_SDL")
set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.cpp)
set(${MODULE_PREFIX}_TESTS TestSDLPrefs.cpp TestSDLWebview.cpp)
disable_warnings_for_directory(${CMAKE_CURRENT_BINARY_DIR})
create_test_sourcelist(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_DRIVER} ${${MODULE_PREFIX}_TESTS})
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../aad")
include_directories("${CMAKE_CURRENT_BINARY_DIR}/../aad")
add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
set(${MODULE_PREFIX}_LIBS freerdp freerdp-client winpr sdl-common-prefs sdl-common-aad-view)
target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
set(TEST_SRC_AREA "${CMAKE_CURRENT_SOURCE_DIR}")
set(TEST_BIN_AREA "${CMAKE_CURRENT_BINARY_DIR}")
if(WIN32)
string(REPLACE "\\" "\\\\" TEST_SRC_AREA "${TEST_SRC_AREA}")
string(REPLACE "\\" "\\\\" TEST_BIN_AREA "${TEST_BIN_AREA}")
endif()
add_compile_definitions(TEST_SRC_AREA="${TEST_SRC_AREA}")
add_compile_definitions(TEST_BIN_AREA="${TEST_BIN_AREA}")
foreach(test ${${MODULE_PREFIX}_TESTS})
get_filename_component(TestName ${test} NAME_WE)
add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
endforeach()
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Client/Test")

View File

@@ -0,0 +1,121 @@
#include "../sdl_prefs.hpp"
#include <iostream>
#include <fstream>
#include <winpr/config.h>
#include <winpr/winpr.h>
#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 std::shared_ptr<SdlPref> instance()
{
#if !defined(TEST_SRC_AREA)
#error "Please define TEST_SRC_AREA to the source directory of this test case"
#endif
fs::path src(TEST_SRC_AREA);
src /= "sdl-freerdp.json";
if (!fs::exists(src))
{
std::cerr << "[ERROR] test configuration file '" << src << "' does not exist" << std::endl;
return nullptr;
}
return SdlPref::instance(src.string());
}
int TestSDLPrefs(int argc, char* argv[])
{
WINPR_UNUSED(argc);
WINPR_UNUSED(argv);
#if defined(WITH_WINPR_JSON)
printf("implementation: json\n");
#else
printf("implementation: fallback\n");
#endif
#if defined(WITH_WINPR_JSON)
printf("config: %s\n", instance()->get_pref_file().c_str());
#endif
auto string_value = instance()->get_string("string_key", "cba");
#if defined(WITH_WINPR_JSON)
if (string_value != "abc")
return -1;
#else
if (string_value != "cba")
return -1;
#endif
auto string_value_nonexistent = instance()->get_string("string_key_nonexistent", "cba");
if (string_value_nonexistent != "cba")
return -1;
auto int_value = instance()->get_int("int_key", 321);
#if defined(WITH_WINPR_JSON)
if (int_value != 123)
return -1;
#else
if (int_value != 321)
return -1;
#endif
auto int_value_nonexistent = instance()->get_int("int_key_nonexistent", 321);
if (int_value_nonexistent != 321)
return -1;
auto bool_value = instance()->get_bool("bool_key", false);
#if defined(WITH_WINPR_JSON)
if (!bool_value)
return -1;
#else
if (bool_value)
return -1;
#endif
auto bool_value_nonexistent = instance()->get_bool("bool_key_nonexistent", false);
if (bool_value_nonexistent)
return -1;
auto array_value = instance()->get_array("array_key", { "c", "b", "a" });
if (array_value.size() != 3)
return -1;
#if defined(WITH_WINPR_JSON)
if (array_value.at(0) != "a")
return -1;
if (array_value.at(1) != "b")
return -1;
if (array_value.at(2) != "c")
return -1;
#else
if (array_value.at(0) != "c")
return -1;
if (array_value.at(1) != "b")
return -1;
if (array_value.at(2) != "a")
return -1;
#endif
auto array_value_nonexistent =
instance()->get_array("array_key_nonexistent", { "c", "b", "a" });
if (array_value_nonexistent.size() != 3)
return -1;
if (array_value_nonexistent.at(0) != "c")
return -1;
if (array_value_nonexistent.at(1) != "b")
return -1;
if (array_value_nonexistent.at(2) != "a")
return -1;
return 0;
}

View File

@@ -0,0 +1,30 @@
#include <iostream>
#include <fstream>
#include <memory>
#include <winpr/config.h>
#include <winpr/winpr.h>
#include <sdl_webview.hpp>
int TestSDLWebview(WINPR_ATTR_UNUSED int argc, WINPR_ATTR_UNUSED char* argv[])
{
#if 0
RDP_CLIENT_ENTRY_POINTS entry = {};
entry.Version = RDP_CLIENT_INTERFACE_VERSION;
entry.Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
entry.ContextSize = sizeof(rdpContext);
std::shared_ptr<rdpContext> context(freerdp_client_context_new(&entry),
[](rdpContext* ptr) { freerdp_client_context_free(ptr); });
char* token = nullptr;
if (!sdl_webview_get_access_token(context->instance, ACCESS_TOKEN_TYPE_AAD, &token, 2, "scope",
"foobar"))
{
std::cerr << "test failed!" << std::endl;
return -1;
}
#endif
return 0;
}

View File

@@ -0,0 +1,10 @@
{
"string_key": "abc",
"int_key": 123,
"bool_key": true,
"array_key": [
"a",
"b",
"c"
]
}