Update genkeymap executable to use new format

This commit is contained in:
matt335672 2024-05-23 19:12:44 +01:00
parent b0cfc1a12b
commit fe410a595d
6 changed files with 448 additions and 403 deletions

View File

@ -290,6 +290,9 @@
#define XR_RDP_SCAN_LSHIFT 42
#define XR_RDP_SCAN_ALT 56
// scancodes affected by numlock
#define XR_RDP_SCAN_MIN_NUMLOCK 71 // KP7
#define XR_RDP_SCAN_MAX_NUMLOCK 83 // KPDL
// Since we're not guaranteed to have pixman, copy these directives.
#define XRDP_PIXMAN_TYPE_ARGB 2

View File

@ -537,6 +537,13 @@ AC_CHECK_HEADER([X11/extensions/Xrandr.h], [],
[AC_MSG_ERROR([please install libxrandr-dev or libXrandr-devel])],
[#include <X11/Xlib.h>])
# checking for XKB
AC_CHECK_HEADER([X11/extensions/XKBrules.h], [],
[AC_MSG_ERROR([please install libxkbfile-dev or libxkbfile-devel])],
[#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <stdio.h>])
if test "x$enable_utmp" = "xyes"
then
AC_CHECK_HEADERS(utmp.h utmpx.h)

View File

@ -2,15 +2,19 @@ EXTRA_DIST = \
dump-keymaps.sh \
readme.txt
AM_CPPFLAGS = \
-I$(top_srcdir)/common
AM_CFLAGS = $(X_CFLAGS)
bin_PROGRAMS = \
xrdp-genkeymap
xrdp_genkeymap_SOURCES = genkeymap.c evdev-map.c
xrdp_genkeymap_SOURCES = genkeymap.c
xrdp_genkeymap_LDFLAGS = \
$(X_LIBS)
xrdp_genkeymap_LDADD = \
$(X_PRE_LIBS) -lX11 $(X_EXTRA_LIBS)
$(top_builddir)/common/libcommon.la \
$(X_PRE_LIBS) -lxkbfile -lX11 $(X_EXTRA_LIBS)

View File

@ -1,279 +0,0 @@
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
* evdev-map.c
* Copyright (C) Michał Górny 2014 <mgorny@gentoo.org>
*
* You may redistribute it and/or modify it under the terms of the
* GNU General Public License, as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* main.cc is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with main.cc. If not, write to:
* The Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301, USA
*
* xfree86(base)->evdev keycode mapping
*/
#if defined(HAVE_CONFIG_H)
#include <config_ac.h>
#endif
int xfree86_to_evdev[255 - 8 + 1] =
{
/* MDSW */ 203,
/* ESC */ 9,
/* AE01 */ 10,
/* AE02 */ 11,
/* AE03 */ 12,
/* AE04 */ 13,
/* AE05 */ 14,
/* AE06 */ 15,
/* AE07 */ 16,
/* AE08 */ 17,
/* AE09 */ 18,
/* AE10 */ 19,
/* AE11 */ 20,
/* AE12 */ 21,
/* BKSP */ 22,
/* TAB */ 23,
/* AD01 */ 24,
/* AD02 */ 25,
/* AD03 */ 26,
/* AD04 */ 27,
/* AD05 */ 28,
/* AD06 */ 29,
/* AD07 */ 30,
/* AD08 */ 31,
/* AD09 */ 32,
/* AD10 */ 33,
/* AD11 */ 34,
/* AD12 */ 35,
/* RTRN */ 36,
/* LCTL */ 37,
/* AC01 */ 38,
/* AC02 */ 39,
/* AC03 */ 40,
/* AC04 */ 41,
/* AC05 */ 42,
/* AC06 */ 43,
/* AC07 */ 44,
/* AC08 */ 45,
/* AC09 */ 46,
/* AC10 */ 47,
/* AC11 */ 48,
/* TLDE */ 49,
/* LFSH */ 50,
/* BKSL */ 51,
/* AB01 */ 52,
/* AB02 */ 53,
/* AB03 */ 54,
/* AB04 */ 55,
/* AB05 */ 56,
/* AB06 */ 57,
/* AB07 */ 58,
/* AB08 */ 59,
/* AB09 */ 60,
/* AB10 */ 61,
/* RTSH */ 62,
/* KPMU */ 63,
/* LALT */ 64,
/* SPCE */ 65,
/* CAPS */ 66,
/* FK01 */ 67,
/* FK02 */ 68,
/* FK03 */ 69,
/* FK04 */ 70,
/* FK05 */ 71,
/* FK06 */ 72,
/* FK07 */ 73,
/* FK08 */ 74,
/* FK09 */ 75,
/* FK10 */ 76,
/* NMLK */ 77,
/* SCLK */ 78,
/* KP7 */ 79,
/* KP8 */ 80,
/* KP9 */ 81,
/* KPSU */ 82,
/* KP4 */ 83,
/* KP5 */ 84,
/* KP6 */ 85,
/* KPAD */ 86,
/* KP1 */ 87,
/* KP2 */ 88,
/* KP3 */ 89,
/* KP0 */ 90,
/* KPDL */ 91,
/* SYRQ */ 107,
/* II5D */ 0,
/* LSGT */ 94,
/* FK11 */ 95,
/* FK12 */ 96,
/* HOME */ 110,
/* UP */ 111,
/* PGUP */ 112,
/* LEFT */ 113,
/* II65 */ 0,
/* RGHT */ 114,
/* END */ 115,
/* DOWN */ 116,
/* PGDN */ 117,
/* INS */ 118,
/* DELE */ 119,
/* KPEN */ 104,
/* RCTL */ 105,
/* PAUS */ 127,
/* PRSC */ 107,
/* KPDV */ 106,
/* RALT */ 108,
/* BRK */ 419,
/* LWIN */ 133,
/* RWIN */ 134,
/* MENU */ 0,
/* FK13 */ 191,
/* FK14 */ 192,
/* FK15 */ 193,
/* XF86AudioMute */ 121,
/* XF86AudioLowerVolume */ 122,
/* XF86AudioRaiseVolume */ 123,
/* LVL3 */ 92,
/* ALT */ 204,
/* KPEQ */ 125,
/* SUPR */ 206,
/* HYPR */ 207,
/* XFER */ 0,
/* I02 */ 0,
/* NFER */ 0,
/* I04 */ 0,
/* AE13 */ 132,
/* I06 */ 0,
/* I07 */ 0,
/* Cancel */ 136,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
/* XF86Launch1 */ 156,
/* XF86Launch2 */ 157,
0,
0,
0,
0,
0,
/* XF86Mail */ 163,
/* XF86Favorites */ 164,
0,
/* XF86Back */ 166,
/* XF86Forward */ 167,
0,
0,
0,
/* XF86AudioNext */ 171,
/* XF86AudioPlay */ 172,
/* XF86AudioPrev */ 173,
/* XF86AudioStop */ 174,
0,
0,
0,
0,
0,
/* XF86HomePage */ 180,
/* XF86Reload */ 181,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
/* XF86Search */ 225,
0,
0,
0,
0,
0,
0,
0,
0,
/* XF86AudioMedia */ 234,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
};

View File

@ -42,135 +42,407 @@
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XKBrules.h>
#include <locale.h>
#include <unistd.h>
extern int xfree86_to_evdev[137 - 8 + 1];
#include "scancode.h"
#include "xrdp_constants.h"
int main(int argc, char **argv)
// cppcheck doesn't always set this macro to something in double-quotes
#if defined(__cppcheck__)
#undef PACKAGE_VERSION
#endif
#if !defined(PACKAGE_VERSION)
#define PACKAGE_VERSION "???"
#endif
#define NUM_STATES 9
#define KEYMAP_FILE_FORMAT_VERSION "2"
// Scancodes affected by numlock
#define IS_KEYPAD_SCANCODE(s) \
((s) >= XR_RDP_SCAN_MIN_NUMLOCK && (s) <= XR_RDP_SCAN_MAX_NUMLOCK)
#define MAX_COMMENTS 10
/**
* Contains info about the current keyboard setting
*/
struct kbd_info
{
const char *programname;
char text[256];
char *displayname = NULL;
char *outfname;
const char *sections[8] =
char *keycode_set; ///< See 'setxkbmap -v'
char *rules; ///< See 'setxkbmap -query'
char *model; ///< See 'setxkbmap -query'
char *layout; ///< See 'setxkbmap -query'
char *variant; ///< See 'setxkbmap -query'
char *options; ///< See 'setxkbmap -query' (comma separated)
};
/*****************************************************************************/
/**
* Print brief info about program usage and exit
* @param programname Unqualified name of program
* @param status Exit status
*/
static void
usage(const char *programname, int status)
{
fprintf(stderr, "Usage: %s [ -k keycode_set] [-c comment] [-c comment...]"
" out_filename\n", programname);
fprintf(stderr, "Example: %s -r evdev -c \"en-US pc104 keyboard\""
" /etc/xrdp/km-00000409.toml\n", programname);
exit(status);
}
/*****************************************************************************/
/**
* Free a kbd_info struct
* @param kbd_info struct to free. May be incomplete or NULL
*/
static void
free_kbd_info(struct kbd_info *kbd_info)
{
if (kbd_info != NULL)
{
"noshift", "shift", "altgr", "shiftaltgr",
"capslock", "capslockaltgr", "shiftcapslock", "shiftcapslockaltgr"
free(kbd_info->keycode_set);
free(kbd_info->rules);
free(kbd_info->model);
free(kbd_info->layout);
free(kbd_info->variant);
free(kbd_info->options);
free(kbd_info);
}
}
/*****************************************************************************/
/**
* Queries the X server to get information about the current keyboard
* @param dpy X11 Display
* @param programname Unqualified name of program
* @return kbd_info struct, or NULL
*
* The structure may be incomplete if some data could not be obtained
*/
static struct kbd_info *
get_kbd_info(Display *dpy, const char *programname)
{
struct kbd_info *kbd_info;
char *rules;
XkbRF_VarDefsRec vd;
XkbDescPtr kbdesc = NULL;
if ((kbd_info = (struct kbd_info *)malloc( sizeof(*kbd_info))) == NULL)
{
fprintf(stderr, "%s: Out of memory\n", programname);
}
else if ((kbdesc = XkbAllocKeyboard()) == NULL)
{
fprintf(stderr, "%s: unable to allocate keyboard desc\n",
programname);
free_kbd_info(kbd_info);
kbd_info = NULL;
}
else if (XkbGetNames(dpy, XkbKeycodesNameMask, kbdesc) != Success)
{
fprintf(stderr, "%s: unable to obtain keycode name for keyboard\n",
programname);
free_kbd_info(kbd_info);
kbd_info = NULL;
}
else
{
char *symatom = XGetAtomName(dpy, kbdesc->names->keycodes);
kbd_info->keycode_set = strdup(symatom);
XFree(symatom);
if (XkbRF_GetNamesProp(dpy, &rules, &vd) == 0 || rules == NULL)
{
fprintf(stderr, "%s: Couldn't interpret %s property\n",
programname, _XKB_RF_NAMES_PROP_ATOM);
kbd_info->rules = NULL;
kbd_info->model = NULL;
kbd_info->layout = NULL;
kbd_info->variant = NULL;
kbd_info->options = NULL;
}
else
{
kbd_info->rules = rules;
kbd_info->model = vd.model;
kbd_info->layout = vd.layout;
kbd_info->variant = vd.variant;
kbd_info->options = vd.options;
}
}
if (kbdesc != NULL)
{
XkbFreeKeyboard(kbdesc, 0, True);
}
return kbd_info;
}
/*****************************************************************************/
/**
* Outputs a comment containing the last setxkbmap command
*
* @param outf Output file
* @param kbd_info Keyboard info struct
*
*/
static void
output_setxkbmap_comment(FILE *outf, const struct kbd_info *kbd_info)
{
if (kbd_info->model != NULL || kbd_info->layout != NULL ||
kbd_info->variant != NULL || kbd_info->options != NULL)
{
fprintf(outf, "# setxkbmap -rules %s", kbd_info->rules);
if (kbd_info->model != NULL)
{
fprintf(outf, " -model %s", kbd_info->model);
}
if (kbd_info->layout != NULL)
{
fprintf(outf, " -layout %s", kbd_info->layout);
}
if (kbd_info->variant != NULL)
{
fprintf(outf, " -variant %s", kbd_info->variant);
}
if (kbd_info->options != NULL)
{
// Options is comma-separated, but to achieve the same effect
// with the command we need to use multiple '-option' args
char *optionstr = strdup(kbd_info->options);
if (optionstr != NULL)
{
char *p = strtok(optionstr, ",");
fprintf(outf, " -option \"\"");
while (p != NULL)
{
fprintf(outf, " -option %s", p);
p = strtok(NULL, ",");
}
free(optionstr);
}
}
putc('\n', outf);
}
}
/*****************************************************************************/
/**
* Output a section of the keymap file
* @param outf Output file
* @param dpy X display
* @param section name Section name (e.g. 'shift')
* @param event_state Modifier state needed for XKeyPressedEvent
*/
static void
output_file_section(FILE *outf,
Display *dpy,
const char *section_name,
unsigned int event_state)
{
XKeyPressedEvent e =
{
.type = KeyPress,
.serial = 16,
.send_event = True,
.display = dpy,
.state = event_state,
.same_screen = True
};
int states[8] = {0, 1, 0x80, 0x81, 2, 0x82, 3, 0x83};
int i;
int idx;
int char_count;
int nbytes = 0;
int unicode;
Display *dpy;
KeySym ks;
FILE *outf;
XKeyPressedEvent e;
const char *ksstr;
unsigned short scancode;
unsigned int iter = 0;
char text[256];
wchar_t wtext[256];
XkbDescPtr kbdesc;
char *symatom;
int is_evdev;
int is_numlock_section = (strcmp(section_name, "numlock") == 0);
fprintf(outf, "\n[%s]\n", section_name);
while ((scancode = scancode_get_next(&iter)) != 0)
{
// Numlock state table can be very small
if (is_numlock_section && !IS_KEYPAD_SCANCODE(scancode))
{
continue;
}
e.keycode = scancode_to_keycode(scancode);
nbytes = XLookupString(&e, text, 255, &ks, NULL);
if (ks == NoSymbol)
{
continue;
}
ksstr = XKeysymToString(ks);
if (ksstr == NULL)
{
continue;
}
text[nbytes] = 0;
char_count = mbstowcs(wtext, text, 255);
unicode = 0;
if (char_count == 1)
{
unicode = wtext[0];
}
if (scancode > 0xff)
{
fputs("E0_", outf);
}
fprintf(outf, "%02X=\"%d", (scancode & 0xff), (int)ks);
if (unicode != 0)
{
fprintf(outf, ":U+%04X", unicode);
}
fprintf(outf, "\" # %s\n", ksstr);
}
}
/*****************************************************************************/
/**
* Main
* @param argc Argument count
* @param argv Pointers to arguments
* @return 0 for success
*/
int main(int argc, char **argv)
{
const char *programname;
int opt;
char *displayname = NULL;
char *outfname;
const char *sections[NUM_STATES] =
{
"noshift", "shift", "altgr", "shiftaltgr",
"capslock", "capslockaltgr", "shiftcapslock", "shiftcapslockaltgr",
"numlock"
};
int states[NUM_STATES] = {0, 1, 0x80, 0x81, 2, 0x82, 3, 0x83, 0x10};
int idx;
Display *dpy = NULL;
FILE *outf = NULL;
const char *comment[MAX_COMMENTS] = {0};
int comment_count = 0;
const char *keycode_set = NULL;
struct kbd_info *kbd_info = NULL;
int status = 1;
setlocale(LC_CTYPE, "");
programname = argv[0];
if (argc != 2)
if (strrchr(argv[0], '/') != NULL)
{
fprintf(stderr, "Usage: %s out_filename\n", programname);
fprintf(stderr, "Example: %s /etc/xrdp/km-00000409.ini\n", programname);
return 1;
programname = strrchr(argv[0], '/') + 1;
}
else
{
programname = argv[0];
}
outfname = argv[1];
dpy = XOpenDisplay(displayname);
while ((opt = getopt(argc, argv, "c:k:")) != -1)
{
switch (opt)
{
case 'c':
if (comment_count < MAX_COMMENTS)
{
comment[comment_count++] = optarg;
}
break;
case 'k':
keycode_set = optarg;
break;
default: /* '?' */
usage(programname, 1);
}
}
if ((optind + 1) != argc)
{
usage(programname, 1);
}
outfname = argv[optind];
dpy = XOpenDisplay(displayname);
if (!dpy)
{
fprintf(stderr, "%s: unable to open display '%s'\n",
programname, XDisplayName(displayname));
return 1;
goto finish;
}
/* check whether evdev is used */
kbdesc = XkbAllocKeyboard();
if (!kbdesc)
if ((kbd_info = get_kbd_info(dpy, programname)) == 0)
{
// An error has already been logged
goto finish;
}
// If the keycode set isn't specified, use the one returned
// by the XKB extension
if (keycode_set == NULL)
{
keycode_set = kbd_info->keycode_set;
}
if (scancode_set_keycode_set(keycode_set) != 0)
{
fprintf(stderr, "%s: keycode set '%s' is not recognised\n",
programname, keycode_set);
goto finish;
}
if ((outf = fopen(outfname, "w")) == NULL)
{
fprintf(stderr, "%s: unable to create file '%s'\n",
programname, outfname);
goto finish;
}
fprintf(outf, "# Created by %s V" PACKAGE_VERSION
"\n# Key code set: %s\n",
programname, keycode_set);
output_setxkbmap_comment(outf, kbd_info);
for (idx = 0; idx < comment_count; ++idx)
{
fprintf(outf, "# %s\n", comment[idx]);
}
fprintf(outf, "\n[General]\nversion=" KEYMAP_FILE_FORMAT_VERSION "\n");
for (idx = 0; idx < NUM_STATES; idx++) /* Sections and states */
{
output_file_section(outf, dpy, sections[idx], states[idx]);
}
status = 0; // Successful run
finish:
free_kbd_info(kbd_info);
if (dpy != NULL)
{
fprintf(stderr, "%s: unable to allocate keyboard desc\n",
programname);
XCloseDisplay(dpy);
return 1;
}
if (XkbGetNames(dpy, XkbKeycodesNameMask, kbdesc) != Success)
if (outf != NULL)
{
fprintf(stderr, "%s: unable to obtain keycode name for keyboard\n",
programname);
XkbFreeKeyboard(kbdesc, 0, True);
XCloseDisplay(dpy);
return 1;
fclose(outf);
}
symatom = XGetAtomName(dpy, kbdesc->names->keycodes);
is_evdev = !strncmp(symatom, "evdev", 5);
XFree(symatom);
XkbFreeKeyboard(kbdesc, 0, True);
outf = fopen(outfname, "w");
if (outf == NULL)
{
fprintf(stderr, "%s: unable to create file '%s'\n", programname, outfname);
XCloseDisplay(dpy);
return 1;
}
memset(&e, 0, sizeof(e));
e.type = KeyPress;
e.serial = 16;
e.send_event = True;
e.display = dpy;
e.same_screen = True;
for (idx = 0; idx < 8; idx++) /* Sections and states */
{
fprintf(outf, "[%s]\n", sections[idx]);
e.state = states[idx];
for (i = 8; i < 256; i++) /* Keycodes */
{
if (is_evdev)
{
e.keycode = xfree86_to_evdev[i - 8];
}
else
{
e.keycode = i;
}
nbytes = XLookupString(&e, text, 255, &ks, NULL);
if (ks == NoSymbol)
{
continue;
}
text[nbytes] = 0;
char_count = mbstowcs(wtext, text, 255);
unicode = 0;
if (char_count == 1)
{
unicode = wtext[0];
}
fprintf(outf, "Key%d=%d:%d\n", i, (int) ks, unicode);
}
if (idx != 7)
{
fprintf(outf, "\n");
}
}
XCloseDisplay(dpy);
fclose(outf);
return 0;
return status;
}

View File

@ -1,39 +1,70 @@
Creating a new keymap file.
---------------------------
Keymap file description
-----------------------
The keymap files are used by the xrdp login screen, and also when
sending keyboard input to a VNC server.
The names of the files are of the format;
km-xxxxxxxx.ini
km-xxxxxxxx.toml
where the xxxxxxxx is replaced by the hex number of the layout of interest.
The files have 8 sections;
The files are TOML compatible, with 10 sections;
[noshift], [shift], [altgr], [shiftaltgr], [capslock], [capslockaltgr],
[shiftcapslock], [shiftcapslockaltgr]
[General], [noshift], [shift], [altgr], [shiftaltgr], [capslock],
[capslockaltgr], [shiftcapslock], [shiftcapslockaltgr], [numlock]
In each section there are multiple lines for each key.
The [General] section contains information about the file. All other
sections contain key mappings corresponding to the state of the modifier
keys when the key is pressed.
An example line looks like;
Key10=49:49
<RDP scancode>="<KeySym>[:<unicode>]" # comment
In this line, 10 is the X11 scancode, the first 49 is the keysym value,
the second 49 if the unicode value of the key. This is the definition
for the 'noshift' '1' key on a en-us keyboard. In this case, the keysym
and the unicode value are the same.
RDP scancode
------------
The RDP scancode is the code received from the RDP client for each
key. RDP scancodes are more-or-less the same as Windows scancodes,
or "Scan Code Set 1" key-down values.
Here is an example where they are not;
Example scancodes might be '1C' for the enter key on most European
keyboards, or 'E0 1C' for the number pad enter key.
This is the definition for the backspace key;
Key22=65288:8
A good website to consult for scancodes for a wide range of keyboards is
https://kbdlayout.info/
And this is the star on the keypad;
Key63=65450:42
KeySym
------
The KeySym is a value used by the X server as an abstraction of the
engraving on the key being pressed. It is needed to interact with the
VNC server.
To create a new file run "xrdp-genkeymap <filename>"
Unicode
-------
Keys which generate a character when pressed have this value added.
This is used by the xrdp login screen.
Example: ./xrdp-genkeymap /etc/xrdp/km-00000409.ini
Comment
-------
For generated keymap files, the comment is the name of the X11 KeySym
for the key. This makes it a lot easier to see what is going on in
the file.
Creating a new file
-------------------
To create a new file:-
1) Start an X server
2) Use the 'setxkbmap' command to get the keyboard configured
for the X server. Currently this has to use the 'evdev' rules so
that xrdp and the X server agree on the low-level X11 keycodes to
be used for the keys.
3) Run the 'xrdp-genkeymap' command to extract the keyboard
mappings
Example: ./xrdp-genkeymap /etc/xrdp/km-00000409.toml
Note: You need to have enough rights to be able to write to the
/etc/xrdp directory.
@ -41,3 +72,10 @@ Note: You need to have enough rights to be able to write to the
Alternatively, create the keymap file in a directory of your choice, then
copy or move it over to /etc/xrdp using sudo/su.
Using the X server of your current session may not be a good idea, as
session and window managers can interfere with key bindings. A good option
is to use an 'Xvfb' dummy X server to do this.
See also the dump_keymaps.sh script which auto-generates many keymap
files for the xrdp distribution. Consider adding your keyboard into this
list.