diff --git a/client/SDL/CMakeLists.txt b/client/SDL/CMakeLists.txt index 234d55b5c..6d2b778c5 100644 --- a/client/SDL/CMakeLists.txt +++ b/client/SDL/CMakeLists.txt @@ -62,6 +62,14 @@ endif() find_package(SDL2 REQUIRED COMPONENTS) include_directories(${SDL2_INCLUDE_DIR}) include_directories(${SDL2_INCLUDE_DIRS}) +find_package(cJSON) + +set(LIBS "") +if (cJSON_FOUND) + include_directories(${CJSON_INCLUDE_DIRS}) + list(APPEND LIBS ${CJSON_LIBRARIES}) + add_compile_definitions(CJSON_FOUND) +endif() find_package(Threads REQUIRED) @@ -89,7 +97,7 @@ set(SRCS ) add_subdirectory(aad) -set(LIBS +list(APPEND LIBS winpr freerdp freerdp-client diff --git a/client/SDL/man/CMakeLists.txt b/client/SDL/man/CMakeLists.txt index d8677c416..1fb2adcf9 100644 --- a/client/SDL/man/CMakeLists.txt +++ b/client/SDL/man/CMakeLists.txt @@ -1,5 +1,6 @@ set(DEPS sdl-freerdp-channels.1.xml + sdl-freerdp-config.1.xml sdl-freerdp-examples.1.xml sdl-freerdp-envvar.1.xml ) @@ -8,4 +9,4 @@ set(MANPAGE_NAME ${PROJECT_NAME}) if (WITH_BINARY_VERSIONING) set(MANPAGE_NAME ${PROJECT_NAME}${PROJECT_VERSION_MAJOR}) endif() -generate_and_install_freerdp_man_from_xml(${PROJECT_NAME}.1 ${MANPAGE_NAME}.1 ${DEPS}) +generate_and_install_freerdp_man_from_xml(${PROJECT_NAME}.1 ${MANPAGE_NAME}.1 "${DEPS}") diff --git a/client/SDL/man/sdl-freerdp-channels.1.xml b/client/SDL/man/sdl-freerdp-channels.1.xml deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/SDL/man/sdl-freerdp-config.1.xml.in b/client/SDL/man/sdl-freerdp-config.1.xml.in new file mode 100644 index 000000000..3bace734f --- /dev/null +++ b/client/SDL/man/sdl-freerdp-config.1.xml.in @@ -0,0 +1,81 @@ + + Configuration file + + + + Format and Location: + + The configuration file is stored per user. + The XDG_CONFIG_HOME environment variable can be used to override the base directory. + This defaults to ~/.config + The location relative to XDG_CONFIG_HOME is $XDG_CONFIG_HOME/@VENDOR@/@PRODUCT@/@PROJECT_NAME@.json + The configuration is stored in JSON format + + + + Supported options: + + + SDL_KeyModMask + + + + Defines the key combination required for SDL client shortcuts. + Default KMOD_RSHIFT + An array of SDL_Keymod strings as defined at https://wiki.libsdl.org/SDL2/SDL_Keymod + + + + + + SDL_Fullscreen + + + + Toggles client fullscreen state. + Default SDL_SCANCODE_RETURN. + A string as defined at https://wiki.libsdl.org/SDL2/SDLScancodeLookup + + + + + + SDL_Resizeable + + + + Toggles local window resizeable state. + Default SDL_SCANCODE_R. + A string as defined at https://wiki.libsdl.org/SDL2/SDLScancodeLookup + + + + + + SDL_Grab + + + + Toggles keyboard and mouse grab state. + Default SDL_SCANCODE_G. + A string as defined at https://wiki.libsdl.org/SDL2/SDLScancodeLookup + + + + + + SDL_Disconnect + + + + Disconnects from the RDP session. + Default SDL_SCANCODE_D. + A string as defined at https://wiki.libsdl.org/SDL2/SDLScancodeLookup + + + + + + + + diff --git a/client/SDL/man/sdl-freerdp-envvar.1.xml b/client/SDL/man/sdl-freerdp-envvar.1.xml.in similarity index 100% rename from client/SDL/man/sdl-freerdp-envvar.1.xml rename to client/SDL/man/sdl-freerdp-envvar.1.xml.in diff --git a/client/SDL/man/sdl-freerdp-examples.1.xml b/client/SDL/man/sdl-freerdp-examples.1.xml.in similarity index 100% rename from client/SDL/man/sdl-freerdp-examples.1.xml rename to client/SDL/man/sdl-freerdp-examples.1.xml.in diff --git a/client/SDL/man/sdl-freerdp.1.xml.in b/client/SDL/man/sdl-freerdp.1.xml.in index 20d23004e..c4b991845 100644 --- a/client/SDL/man/sdl-freerdp.1.xml.in +++ b/client/SDL/man/sdl-freerdp.1.xml.in @@ -4,6 +4,7 @@ PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ + ] @@ -51,6 +52,8 @@ PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" &channels; + &config; + &envvar; &examples; diff --git a/client/SDL/sdl_freerdp.cpp b/client/SDL/sdl_freerdp.cpp index 7226d10b0..b6d5facdb 100644 --- a/client/SDL/sdl_freerdp.cpp +++ b/client/SDL/sdl_freerdp.cpp @@ -19,6 +19,7 @@ #include #include +#include #include @@ -1600,6 +1601,58 @@ static void SDLCALL winpr_LogOutputFunction(void* userdata, int category, SDL_Lo category2str(category), message); } +static void print_config_file_help() +{ +#if defined(CJSON_FOUND) + 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 " + << sdl_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 " + "https://wiki.libsdl.org/SDL2/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 https://wiki.libsdl.org/SDL2/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 https://wiki.libsdl.org/SDL2/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 https://wiki.libsdl.org/SDL2/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 https://wiki.libsdl.org/SDL2/SDLScancodeLookup" + << std::endl; +#endif +} + int main(int argc, char* argv[]) { int rc = -1; @@ -1624,6 +1677,7 @@ int main(int argc, char* argv[]) if (status) { rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv); + print_config_file_help(); if (freerdp_settings_get_bool(settings, FreeRDP_ListMonitors)) sdl_list_monitors(sdl); return rc; diff --git a/client/SDL/sdl_kbd.cpp b/client/SDL/sdl_kbd.cpp index 03a608fc6..5bd6d6db3 100644 --- a/client/SDL/sdl_kbd.cpp +++ b/client/SDL/sdl_kbd.cpp @@ -22,6 +22,8 @@ #include "sdl_freerdp.hpp" #include "sdl_utils.hpp" +#include + #include #include @@ -376,40 +378,93 @@ BOOL sdlInput::keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 return TRUE; } +uint32_t sdlInput::prefToMask() +{ + const std::map mapping = { + { "KMOD_LSHIFT", KMOD_LSHIFT }, + { "KMOD_RSHIFT", KMOD_RSHIFT }, + { "KMOD_LCTRL", KMOD_LCTRL }, + { "KMOD_RCTRL", KMOD_RCTRL }, + { "KMOD_LALT", KMOD_LALT }, + { "KMOD_RALT", KMOD_RALT }, + { "KMOD_LGUI", KMOD_LGUI }, + { "KMOD_RGUI", KMOD_RGUI }, + { "KMOD_NUM", KMOD_NUM }, + { "KMOD_CAPS", KMOD_CAPS }, + { "KMOD_MODE", KMOD_MODE }, +#if SDL_VERSION_ATLEAST(2, 0, 18) + { "KMOD_SCROLL", KMOD_SCROLL }, +#endif + { "KMOD_CTRL", KMOD_CTRL }, + { "KMOD_SHIFT", KMOD_SHIFT }, + { "KMOD_ALT", KMOD_ALT }, + { "KMOD_GUI", KMOD_GUI } + }; + uint32_t mod = KMOD_NONE; + for (const auto& val : sdl_get_pref_array("SDL_KeyModMask", { "KMOD_RSHIFT" })) + { + auto it = mapping.find(val); + if (it != mapping.end()) + { + mod |= it->second; + } + } + return mod; +} + static const char* sdl_scancode_name(Uint32 scancode) { - for (size_t x = 0; x < ARRAYSIZE(map); x++) + for (const auto& cur : map) { - const scancode_entry_t* cur = &map[x]; - if (cur->sdl == scancode) - return cur->sdl_name; + if (cur.sdl == scancode) + return cur.sdl_name; } return "SDL_SCANCODE_UNKNOWN"; } +static Uint32 sdl_scancode_val(const char* scancodeName) +{ + for (const auto& cur : map) + { + if (strcmp(cur.sdl_name, scancodeName) == 0) + return cur.sdl; + } + + return SDL_SCANCODE_UNKNOWN; +} + static const char* sdl_rdp_scancode_name(UINT32 scancode) { - for (size_t x = 0; x < ARRAYSIZE(map); x++) + for (const auto& cur : map) { - const scancode_entry_t* cur = &map[x]; - if (cur->rdp == scancode) - return cur->rdp_name; + if (cur.rdp == scancode) + return cur.rdp_name; } return "RDP_SCANCODE_UNKNOWN"; } +static UINT32 sdl_rdp_scancode_val(const char* scancodeName) +{ + for (const auto& cur : map) + { + if (strcmp(cur.rdp_name, scancodeName) == 0) + return cur.rdp; + } + + return RDP_SCANCODE_UNKNOWN; +} + static UINT32 sdl_scancode_to_rdp(Uint32 scancode) { UINT32 rdp = RDP_SCANCODE_UNKNOWN; - for (size_t x = 0; x < ARRAYSIZE(map); x++) + for (const auto& cur : map) { - const scancode_entry_t* cur = &map[x]; - if (cur->sdl == scancode) + if (cur.sdl == scancode) { - rdp = cur->rdp; + rdp = cur.rdp; break; } } @@ -422,32 +477,52 @@ static UINT32 sdl_scancode_to_rdp(Uint32 scancode) return rdp; } +uint32_t sdlInput::prefKeyValue(const std::string& key, uint32_t fallback) +{ + auto item = sdl_get_pref_string(key); + if (item.empty()) + return fallback; + auto val = sdl_scancode_val(item.c_str()); + if (val == SDL_SCANCODE_UNKNOWN) + return fallback; + return val; +} + BOOL sdlInput::keyboard_handle_event(const SDL_KeyboardEvent* ev) { WINPR_ASSERT(ev); const UINT32 rdp_scancode = sdl_scancode_to_rdp(ev->keysym.scancode); const SDL_Keymod mods = SDL_GetModState(); - const SDL_Keymod mask = KMOD_RSHIFT; + const auto mask = prefToMask(); + const auto valFullscreen = prefKeyValue("SDL_Fullscreen", SDL_SCANCODE_RETURN); + const auto valResizeable = prefKeyValue("SDL_Resizeable", SDL_SCANCODE_R); + const auto valGrab = prefKeyValue("SDL_Grab", SDL_SCANCODE_G); + const auto valDisconnect = prefKeyValue("SDL_Disconnect", SDL_SCANCODE_D); + if ((mods & mask) == mask) { if (ev->type == SDL_KEYDOWN) { - switch (ev->keysym.scancode) + if (ev->keysym.scancode == valFullscreen) { - case SDL_SCANCODE_RETURN: - _sdl->update_fullscreen(!_sdl->fullscreen); - return TRUE; - case SDL_SCANCODE_R: - _sdl->update_resizeable(!_sdl->resizeable); - return TRUE; - case SDL_SCANCODE_G: - keyboard_grab(ev->windowID, _sdl->grab_kbd ? SDL_FALSE : SDL_TRUE); - return TRUE; - case SDL_SCANCODE_D: - freerdp_abort_connect_context(_sdl->context()); - return true; - default: - break; + _sdl->update_fullscreen(!_sdl->fullscreen); + return TRUE; + } + if (ev->keysym.scancode == valResizeable) + { + _sdl->update_resizeable(!_sdl->resizeable); + return TRUE; + } + + if (ev->keysym.scancode == valGrab) + { + keyboard_grab(ev->windowID, _sdl->grab_kbd ? SDL_FALSE : SDL_TRUE); + return TRUE; + } + if (ev->keysym.scancode == valDisconnect) + { + freerdp_abort_connect_context(_sdl->context()); + return TRUE; } } } diff --git a/client/SDL/sdl_kbd.hpp b/client/SDL/sdl_kbd.hpp index 4b622fa45..6dc5a83af 100644 --- a/client/SDL/sdl_kbd.hpp +++ b/client/SDL/sdl_kbd.hpp @@ -19,6 +19,8 @@ #pragma once +#include + #include #include #include @@ -45,6 +47,9 @@ class sdlInput static BOOL keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState, UINT32 imeConvMode); + static uint32_t prefToMask(); + static uint32_t prefKeyValue(const std::string& key, uint32_t fallback = SDL_SCANCODE_UNKNOWN); + private: SdlContext* _sdl; Uint32 _lastWindowID; diff --git a/client/SDL/sdl_utils.cpp b/client/SDL/sdl_utils.cpp index 4d8d54519..10fe90bae 100644 --- a/client/SDL/sdl_utils.cpp +++ b/client/SDL/sdl_utils.cpp @@ -17,6 +17,17 @@ * limitations under the License. */ +#include +#if __has_include() +#include +namespace fs = std::filesystem; +#elif __has_include() +#include +namespace fs = std::experimental::filesystem; +#else +#error Could not find system header "" or "" +#endif + #include #include "sdl_utils.hpp" @@ -24,6 +35,12 @@ #include +#include +#include +#if defined(CJSON_FOUND) +#include +#endif + const char* sdl_event_type_str(Uint32 type) { #define STR(x) #x @@ -286,3 +303,112 @@ bool sdl_push_quit() SDL_PushEvent(&ev); return true; } + +#if defined(CJSON_FOUND) +using cJSONPtr = std::unique_ptr; + +static cJSONPtr get() +{ + auto config = sdl_get_pref_file(); + + std::ifstream ifs(config); + std::string content((std::istreambuf_iterator(ifs)), (std::istreambuf_iterator())); + return { cJSON_ParseWithLength(content.c_str(), content.size()), cJSON_Delete }; +} + +static cJSON* get_item(const std::string& key) +{ + static cJSONPtr config{ nullptr, cJSON_Delete }; + if (!config) + config = get(); + if (!config) + return nullptr; + return cJSON_GetObjectItem(config.get(), key.c_str()); +} + +static std::string item_to_str(cJSON* item, const std::string& fallback = "") +{ + if (!item || !cJSON_IsString(item)) + return fallback; + auto str = cJSON_GetStringValue(item); + if (!str) + return {}; + return str; +} +#endif + +std::string sdl_get_pref_string(const std::string& key, const std::string& fallback) +{ +#if defined(CJSON_FOUND) + auto item = get_item(key); + return item_to_str(item, fallback); +#else + return fallback; +#endif +} + +bool sdl_get_pref_bool(const std::string& key, bool fallback) +{ +#if defined(CJSON_FOUND) + auto item = get_item(key); + if (!item || !cJSON_IsBool(item)) + return fallback; + return cJSON_IsTrue(item); +#else + return fallback; +#endif +} + +int64_t sdl_get_pref_int(const std::string& key, int64_t fallback) +{ +#if defined(CJSON_FOUND) + auto item = get_item(key); + if (!item || !cJSON_IsNumber(item)) + return fallback; + auto val = cJSON_GetNumberValue(item); + return static_cast(val); +#else + return fallback; +#endif +} + +std::vector sdl_get_pref_array(const std::string& key, + const std::vector& fallback) +{ +#if defined(CJSON_FOUND) + auto item = get_item(key); + if (!item || !cJSON_IsArray(item)) + return fallback; + + std::vector values; + for (int x = 0; x < cJSON_GetArraySize(item); x++) + { + auto cur = cJSON_GetArrayItem(item, x); + values.push_back(item_to_str(cur)); + } + + return values; +#else + return fallback; +#endif +} + +std::string sdl_get_pref_dir() +{ + using CStringPtr = std::unique_ptr; + CStringPtr path(GetKnownPath(KNOWN_PATH_XDG_CONFIG_HOME), free); + if (!path) + return {}; + + fs::path config{ path.get() }; + config /= FREERDP_VENDOR; + config /= FREERDP_PRODUCT; + return config.string(); +} + +std::string sdl_get_pref_file() +{ + fs::path config{ sdl_get_pref_dir() }; + config /= "sdl-freerdp.json"; + return config.string(); +} diff --git a/client/SDL/sdl_utils.hpp b/client/SDL/sdl_utils.hpp index f9ef194fb..882eeadd5 100644 --- a/client/SDL/sdl_utils.hpp +++ b/client/SDL/sdl_utils.hpp @@ -24,6 +24,8 @@ #include #include +#include +#include class CriticalSection { @@ -98,3 +100,12 @@ const char* sdl_error_string(Uint32 res); #define sdl_log_error(res, log, what) sdl_log_error_ex(res, log, what, __FILE__, __LINE__, __func__) BOOL sdl_log_error_ex(Uint32 res, wLog* log, const char* what, const char* file, size_t line, const char* fkt); + +std::string sdl_get_pref_dir(); +std::string sdl_get_pref_file(); + +std::string sdl_get_pref_string(const std::string& key, const std::string& fallback = ""); +int64_t sdl_get_pref_int(const std::string& key, int64_t fallback = 0); +bool sdl_get_pref_bool(const std::string& key, bool fallback = false); +std::vector sdl_get_pref_array(const std::string& key, + const std::vector& fallback = {}); diff --git a/cmake/InstallFreeRDPMan.cmake b/cmake/InstallFreeRDPMan.cmake index 5e0b6a3c8..ba0d8a8af 100644 --- a/cmake/InstallFreeRDPMan.cmake +++ b/cmake/InstallFreeRDPMan.cmake @@ -2,48 +2,54 @@ include(GNUInstallDirs) include(FindDocBookXSL) function(install_freerdp_man manpage section) - if(WITH_MANPAGES) - install(FILES ${manpage} DESTINATION ${CMAKE_INSTALL_MANDIR}/man${section}) - endif() + if(WITH_MANPAGES) + install(FILES ${manpage} DESTINATION ${CMAKE_INSTALL_MANDIR}/man${section}) + endif() endfunction() function(generate_and_install_freerdp_man_from_xml template manpage dependencies) - if(WITH_MANPAGES) - find_program(XSLTPROC_EXECUTABLE NAMES xsltproc REQUIRED) - if (NOT DOCBOOKXSL_FOUND) - message(FATAL_ERROR "docbook xsl not found but required for manpage generation") + if(WITH_MANPAGES) + find_program(XSLTPROC_EXECUTABLE NAMES xsltproc REQUIRED) + if (NOT DOCBOOKXSL_FOUND) + message(FATAL_ERROR "docbook xsl not found but required for manpage generation") + endif() + + # We need the variable ${MAN_TODAY} to contain the current date in ISO + # format to replace it in the configure_file step. + include(today) + + TODAY(MAN_TODAY) + + configure_file(${template}.xml.in ${manpage}.xml @ONLY IMMEDIATE) + + foreach(DEP IN LISTS dependencies) + set(SRC ${CMAKE_CURRENT_SOURCE_DIR}/${DEP}.in) + set(DST ${CMAKE_CURRENT_BINARY_DIR}/${DEP}) + + if (EXISTS ${SRC}) + message("generating ${DST} from ${SRC}") + configure_file(${SRC} ${DST} @ONLY IMMEDIATE) + else() + message("using ${DST} from ${SRC}") + endif() + endforeach() + + add_custom_command( + OUTPUT ${manpage} + COMMAND ${CMAKE_BINARY_DIR}/client/common/man/generate_argument_docbook + COMMAND ${XSLTPROC_EXECUTABLE} --path "${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}" ${DOCBOOKXSL_DIR}/manpages/docbook.xsl ${manpage}.xml + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS + ${CMAKE_CURRENT_BINARY_DIR}/${manpage}.xml + generate_argument_docbook + ${template}.xml.in + ) + + add_custom_target( + ${manpage}.manpage ALL + DEPENDS + ${manpage} + ) + install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${manpage} 1) endif() - - # We need the variable ${MAN_TODAY} to contain the current date in ISO - # format to replace it in the configure_file step. - include(today) - - TODAY(MAN_TODAY) - - configure_file(${template}.xml.in ${manpage}.xml @ONLY IMMEDIATE) - - set(dep_SRC) - foreach(dep ${dependencies}) - set(cur_SRC ${CMAKE_CURRENT_SOURCE_DIR}/${dep}) - list(APPEND dep_SRC ${cur_SRC}) - endforeach() - - add_custom_command( - OUTPUT ${manpage} - COMMAND ${CMAKE_BINARY_DIR}/client/common/man/generate_argument_docbook - COMMAND ${XSLTPROC_EXECUTABLE} --path ${CMAKE_CURRENT_SOURCE_DIR} ${DOCBOOKXSL_DIR}/manpages/docbook.xsl ${manpage}.xml - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS - ${CMAKE_CURRENT_BINARY_DIR}/${manpage}.xml - generate_argument_docbook - ${template}.xml.in - ) - - add_custom_target( - ${manpage}.manpage ALL - DEPENDS - ${manpage} - ) - install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${manpage} 1) - endif() endfunction() diff --git a/include/config/version.h.in b/include/config/version.h.in index 406b42cbb..0dffcbac9 100644 --- a/include/config/version.h.in +++ b/include/config/version.h.in @@ -29,5 +29,7 @@ #define FREERDP_VERSION_FULL "${FREERDP_VERSION_FULL}" #define FREERDP_GIT_REVISION "${GIT_REVISION}" #define FREERDP_USER_AGENT "FreeRDP/${FREERDP_VERSION_FULL}" +#define FREERDP_VENDOR "${VENDOR}" +#define FREERDP_PRODUCT "${PRODUCT}" #endif /* FREERDP_VERSION_H */