Files
xrdp/xrdp/lang.c
T
matt335672 17ad375aba Allow a programmable backspace key for the login screen
The login screen uses hardcoded scancode values to identify particular
keys. This is changed for the backspace key for Colemak support, as
the caps lock key has been re-purposed as a backspace key. We now
identify a backspace request by looking at the keysym for the key.
2024-11-18 16:28:02 +00:00

756 lines
23 KiB
C

/**
* xrdp: A Remote Desktop Protocol server.
*
* Copyright (C) Jay Sorg 2004-2014
*
* 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.
*
* keylayout
* maximum unicode 19996(0x4e00)
*/
#if defined(HAVE_CONFIG_H)
#include <config_ac.h>
#endif
#include <ctype.h>
#include "xrdp.h"
#include "ms-rdpbcgr.h"
#include "log.h"
#include "string_calls.h"
#include "toml.h"
// Macro to return 0..15 for a valid isxdigit() character
#define XDIGIT_TO_VAL(d) (\
isdigit(d) ? (d) - '0' : \
((d) >= 'a' && (d) <= 'f') ? (d) - 'a' + 10 : \
(d) - 'A' + 10)
/*
* Struct representing the contents of a km file [General] section
*/
struct km_general
{
unsigned int version;
int caps_lock_supported;
};
const struct km_general km_general_default =
{
.version = 0,
.caps_lock_supported = 1
};
/*****************************************************************************/
struct xrdp_key_info *
get_key_info_from_kbd_event(int keyboard_flags, int key_code, int *keys,
int caps_lock, int num_lock, int scroll_lock,
struct xrdp_keymap *keymap)
{
struct xrdp_key_info *rv;
int shift;
int altgr;
int index;
shift = keys[SCANCODE_INDEX_LSHIFT_KEY] || keys[SCANCODE_INDEX_RSHIFT_KEY];
altgr = keys[SCANCODE_INDEX_RALT_KEY]; /* right alt */
rv = 0;
index = scancode_to_index(SCANCODE_FROM_KBD_EVENT(key_code, keyboard_flags));
// Don't take caps_lock into account if the keymap doesn't support it.
caps_lock = caps_lock && keymap->caps_lock_supported;
if (index >= 0)
{
// scancode_to_index() guarantees to map numlock scancodes
// to the same index values.
if (num_lock &&
index >= SCANCODE_MIN_NUMLOCK &&
index <= SCANCODE_MAX_NUMLOCK)
{
rv = &(keymap->keys_numlock[index - SCANCODE_MIN_NUMLOCK]);
}
else if (shift && caps_lock && altgr)
{
rv = &(keymap->keys_shiftcapslockaltgr[index]);
}
else if (shift && caps_lock)
{
rv = &(keymap->keys_shiftcapslock[index]);
}
else if (shift && altgr)
{
rv = &(keymap->keys_shiftaltgr[index]);
}
else if (shift)
{
rv = &(keymap->keys_shift[index]);
}
else if (caps_lock && altgr)
{
rv = &(keymap->keys_capslockaltgr[index]);
}
else if (caps_lock)
{
rv = &(keymap->keys_capslock[index]);
}
else if (altgr)
{
rv = &(keymap->keys_altgr[index]);
}
else
{
rv = &(keymap->keys_noshift[index]);
}
}
return rv;
}
/*****************************************************************************/
/**
* Converts a table key to a scancode index value
*
* @param key Table key
* @return index >= 0, or < 0 for an invalid key
*/
static int
key_to_scancode_index(const char *key)
{
int rv = -1;
int keyboard_flags = 0;
if ((key[0] == 'E' || key[0] == 'e') && key[2] == '_')
{
if (key[1] == '0')
{
keyboard_flags |= KBDFLAGS_EXTENDED;
key += 3;
}
else if (key[1] == '1')
{
keyboard_flags |= KBDFLAGS_EXTENDED1;
key += 3;
}
}
if (isxdigit(key[0]) && isxdigit(key[1]) && key[2] == '\0')
{
int code = XDIGIT_TO_VAL(key[0]) * 16 + XDIGIT_TO_VAL(key[1]);
rv = scancode_to_index(SCANCODE_FROM_KBD_EVENT(code, keyboard_flags));
}
return rv;
}
/*****************************************************************************/
/**
* Tests a value to see if it's a valid KeySym (decimal number)
*
* @param val
* @param[out] keysym. Keysym value if 1 is returned
* @return Boolean != 0 if the string is valid
*/
static int
is_valid_keysym(const char *val, int *sym)
{
int rv = 0;
int s = 0;
if (*val != '\0')
{
while (isdigit(*val))
{
s = s * 10 + (*val++ - '0');
}
if (*val == '\0')
{
*sym = s;
rv = 1;
}
}
return rv;
}
/*****************************************************************************/
/**
* Tests a value to see if it's a valid unicode character (U+xxxx)
*
* @param val
* @param[out] chr value if 1 is returned
* @return Boolean != 0 if the string is valid
*/
static int
is_valid_unicode_char(const char *val, char32_t *chr)
{
int rv = 0;
if ((val[0] == 'U' || val[0] == 'u') &&
val[1] == '+' && isxdigit(val[2]))
{
val += 2; // Skip 'U+'
const char *p = val;
char32_t c = 0;
while (isxdigit(*p))
{
c = c * 16 + XDIGIT_TO_VAL(*p);
++p;
}
if (*p == '\0' && (p - val) >= 4 && (p - val) <= 6)
{
rv = 1;
*chr = c;
}
}
return rv;
}
/*****************************************************************************/
/**
* keymap must be cleared before calling this function
*/
static int
km_read_section(toml_table_t *tfile, const char *section_name,
struct xrdp_key_info *keymap)
{
toml_table_t *section = toml_table_in(tfile, section_name);
if (section == NULL)
{
LOG(LOG_LEVEL_WARNING, "Section [%s] not found in keymap file",
section_name);
}
else
{
const char *key;
toml_datum_t val;
int i;
char *p;
const char *unicode_str;
for (i = 0 ; (key = toml_key_in(section, i)) != NULL; ++i)
{
// Get a scancode index from the key if possible
int sindex = key_to_scancode_index(key);
if (sindex < 0)
{
LOG(LOG_LEVEL_WARNING,
"Can't parse value '%s' in [%s] in keymap file",
key, section_name);
continue;
}
val = toml_string_in(section, key);
if (!val.ok)
{
LOG(LOG_LEVEL_WARNING,
"Can't read value for [%s]:%s in keymap file",
section_name, key);
continue;
}
// Does the value contain a unicode character?
if ((p = strchr(val.u.s, ':')) != NULL)
{
unicode_str = (p + 1);
*p = '\0'; // val is a copy, writeable by us
}
else
{
unicode_str = NULL;
}
/* Parse both values and add them to the keymap, logging any
* errors */
if (!is_valid_keysym(val.u.s, &keymap[sindex].sym))
{
LOG(LOG_LEVEL_WARNING,
"Can't read KeySym for [%s]:%s in keymap file",
section_name, key);
}
if (unicode_str != NULL &&
!is_valid_unicode_char(unicode_str, &keymap[sindex].chr))
{
LOG(LOG_LEVEL_WARNING,
"Can't read unicode character for [%s]:%s in keymap file",
section_name, key);
}
free(val.u.s);
}
}
return 0;
}
/*****************************************************************************/
int
get_keymaps(int keylayout, struct xrdp_keymap *keymap)
{
int basic_key_layout = keylayout & 0x0000ffff;
char filename[256];
int layout_list[10];
int layout_count = 0;
int i;
/* Work out a list of layouts to try to load */
layout_list[layout_count++] = keylayout; // Requested layout
if (basic_key_layout != keylayout)
{
layout_list[layout_count++] = basic_key_layout; // First fallback
}
layout_list[layout_count++] = 0x0409; // Last chance 'en-US'
/* search for a loadable layout in the list */
for (i = 0; i < layout_count; ++i)
{
// Convert key layout to a filename
g_snprintf(filename, sizeof(filename),
XRDP_CFG_PATH "/km-%08x.toml", layout_list[i]);
if (km_load_file(filename, keymap) == 0)
{
return 0;
}
}
/* No luck finding anything */
LOG(LOG_LEVEL_ERROR, "Cannot find a usable keymap file");
return 0;
}
/*****************************************************************************/
/**
* Parses the [General] section in a keymap file
* @param tfile TOML file in memory
* @param general result (initialised to defaults)
*/
static void
parse_km_general(toml_table_t *tfile, struct km_general *general)
{
toml_table_t *section;
if (tfile != NULL && (section = toml_table_in(tfile, "General")) != NULL)
{
toml_datum_t d;
if ((d = toml_int_in(section, "version")).ok)
{
general->version = d.u.i;
}
if ((d = toml_bool_in(section, "caps_lock_supported")).ok)
{
general->caps_lock_supported = d.u.b;
}
}
}
/*****************************************************************************/
/**
* Loads the [General] section only from a TOML file
* @param filename Name of TOML file
* @param quiet Set true to not log errors
* @param[out] km_general Contents of [General] section. Defaults are provided.
* @return 0 if the operation was successful
*/
static int
km_load_file_general(const char *filename, int quiet,
struct km_general *general)
{
FILE *fp;
toml_table_t *tfile;
char errbuf[200];
int rv = 1;
*general = km_general_default;
if ((fp = fopen(filename, "r")) == NULL)
{
if (!quiet)
{
LOG(LOG_LEVEL_ERROR, "Error loading keymap file %s (%s)",
filename, g_get_strerror());
}
}
else
{
tfile = toml_parse_file(fp, errbuf, sizeof(errbuf));
fclose(fp);
if (tfile == NULL)
{
if (!quiet)
{
LOG(LOG_LEVEL_ERROR, "Error in keymap file %s - %s",
filename, errbuf);
}
}
else
{
parse_km_general(tfile, general);
rv = 0;
toml_free(tfile);
}
}
return rv;
}
/*****************************************************************************/
/**
* Boolean to test if a keylayout supports the caps_lock modifier key
* @param keylayout keyboardLayout from TS_UD_CS_CORE (see [MS-RDPBCGR])
* @return True if layout supports caps lock
*/
static int
keylayout_supports_caps_lock(int keylayout)
{
char filename[256];
struct km_general general;
g_snprintf(filename, sizeof(filename),
XRDP_CFG_PATH "/km-%08x.toml", keylayout);
(void)km_load_file_general(filename, 1, &general);
return general.caps_lock_supported;
}
/*****************************************************************************/
int
km_load_file(const char *filename, struct xrdp_keymap *keymap)
{
FILE *fp;
toml_table_t *tfile;
char errbuf[200];
int rv = 0;
struct km_general general = km_general_default;
if ((fp = fopen(filename, "r")) == NULL)
{
LOG(LOG_LEVEL_ERROR, "Error loading keymap file %s (%s)",
filename, g_get_strerror());
rv = 1;
}
else if ((tfile = toml_parse_file(fp, errbuf, sizeof(errbuf))) == NULL)
{
LOG(LOG_LEVEL_ERROR, "Error in keymap file %s - %s", filename, errbuf);
fclose(fp);
rv = 1;
}
else
{
LOG(LOG_LEVEL_INFO, "Loading keymap file %s", filename);
fclose(fp);
/* Clear the whole keymap */
memset(keymap, 0, sizeof(*keymap));
/* Check to see if we should expect caps lock entries */
parse_km_general(tfile, &general);
keymap->caps_lock_supported = general.caps_lock_supported;
/* read the keymap sections */
km_read_section(tfile, "noshift", keymap->keys_noshift);
km_read_section(tfile, "shift", keymap->keys_shift);
km_read_section(tfile, "altgr", keymap->keys_altgr);
km_read_section(tfile, "shiftaltgr", keymap->keys_shiftaltgr);
if (keymap->caps_lock_supported)
{
km_read_section(tfile, "capslock", keymap->keys_capslock);
km_read_section(tfile, "capslockaltgr", keymap->keys_capslockaltgr);
km_read_section(tfile, "shiftcapslock", keymap->keys_shiftcapslock);
km_read_section(tfile, "shiftcapslockaltgr",
keymap->keys_shiftcapslockaltgr);
}
/* The numlock map is much smaller and offset by
* SCANCODE_MIX_NUMLOCK. Read the section into a temporary
* area and copy it over */
struct xrdp_key_info keys_numlock[SCANCODE_MAX_INDEX + 1];
int i;
for (i = SCANCODE_MIN_NUMLOCK; i <= SCANCODE_MAX_NUMLOCK; ++i)
{
keys_numlock[i].sym = 0;
keys_numlock[i].chr = 0;
}
km_read_section(tfile, "numlock", keys_numlock);
for (i = SCANCODE_MIN_NUMLOCK; i <= SCANCODE_MAX_NUMLOCK; ++i)
{
keymap->keys_numlock[i - SCANCODE_MIN_NUMLOCK] = keys_numlock[i];
}
toml_free(tfile);
}
return rv;
}
/*****************************************************************************/
void
xrdp_init_xkb_layout(struct xrdp_client_info *client_info)
{
int fd;
int index = 0;
int bytes;
struct list *names = (struct list *)NULL;
struct list *items = (struct list *)NULL;
struct list *values = (struct list *)NULL;
char *item = (char *)NULL;
char *value = (char *)NULL;
char *q = (char *)NULL;
char keyboard_cfg_file[256] = { 0 };
char rdp_layout[256] = { 0 };
const struct xrdp_keyboard_overrides *ko =
&client_info->xrdp_keyboard_overrides;
LOG(LOG_LEVEL_INFO, "xrdp_init_xkb_layout: Keyboard information sent"
" by the RDP client, keyboard_type:[0x%02X], keyboard_subtype:[0x%02X],"
" keylayout:[0x%08X]",
client_info->keyboard_type, client_info->keyboard_subtype,
client_info->keylayout);
if (ko->type != -1)
{
LOG(LOG_LEVEL_INFO, "overrode keyboard_type 0x%02X"
" with 0x%02X", client_info->keyboard_type, ko->type);
client_info->keyboard_type = ko->type;
}
if (ko->subtype != -1)
{
LOG(LOG_LEVEL_INFO, "overrode keyboard_subtype 0x%02X"
" with 0x%02X", client_info->keyboard_subtype,
ko->subtype);
client_info->keyboard_subtype = ko->subtype;
}
if (ko->layout != -1)
{
LOG(LOG_LEVEL_INFO, "overrode keylayout 0x%08X"
" with 0x%08X", client_info->keylayout, ko->layout);
client_info->keylayout = ko->layout;
}
/* infer model/variant */
/* TODO specify different X11 keyboard models/variants */
g_memset(client_info->model, 0, sizeof(client_info->model));
g_memset(client_info->variant, 0, sizeof(client_info->variant));
g_strncpy(client_info->layout, "us", sizeof(client_info->layout) - 1);
if (client_info->keyboard_subtype == 3)
{
/* macintosh keyboard */
bytes = sizeof(client_info->variant);
g_strncpy(client_info->variant, "mac", bytes - 1);
}
else if (client_info->keyboard_subtype == 0)
{
/* default - standard subtype */
client_info->keyboard_subtype = 1;
}
g_snprintf(keyboard_cfg_file, 255, "%s/xrdp_keyboard.ini", XRDP_CFG_PATH);
LOG(LOG_LEVEL_DEBUG, "keyboard_cfg_file %s", keyboard_cfg_file);
fd = g_file_open_ro(keyboard_cfg_file);
if (fd >= 0)
{
int section_found = -1;
char section_rdp_layouts[256] = { 0 };
char section_layouts_map[256] = { 0 };
names = list_create();
names->auto_free = 1;
items = list_create();
items->auto_free = 1;
values = list_create();
values->auto_free = 1;
file_read_sections(fd, names);
for (index = 0; index < names->count; index++)
{
q = (char *)list_get_item(names, index);
if (g_strncasecmp("default", q, 8) != 0)
{
int i;
file_read_section(fd, q, items, values);
for (i = 0; i < items->count; i++)
{
item = (char *)list_get_item(items, i);
value = (char *)list_get_item(values, i);
LOG(LOG_LEVEL_DEBUG, "xrdp_init_xkb_layout: item %s value %s",
item, value);
if (g_strcasecmp(item, "keyboard_type") == 0)
{
int v = g_atoi(value);
if (v == client_info->keyboard_type)
{
section_found = index;
}
}
else if (g_strcasecmp(item, "keyboard_subtype") == 0)
{
int v = g_atoi(value);
if (v != client_info->keyboard_subtype &&
section_found == index)
{
section_found = -1;
break;
}
}
else if (g_strcasecmp(item, "rdp_layouts") == 0)
{
if (section_found != -1 && section_found == index)
{
g_strncpy(section_rdp_layouts, value, 255);
}
}
else if (g_strcasecmp(item, "layouts_map") == 0)
{
if (section_found != -1 && section_found == index)
{
g_strncpy(section_layouts_map, value, 255);
}
}
else if (g_strcasecmp(item, "model") == 0)
{
if (section_found != -1 && section_found == index)
{
bytes = sizeof(client_info->model);
g_memset(client_info->model, 0, bytes);
g_strncpy(client_info->model, value, bytes - 1);
}
}
else if (g_strcasecmp(item, "variant") == 0)
{
if (section_found != -1 && section_found == index)
{
bytes = sizeof(client_info->variant);
g_memset(client_info->variant, 0, bytes);
g_strncpy(client_info->variant, value, bytes - 1);
}
}
else if (g_strcasecmp(item, "options") == 0)
{
if (section_found != -1 && section_found == index)
{
bytes = sizeof(client_info->options);
g_memset(client_info->options, 0, bytes);
g_strncpy(client_info->options, value, bytes - 1);
}
}
else
{
/*
* mixing items from different sections will result in
* skipping over current section.
*/
LOG(LOG_LEVEL_DEBUG, "xrdp_init_xkb_layout: skipping "
"configuration item - %s, continuing to next "
"section", item);
break;
}
}
list_clear(items);
list_clear(values);
}
}
if (section_found == -1)
{
g_memset(section_rdp_layouts, 0, sizeof(char) * 256);
g_memset(section_layouts_map, 0, sizeof(char) * 256);
// read default section
file_read_section(fd, "default", items, values);
for (index = 0; index < items->count; index++)
{
item = (char *)list_get_item(items, index);
value = (char *)list_get_item(values, index);
if (g_strcasecmp(item, "rdp_layouts") == 0)
{
g_strncpy(section_rdp_layouts, value, 255);
}
else if (g_strcasecmp(item, "layouts_map") == 0)
{
g_strncpy(section_layouts_map, value, 255);
}
}
list_clear(items);
list_clear(values);
}
/* load the map */
file_read_section(fd, section_rdp_layouts, items, values);
for (index = 0; index < items->count; index++)
{
int rdp_layout_id;
item = (char *)list_get_item(items, index);
value = (char *)list_get_item(values, index);
rdp_layout_id = g_htoi(value);
if (rdp_layout_id == client_info->keylayout)
{
g_strncpy(rdp_layout, item, 255);
break;
}
}
list_clear(items);
list_clear(values);
file_read_section(fd, section_layouts_map, items, values);
for (index = 0; index < items->count; index++)
{
item = (char *)list_get_item(items, index);
value = (char *)list_get_item(values, index);
if (g_strcasecmp(item, rdp_layout) == 0)
{
bytes = sizeof(client_info->layout);
g_strncpy(client_info->layout, value, bytes - 1);
break;
}
}
list_delete(names);
list_delete(items);
list_delete(values);
LOG(LOG_LEVEL_INFO, "xrdp_init_xkb_layout: model [%s] variant [%s] "
"layout [%s] options [%s]", client_info->model,
client_info->variant, client_info->layout, client_info->options);
g_file_close(fd);
}
else
{
LOG(LOG_LEVEL_ERROR, "xrdp_init_xkb_layout: error opening %s",
keyboard_cfg_file);
}
// Initialise the rules and a few keycodes for xorgxrdp
snprintf(client_info->xkb_rules, sizeof(client_info->xkb_rules),
"%s", scancode_get_xkb_rules());
if (keylayout_supports_caps_lock(client_info->keylayout))
{
client_info->x11_keycode_caps_lock =
scancode_to_x11_keycode(SCANCODE_CAPS_KEY);
}
else
{
LOG(LOG_LEVEL_INFO, "xrdp_init_xkb_layout: caps lock is not supported");
client_info->x11_keycode_caps_lock = 0;
}
client_info->x11_keycode_num_lock =
scancode_to_x11_keycode(SCANCODE_NUMLOCK_KEY);
client_info->x11_keycode_scroll_lock =
scancode_to_x11_keycode(SCANCODE_SCROLL_KEY);
}