[client,sdl] add shortcut config file

Allow keyboard shortcuts to be configured via config file.
This commit is contained in:
Armin Novak 2024-02-21 11:37:28 +01:00 committed by akallabeth
parent 118f43b377
commit 62f974a5c2
14 changed files with 442 additions and 70 deletions

View File

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

View File

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

View File

@ -0,0 +1,81 @@
<refsect1>
<title>Configuration file</title>
<variablelist>
<varlistentry>
<term>Format and Location:</term>
<listitem>
<para>The configuration file is stored per user.<sbr/>
The <replaceable>XDG_CONFIG_HOME</replaceable> environment variable can be used to override the base directory.<sbr/>
This defaults to <replaceable>~/.config</replaceable>
The location relative to <replaceable>XDG_CONFIG_HOME</replaceable> is <replaceable>$XDG_CONFIG_HOME/@VENDOR@/@PRODUCT@/@PROJECT_NAME@.json</replaceable><sbr/>
The configuration is stored in JSON format</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Supported options:</term>
<listitem>
<varlistentry>
<term><replaceable>SDL_KeyModMask</replaceable></term>
<listitem>
<varlistentry>
<listitem>
<para>Defines the key combination required for SDL client shortcuts.<sbr/>
Default <replaceable>KMOD_RSHIFT</replaceable><sbr/>
An array of <replaceable>SDL_Keymod</replaceable> strings as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDL_Keymod</replaceable></para>
</listitem>
</varlistentry>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>SDL_Fullscreen</replaceable></term>
<listitem>
<varlistentry>
<listitem>
<para>Toggles client fullscreen state.<sbr/>
Default <replaceable>SDL_SCANCODE_RETURN</replaceable>.<sbr/>
A string as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDLScancodeLookup</replaceable></para>
</listitem>
</varlistentry>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>SDL_Resizeable</replaceable></term>
<listitem>
<varlistentry>
<listitem>
<para>Toggles local window resizeable state.<sbr/>
Default <replaceable>SDL_SCANCODE_R</replaceable>.<sbr/>
A string as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDLScancodeLookup</replaceable></para>
</listitem>
</varlistentry>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>SDL_Grab</replaceable></term>
<listitem>
<varlistentry>
<listitem>
<para>Toggles keyboard and mouse grab state.<sbr/>
Default <replaceable>SDL_SCANCODE_G</replaceable>.<sbr/>
A string as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDLScancodeLookup</replaceable></para>
</listitem>
</varlistentry>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>SDL_Disconnect</replaceable></term>
<listitem>
<varlistentry>
<listitem>
<para>Disconnects from the RDP session.<sbr/>
Default <replaceable>SDL_SCANCODE_D</replaceable>.<sbr/>
A string as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDLScancodeLookup</replaceable></para>
</listitem>
</varlistentry>
</listitem>
</varlistentry>
</listitem>
</varlistentry>
</variablelist>
</refsect1>

View File

@ -4,6 +4,7 @@ PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
<!ENTITY syntax SYSTEM "freerdp-argument.1.xml">
<!ENTITY channels SYSTEM "sdl-freerdp-channels.1.xml">
<!ENTITY config SYSTEM "sdl-freerdp-config.1.xml">
<!ENTITY envvar SYSTEM "sdl-freerdp-envvar.1.xml">
<!ENTITY examples SYSTEM "sdl-freerdp-examples.1.xml">
]
@ -51,6 +52,8 @@ PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
&channels;
&config;
&envvar;
&examples;

View File

@ -19,6 +19,7 @@
#include <memory>
#include <mutex>
#include <iostream>
#include <freerdp/config.h>
@ -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;

View File

@ -22,6 +22,8 @@
#include "sdl_freerdp.hpp"
#include "sdl_utils.hpp"
#include <map>
#include <freerdp/scancode.h>
#include <freerdp/log.h>
@ -376,40 +378,93 @@ BOOL sdlInput::keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32
return TRUE;
}
uint32_t sdlInput::prefToMask()
{
const std::map<std::string, SDL_Keymod> 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;
}
}
}

View File

@ -19,6 +19,8 @@
#pragma once
#include <string>
#include <winpr/wtypes.h>
#include <freerdp/freerdp.h>
#include <SDL.h>
@ -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;

View File

@ -17,6 +17,17 @@
* limitations under the License.
*/
#include <fstream>
#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
#include <assert.h>
#include "sdl_utils.hpp"
@ -24,6 +35,12 @@
#include <SDL.h>
#include <winpr/path.h>
#include <freerdp/version.h>
#if defined(CJSON_FOUND)
#include <cjson/cJSON.h>
#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<cJSON, decltype(&cJSON_Delete)>;
static cJSONPtr get()
{
auto config = sdl_get_pref_file();
std::ifstream ifs(config);
std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
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<int64_t>(val);
#else
return fallback;
#endif
}
std::vector<std::string> sdl_get_pref_array(const std::string& key,
const std::vector<std::string>& fallback)
{
#if defined(CJSON_FOUND)
auto item = get_item(key);
if (!item || !cJSON_IsArray(item))
return fallback;
std::vector<std::string> 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<char, decltype(&free)>;
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();
}

View File

@ -24,6 +24,8 @@
#include <stdbool.h>
#include <SDL.h>
#include <string>
#include <vector>
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<std::string> sdl_get_pref_array(const std::string& key,
const std::vector<std::string>& fallback = {});

View File

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

View File

@ -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 */