tool: generate input_filter config from XKB

Issue #3483
This commit is contained in:
Christian Helmuth 2019-08-23 15:58:23 +02:00
parent ca850c787f
commit 43a8118d2e
7 changed files with 1211 additions and 0 deletions

1
tool/xkb2ifcfg/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/xkb2ifcfg

36
tool/xkb2ifcfg/Makefile Normal file
View File

@ -0,0 +1,36 @@
#
# Check config
#
ifeq ($(filter clean cleanall, $(MAKECMDGOALS)),)
GENODE_DIR := $(realpath $(dir $(MAKEFILE_LIST))/../..)
ifneq ($(shell pkg-config --print-errors --errors-to-stdout --exists xkbcommon),)
$(error Please install libxkbcommon-dev)
endif
endif
#
# Build rules
#
TARGET = xkb2ifcfg
SRC_CC = $(wildcard *.cc)
SRC_H = $(wildcard *.h)
CFLAGS = -Werror -Wall -Wextra -Wno-attributes -std=gnu++17 -ggdb
CFLAGS += -I$(GENODE_DIR)/repos/os/include
CFLAGS += -I$(GENODE_DIR)/repos/base/include
CFLAGS += -I$(GENODE_DIR)/repos/base/include/spec/64bit
CFLAGS += -I$(GENODE_DIR)/repos/base/include/spec/x86
CFLAGS += -I$(GENODE_DIR)/repos/base/include/spec/x86_64
CFLAGS += $(shell pkg-config --cflags --libs xkbcommon)
$(TARGET): $(SRC_CC) $(SRC_H) Makefile
g++ -o $@ $(SRC_CC) $(CFLAGS)
cleanall clean:
rm -f $(TARGET) *~
.PHONY: cleanall clean

74
tool/xkb2ifcfg/README Normal file
View File

@ -0,0 +1,74 @@
XKB to Genode input_filter configuration
Christian Helmuth
This tool generates Genode input_filter XML configuration files from
X11 XKB configuration. It must be built and run on Linux with
libxkbcommon development packages installed.
For pragmatic (and realistic reasons) this tool assumes a standard
105-keys PC keyboard. This assumption automatically includes US
keyboards with 104 keys. The differences are:
- PC105 has an additional 105th Less Then / Greater Then key <LSGT>
right of Left Shift <LFSH> as additional 105th key
- PC105 has the Backslash key <BKSL> left of Return <RTRN>
xkb2ifcfg supports Shift, AltGr, and Caps Lock modifiers, as well as
localized dead-key/compose sequences. The tool does not support
latched modifiers.
Build
=====
Just execute 'make' and 'xkb2ifcfg' should be built.
Usage
=====
! xkb2ifcfg <command> <layout> <variant> <locale>
!
! Commands
!
! generate generate input_filter config
! dump dump raw XKB keymap
! info simple per-key information
!
! Examples
!
! xkb2ifcfg generate us "" en_US.UTF-8 > en_US.chargen
! xkb2ifcfg info de nodeadkeys de_DE.UTF-8 > de_DE.chargen
All information is printed on standard output while diagnostic
messages are written to standard error.
Valid 'layout' and 'variant' options can be figured out from the
LAYOUTS section in
! man 7 xkeyboard-config
'variant' strings are depicted in parentheses after the layout (e.g.,
'us(euro)').
The 'locale' option has the standard locale syntax (see
/usr/share/i18n/locales).
Generation of keyboard-layout PDFs (and other examples)
=======================================================
setxkbmap -print -rules evdev -model pc104 -layout us -variant "" -option "" | xkbcomp -xkm - - | xkbprint -color -label name - - | ps2pdf - t.pdf
setxkbmap -print -rules evdev -model pc105 -layout de -variant "nodeadkeys" -option "" | xkbcomp -xkm - - | xkbprint -color -label symbols - - | ps2pdf - t.pdf
setxkbmap -print -model pc104 -option -layout us
setxkbmap -print -model pc105 -option -layout de -variant nodeadkeys
setxkbmap -print -model pc105 -option -layout ch -variant de_nodeadkeys
setxkbmap -print -model pc105 -option -layout ch -variant fr_nodeadkeys
setxkbmap -print -model pc105 -option -layout gb
setxkbmap -print -model pc104 -option -layout us -variant euro
setxkbmap -print -model pc104 -option -layout us -variant workman

43
tool/xkb2ifcfg/genode.cc Normal file
View File

@ -0,0 +1,43 @@
/*
* \brief Genode utility support
* \author Christian Helmuth <christian.helmuth@genode-labs.com>
* \date 2019-07-25
*/
/*
* Copyright (C) 2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <base/console.h>
#include "util.h"
void Genode::Console::_out_string(char const *str)
{
if (!str)
_out_string("<NULL>");
else
while (*str) _out_char(*str++);
}
void Genode::Console::printf(const char *format, ...)
{
va_list list;
va_start(list, format);
vprintf(format, list);
va_end(list);
}
void Genode::Console::vprintf(const char *format, va_list list)
{
Formatted str(format, list);
_out_string(str.string());
}

842
tool/xkb2ifcfg/main.cc Normal file
View File

@ -0,0 +1,842 @@
/*
* \brief Libxkbcommon-based keyboard-layout generator
* \author Christian Helmuth
* \date 2019-07-16
*/
/*
* Copyright (C) 2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Linux includes */
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <xkbcommon/xkbcommon-compose.h>
#include <set>
#include <vector>
#include <stdexcept>
/* Genode includes */
#include <util/xml_generator.h>
#include <util/retry.h>
#include <util/reconstructible.h>
#include "xkb_mapping.h"
#include "util.h"
using Genode::Xml_generator;
using Genode::Constructible;
static void append_comment(Xml_generator &xml, char const *prefix,
char const *comment, char const *suffix)
{
xml.append(prefix);
xml.append("<!-- "); xml.append(comment); xml.append(" -->");
xml.append(suffix);
}
/*
* XML generator that expands to your needs
*/
class Expanding_xml_buffer
{
private:
char const *_name;
enum { BUFFER_INCREMENT = 1024*1024 };
size_t _buffer_size { 0 };
char *_buffer { nullptr };
void _increase_buffer()
{
::free(_buffer);
_buffer_size += BUFFER_INCREMENT;
_buffer = (char *)::calloc(1, _buffer_size);
}
public:
Expanding_xml_buffer() { _increase_buffer(); }
~Expanding_xml_buffer() { ::free(_buffer); }
char const *buffer() const { return _buffer; }
template <typename FUNC>
void generate(char const *name, FUNC const &func)
{
Genode::retry<Xml_generator::Buffer_exceeded>(
[&] () {
Xml_generator xml(_buffer, _buffer_size,
name, [&] () { func(xml); });
},
[&] () { _increase_buffer(); }
);
}
};
static bool keysym_composing(xkb_compose_state *compose_state, xkb_keysym_t sym)
{
xkb_compose_state_reset(compose_state);
xkb_compose_state_feed(compose_state, sym);
return (XKB_COMPOSE_COMPOSING == xkb_compose_state_get_status(compose_state));
}
struct Key_info
{
enum { COMMENT_CAPACITY = 100 };
xkb_keysym_t _keysym { XKB_KEY_NoSymbol };
bool _composing { false };
unsigned _utf32 { 0 };
char *_comment { (char *)::calloc(1, COMMENT_CAPACITY) };
Key_info(xkb_state *state, xkb_compose_state *compose_state, Input::Keycode code)
{
_keysym = xkb_state_key_get_one_sym(state, Xkb::keycode(code));
if (_keysym == XKB_KEY_NoSymbol) return;
_composing = keysym_composing(compose_state, _keysym);
if (!_composing) {
_utf32 = xkb_state_key_get_utf32(state, Xkb::keycode(code));
xkb_state_key_get_utf8(state, Xkb::keycode(code), _comment, COMMENT_CAPACITY);
} else {
char keysym_str[32];
xkb_keysym_get_name(_keysym, keysym_str, sizeof(keysym_str));
for (Xkb::Dead_keysym &d : Xkb::dead_keysym) {
if (d.xkb != _keysym) continue;
_utf32 = d.utf32;
strncat(_comment, keysym_str, COMMENT_CAPACITY - 1);
return;
}
::fprintf(stderr, "no UTF32 mapping found for composing keysym <%s>\n", keysym_str);
}
}
~Key_info()
{
::free(_comment);
}
bool valid() const { return _utf32 != 0; }
xkb_keysym_t keysym() const { return _keysym; }
bool composing() const { return _composing; }
unsigned utf32() const { return _utf32; }
void attributes(Xml_generator &xml)
{
xml.attribute("code", Formatted("0x%04x", _utf32).string());
}
void comment(Xml_generator &xml)
{
append_comment(xml, "\t", _comment, "");
}
};
struct Keysym
{
bool composing { 0 };
xkb_keysym_t keysym { 0 };
unsigned utf32 { 0 };
};
static bool operator < (Keysym const &a, Keysym const &b)
{
return a.keysym < b.keysym;
}
template <Input::Keycode code>
struct Locked
{
xkb_state *state;
Locked(xkb_state *state) : state(state)
{
xkb_state_update_key(state, Xkb::keycode(code), XKB_KEY_DOWN);
xkb_state_update_key(state, Xkb::keycode(code), XKB_KEY_UP);
}
~Locked()
{
xkb_state_update_key(state, Xkb::keycode(code), XKB_KEY_DOWN);
xkb_state_update_key(state, Xkb::keycode(code), XKB_KEY_UP);
}
};
template <Input::Keycode code>
struct Pressed
{
xkb_state *state;
Pressed(xkb_state *state) : state(state)
{
xkb_state_update_key(state, Xkb::keycode(code), XKB_KEY_DOWN);
}
~Pressed()
{
xkb_state_update_key(state, Xkb::keycode(code), XKB_KEY_UP);
}
};
struct Args
{
struct Invalid_args { };
enum class Command { GENERATE, DUMP, INFO };
Command command;
char const *layout;
char const *variant;
char const *locale;
char const *usage =
"usage: xkb2ifcfg <command> <layout> <variant> <locale>\n"
"\n"
" Commands\n"
"\n"
" generate generate input_filter config\n"
" dump dump raw XKB keymap\n"
" info simple per-key information\n"
"\n"
" Example\n"
"\n"
" xkb2ifcfg generate us '' en_US.UTF-8\n"
" xkb2ifcfg info de nodeadkeys de_DE.UTF-8\n";
Args(int argc, char **argv)
try {
if (argc != 5) throw Invalid_args();
if (!::strcmp("generate", argv[1])) command = Command::GENERATE;
else if (!::strcmp("dump", argv[1])) command = Command::DUMP;
else if (!::strcmp("info", argv[1])) command = Command::INFO;
else throw Invalid_args();
layout = argv[2];
variant = argv[3];
locale = argv[4];
if (!strlen(layout) || !strlen(locale))
throw Invalid_args();
} catch (...) { ::fputs(usage, stderr); throw; }
};
class Main
{
private:
struct Map;
struct Sequence;
Args args;
xkb_context *_context;
xkb_rule_names _rmlvo;
xkb_keymap *_keymap;
xkb_state *_state;
xkb_compose_table *_compose_table;
xkb_compose_state *_compose_state;
std::set<Keysym> _keysyms;
/*
* Numpad keys are remapped in input_filter if numlock=off, so we
* always assume numlock=on to handle KP1 etc. correctly.
*/
Constructible<Locked<Input::KEY_NUMLOCK>> _numlock;
/* utilities */
char const * _string(enum xkb_compose_status);
char const * _string(enum xkb_compose_feed_result);
void _keycode_info(xkb_keycode_t);
void _keycode_xml_non_printable(Xml_generator &, xkb_keycode_t);
void _keycode_xml_control(Xml_generator &, xkb_keycode_t);
void _keycode_xml_printable(Xml_generator &, xkb_keycode_t);
void _keycode_xml_printable_shift(Xml_generator &, xkb_keycode_t);
void _keycode_xml_printable_altgr(Xml_generator &, xkb_keycode_t);
void _keycode_xml_printable_capslock(Xml_generator &, xkb_keycode_t);
void _keycode_xml_printable_shift_altgr(Xml_generator &, xkb_keycode_t);
void _keycode_xml_printable_shift_capslock(Xml_generator &, xkb_keycode_t);
void _keycode_xml_printable_altgr_capslock(Xml_generator &, xkb_keycode_t);
void _keycode_xml_printable_shift_altgr_capslock(Xml_generator &, xkb_keycode_t);
int _generate();
int _dump();
int _info();
public:
Main(int argc, char **argv);
~Main();
xkb_keymap * keymap() { return _keymap; }
int exec();
};
struct Main::Map
{
Main &main;
Xml_generator &xml;
enum class Mod : unsigned {
NONE = 0,
SHIFT = 0b0001, /* mod1 */
CONTROL = 0b0010, /* mod2 */
ALTGR = 0b0100, /* mod3 */
CAPSLOCK = 0b1000, /* mod4 */
SHIFT_ALTGR = SHIFT | ALTGR,
SHIFT_CAPSLOCK = SHIFT | CAPSLOCK,
ALTGR_CAPSLOCK = ALTGR | CAPSLOCK,
SHIFT_ALTGR_CAPSLOCK = SHIFT | ALTGR | CAPSLOCK,
} mod;
static char const * _string(Mod mod)
{
switch (mod) {
case Map::Mod::NONE: return "no modifier";
case Map::Mod::SHIFT: return "SHIFT";
case Map::Mod::CONTROL: return "CONTROL";
case Map::Mod::ALTGR: return "ALTGR";
case Map::Mod::CAPSLOCK: return "CAPSLOCK";
case Map::Mod::SHIFT_ALTGR: return "SHIFT-ALTGR";
case Map::Mod::SHIFT_CAPSLOCK: return "SHIFT-CAPSLOCK";
case Map::Mod::ALTGR_CAPSLOCK: return "ALTGR-CAPSLOCK";
case Map::Mod::SHIFT_ALTGR_CAPSLOCK: return "SHIFT-ALTGR-CAPSLOCK";
}
return "invalid";
}
static void _non_printable(xkb_keymap *, xkb_keycode_t keycode, void *data)
{
Map &m = *reinterpret_cast<Map *>(data);
if (m.mod != Map::Mod::NONE) {
::fprintf(stderr, "%s: mod=%u not supported\n",
__PRETTY_FUNCTION__, unsigned(m.mod));
return;
}
m.main._keycode_xml_non_printable(m.xml, keycode);
}
static void _control(xkb_keymap *, xkb_keycode_t keycode, void *data)
{
Map &m = *reinterpret_cast<Map *>(data);
if (m.mod != Map::Mod::CONTROL) {
::fprintf(stderr, "%s: mod=%u not supported\n",
__PRETTY_FUNCTION__, unsigned(m.mod));
return;
}
m.main._keycode_xml_control(m.xml, keycode);
}
static void _printable(xkb_keymap *, xkb_keycode_t keycode, void *data)
{
Map &m = *reinterpret_cast<Map *>(data);
switch (m.mod) {
case Map::Mod::NONE:
m.main._keycode_xml_printable(m.xml, keycode); break;
case Map::Mod::SHIFT:
m.main._keycode_xml_printable_shift(m.xml, keycode); break;
case Map::Mod::CONTROL:
/* not printable */ break;
case Map::Mod::ALTGR:
m.main._keycode_xml_printable_altgr(m.xml, keycode); break;
case Map::Mod::CAPSLOCK:
m.main._keycode_xml_printable_capslock(m.xml, keycode); break;
case Map::Mod::SHIFT_ALTGR:
m.main._keycode_xml_printable_shift_altgr(m.xml, keycode); break;
case Map::Mod::SHIFT_CAPSLOCK:
m.main._keycode_xml_printable_shift_capslock(m.xml, keycode); break;
case Map::Mod::ALTGR_CAPSLOCK:
m.main._keycode_xml_printable_altgr_capslock(m.xml, keycode); break;
case Map::Mod::SHIFT_ALTGR_CAPSLOCK:
m.main._keycode_xml_printable_shift_altgr_capslock(m.xml, keycode); break;
}
}
Map(Main &main, Xml_generator &xml, Mod mod)
:
main(main), xml(xml), mod(mod)
{
if (mod == Mod::NONE) {
/* generate basic character map */
xml.node("map", [&] ()
{
append_comment(xml, "\n\t\t", "printable", "");
xkb_keymap_key_for_each(main.keymap(), _printable, this);
append_comment(xml, "\n\n\t\t", "non-printable", "");
xkb_keymap_key_for_each(main.keymap(), _non_printable, this);
/* FIXME xml.append() as last operation breaks indentation */
xml.node("dummy", [] () {});
});
} else if (mod == Mod::CONTROL) {
/* generate control character map */
append_comment(xml, "\n\n\t", _string(mod), "");
xml.node("map", [&] ()
{
xml.attribute("mod2", true);
xkb_keymap_key_for_each(main.keymap(), _control, this);
/* FIXME xml.append() as last operation breaks indentation */
xml.node("dummy", [] () {});
});
} else {
/* generate characters depending on modifier state */
append_comment(xml, "\n\n\t", _string(mod), "");
xml.node("map", [&] ()
{
xml.attribute("mod1", (bool)(unsigned(mod) & unsigned(Mod::SHIFT)));
xml.attribute("mod2", false);
xml.attribute("mod3", (bool)(unsigned(mod) & unsigned(Mod::ALTGR)));
xml.attribute("mod4", (bool)(unsigned(mod) & unsigned(Mod::CAPSLOCK)));
xkb_keymap_key_for_each(main.keymap(), _printable, this);
/* FIXME xml.append() as last operation breaks indentation */
xml.node("dummy", [] () {});
});
}
}
};
struct Main::Sequence
{
struct Guard
{
std::vector<Keysym> &seq;
Guard(std::vector<Keysym> &seq, Keysym keysym) : seq(seq) { seq.push_back(keysym); }
~Guard() { seq.pop_back(); }
};
Main &_main;
Xml_generator &_xml;
xkb_compose_state *_state { xkb_compose_state_ref(_main._compose_state) };
void _generate(std::vector<Keysym> &seq, Keysym keysym)
{
Guard g(seq, keysym);
if (seq.size() > 4) {
::fprintf(stderr, "dead-key / compose sequence too long (max=4)\n");
return;
}
xkb_compose_state_reset(_state);
for (Keysym k : seq) xkb_compose_state_feed(_state, k.keysym);
switch (xkb_compose_state_get_status(_state)) {
case XKB_COMPOSE_COMPOSED: {
xkb_keysym_t result = xkb_compose_state_get_one_sym(_state);
unsigned utf32 = xkb_keysym_to_utf32(result);
if (utf32 == 0) {
::fprintf(stderr, "skipping sequence");
for (Keysym k : seq) fprintf(stderr, " U+%05x", k.utf32);
::fprintf(stderr, " generating U+%04x\n", utf32);
break;
}
_xml.node("sequence", [&] ()
{
try {
_xml.attribute("first", Formatted("0x%04x", seq.at(0).utf32).string());
_xml.attribute("second", Formatted("0x%04x", seq.at(1).utf32).string());
_xml.attribute("third", Formatted("0x%04x", seq.at(2).utf32).string());
_xml.attribute("fourth", Formatted("0x%04x", seq.at(3).utf32).string());
} catch (std::out_of_range) { }
_xml.attribute("code", Formatted("0x%04x", utf32).string());
});
char comment[32];
xkb_keysym_to_utf8(result, comment, sizeof(comment));
append_comment(_xml, "\t", comment, "");
} break;
case XKB_COMPOSE_COMPOSING:
for (Keysym k : _main._keysyms) {
_generate(seq, k);
}
break;
case XKB_COMPOSE_CANCELLED:
case XKB_COMPOSE_NOTHING:
break;
}
}
Sequence(Main &main, Xml_generator &xml)
:
_main(main), _xml(xml)
{
append_comment(_xml, "\n\n\t", "dead-key / compose sequences", "");
for (Keysym k : _main._keysyms) {
/* first must be a dead/composing keysym */
if (!k.composing) continue;
std::vector<Keysym> seq;
_generate(seq, k);
}
/* FIXME xml.append() as last operation breaks indentation */
xml.node("dummy", [] () {});
}
~Sequence() { xkb_compose_state_unref(_state); }
};
char const * Main::_string(enum xkb_compose_status status)
{
switch (status) {
case XKB_COMPOSE_NOTHING: return "XKB_COMPOSE_NOTHING";
case XKB_COMPOSE_COMPOSING: return "XKB_COMPOSE_COMPOSING";
case XKB_COMPOSE_COMPOSED: return "XKB_COMPOSE_COMPOSED";
case XKB_COMPOSE_CANCELLED: return "XKB_COMPOSE_CANCELLED";
}
return "invalid";
}
char const * Main::_string(enum xkb_compose_feed_result result)
{
switch (result) {
case XKB_COMPOSE_FEED_IGNORED: return "XKB_COMPOSE_FEED_IGNORED";
case XKB_COMPOSE_FEED_ACCEPTED: return "XKB_COMPOSE_FEED_ACCEPTED";
}
return "invalid";
}
void Main::_keycode_info(xkb_keycode_t keycode)
{
for (Xkb::Mapping &m : Xkb::printable) {
if (m.xkb != keycode) continue;
::printf("keycode %3u:", m.xkb);
::printf(" %-8s", m.xkb_name);
::printf(" %-16s", Input::key_name(m.code));
unsigned const num_levels = xkb_keymap_num_levels_for_key(_keymap, m.xkb, 0);
::printf("\t%u levels { ", num_levels);
for (unsigned l = 0; l < num_levels; ++l) {
::printf(" %u:", l);
xkb_keysym_t const *syms = nullptr;
unsigned const num_syms = xkb_keymap_key_get_syms_by_level(_keymap, m.xkb, 0, l, &syms);
for (unsigned s = 0; s < num_syms; ++s) {
char buffer[7] = { 0, };
xkb_keysym_to_utf8(syms[s], buffer, sizeof(buffer));
::printf(" %x %s", syms[s], keysym_composing(_compose_state, syms[s])
? "COMPOSING!" : buffer);
}
}
::printf(" }");
::printf("\n");
return;
}
}
void Main::_keycode_xml_non_printable(Xml_generator &xml, xkb_keycode_t keycode)
{
/* non-printable symbols with chargen entry (e.g., ENTER) */
for (Xkb::Mapping &m : Xkb::non_printable) {
if (m.xkb != keycode) continue;
xml.node("key", [&] ()
{
xml.attribute("name", Input::key_name(m.code));
xml.attribute("ascii", m.ascii);
});
return;
}
}
void Main::_keycode_xml_control(Xml_generator &xml, xkb_keycode_t keycode)
{
/* chargen entry for control characters (e.g., CTRL-J) */
static char const *desc[] {
"SOH (start of heading) ",
"STX (start of text) ",
"ETX (end of text) ",
"EOT (end of transmission) ",
"ENQ (enquiry) ",
"ACK (acknowledge) ",
"BEL '\\a' (bell) ",
"BS '\\b' (backspace) ",
"HT '\\t' (horizontal tab) ",
"LF '\\n' (new line) ",
"VT '\\v' (vertical tab) ",
"FF '\\f' (form feed) ",
"CR '\\r' (carriage ret) ",
"SO (shift out) ",
"SI (shift in) ",
"DLE (data link escape) ",
"DC1 (device control 1) ",
"DC2 (device control 2) ",
"DC3 (device control 3) ",
"DC4 (device control 4) ",
"NAK (negative ack.) ",
"SYN (synchronous idle) ",
"ETB (end of trans. blk) ",
"CAN (cancel) ",
"EM (end of medium) ",
"SUB (substitute) ",
"ESC (escape) ",
"FS (file separator) ",
"GS (group separator) ",
"RS (record separator) ",
"US (unit separator) ",
};
Pressed<Input::KEY_LEFTCTRL> control(_state);
for (Xkb::Mapping &m : Xkb::printable) {
if (m.xkb != keycode) continue;
xkb_keysym_t const keysym = xkb_state_key_get_one_sym(_state, keycode);
if (keysym == XKB_KEY_NoSymbol) return;
unsigned const utf32 = xkb_state_key_get_utf32(_state, m.xkb);
if (!utf32 || utf32 > 0x1f) return;
char keysym_str[32];
xkb_keysym_get_name(keysym, keysym_str, sizeof(keysym_str));
xml.node("key", [&] ()
{
xml.attribute("name", Input::key_name(m.code));
xml.attribute("code", Formatted("0x%04x", utf32).string());
});
append_comment(xml, "\t",
Formatted("%s CTRL-%s", desc[utf32-1], keysym_str).string(),
"");
return;
}
}
void Main::_keycode_xml_printable(Xml_generator &xml, xkb_keycode_t keycode)
{
for (Xkb::Mapping &m : Xkb::printable) {
if (m.xkb != keycode) continue;
Key_info key_info(_state, _compose_state, m.code);
if (!key_info.valid()) break;
xml.node("key", [&] ()
{
xml.attribute("name", Input::key_name(m.code));
key_info.attributes(xml);
});
key_info.comment(xml);
Keysym keysym { key_info.composing(), key_info.keysym(), key_info.utf32() };
_keysyms.insert(keysym);
return;
}
}
void Main::_keycode_xml_printable_shift(Xml_generator &xml, xkb_keycode_t keycode)
{
Pressed<Input::KEY_LEFTSHIFT> shift(_state);
_keycode_xml_printable(xml, keycode);
}
void Main::_keycode_xml_printable_altgr(Xml_generator &xml, xkb_keycode_t keycode)
{
Pressed<Input::KEY_RIGHTALT> altgr(_state);
_keycode_xml_printable(xml, keycode);
}
void Main::_keycode_xml_printable_capslock(Xml_generator &xml, xkb_keycode_t keycode)
{
Locked<Input::KEY_CAPSLOCK> capslock(_state);
_keycode_xml_printable(xml, keycode);
}
void Main::_keycode_xml_printable_shift_altgr(Xml_generator &xml, xkb_keycode_t keycode)
{
Pressed<Input::KEY_LEFTSHIFT> shift(_state);
Pressed<Input::KEY_RIGHTALT> altgr(_state);
_keycode_xml_printable(xml, keycode);
}
void Main::_keycode_xml_printable_shift_capslock(Xml_generator &xml, xkb_keycode_t keycode)
{
Locked<Input::KEY_CAPSLOCK> capslock(_state);
Pressed<Input::KEY_LEFTSHIFT> shift(_state);
_keycode_xml_printable(xml, keycode);
}
void Main::_keycode_xml_printable_altgr_capslock(Xml_generator &xml, xkb_keycode_t keycode)
{
Locked<Input::KEY_CAPSLOCK> capslock(_state);
Pressed<Input::KEY_RIGHTALT> altgr(_state);
_keycode_xml_printable(xml, keycode);
}
void Main::_keycode_xml_printable_shift_altgr_capslock(Xml_generator &xml, xkb_keycode_t keycode)
{
Locked<Input::KEY_CAPSLOCK> capslock(_state);
Pressed<Input::KEY_LEFTSHIFT> shift(_state);
Pressed<Input::KEY_RIGHTALT> altgr(_state);
_keycode_xml_printable(xml, keycode);
}
int Main::_generate()
{
::printf("<!-- %s/%s/%s chargen configuration generated by xkb2ifcfg -->\n",
args.layout, args.variant, args.locale);
Expanding_xml_buffer xml_buffer;
auto generate_xml = [&] (Xml_generator &xml)
{
{ Map map { *this, xml, Map::Mod::NONE }; }
{ Map map { *this, xml, Map::Mod::SHIFT }; }
{ Map map { *this, xml, Map::Mod::CONTROL }; }
{ Map map { *this, xml, Map::Mod::ALTGR }; }
{ Map map { *this, xml, Map::Mod::CAPSLOCK }; }
{ Map map { *this, xml, Map::Mod::SHIFT_ALTGR }; }
{ Map map { *this, xml, Map::Mod::SHIFT_CAPSLOCK }; }
{ Map map { *this, xml, Map::Mod::ALTGR_CAPSLOCK }; }
{ Map map { *this, xml, Map::Mod::SHIFT_ALTGR_CAPSLOCK }; }
{ Sequence sequence { *this, xml }; }
};
xml_buffer.generate("chargen", generate_xml);
::puts(xml_buffer.buffer());
return 0;
}
int Main::_dump()
{
::printf("Dump of XKB keymap for %s/%s/%s by xkb2ifcfg\n",
args.layout, args.variant, args.locale);
::puts(xkb_keymap_get_as_string(_keymap, XKB_KEYMAP_FORMAT_TEXT_V1));
return 0;
}
int Main::_info()
{
::printf("Simple per-key info for %s/%s/%s by xkb2ifcfg\n",
args.layout, args.variant, args.locale);
auto lambda = [] (xkb_keymap *, xkb_keycode_t keycode, void *data)
{
reinterpret_cast<Main *>(data)->_keycode_info(keycode);
};
xkb_keymap_key_for_each(_keymap, lambda, this);
return 0;
}
int Main::exec()
{
switch (args.command) {
case Args::Command::GENERATE: return _generate();
case Args::Command::DUMP: return _dump();
case Args::Command::INFO: return _info();
}
return -1;
}
Main::Main(int argc, char **argv) : args(argc, argv)
{
/* TODO error handling */
_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
_rmlvo = { "evdev", "pc105", args.layout, args.variant, "" };
_keymap = xkb_keymap_new_from_names(_context, &_rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
_state = xkb_state_new(_keymap);
_compose_table = xkb_compose_table_new_from_locale(_context, args.locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
_compose_state = xkb_compose_state_new(_compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
_numlock.construct(_state);
}
Main::~Main()
{
_numlock.destruct();
xkb_compose_state_unref(_compose_state);
xkb_compose_table_unref(_compose_table);
xkb_state_unref(_state);
xkb_keymap_unref(_keymap);
xkb_context_unref(_context);
}
int main(int argc, char **argv)
{
try {
static Main m(argc, argv);
return m.exec();
} catch (...) { return -1; }
}

48
tool/xkb2ifcfg/util.h Normal file
View File

@ -0,0 +1,48 @@
/*
* \brief Libxkbcommon-based keyboard-layout generator
* \author Christian Helmuth
* \date 2019-08-16
*/
/*
* Copyright (C) 2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _UTIL_H_
#define _UTIL_H_
/* Linux includes */
#include <cstdio>
#include <cstdlib>
struct Formatted
{
char *_string;
Formatted(char const *format, va_list list)
{
::vasprintf(&_string, format, list);
}
Formatted(char const *format, ...)
{
va_list list;
va_start(list, format);
::vasprintf(&_string, format, list);
va_end(list);
}
~Formatted()
{
::free(_string);
}
char const * string() const { return _string; }
};
#endif /* _UTIL_H_ */

View File

@ -0,0 +1,167 @@
/*
* \brief Libxkbcommon-based keyboard-layout generator
* \author Christian Helmuth
* \date 2019-07-16
*/
/*
* Copyright (C) 2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _XKB_MAPPING_H_
#define _XKB_MAPPING_H_
/* Linux includes */
#include <xkbcommon/xkbcommon.h>
/* Genode includes */
#include <input/keycodes.h>
namespace Xkb {
/*
* It's a documented fact that 'xkb keycode == evdev keycode + 8'
*/
inline xkb_keycode_t keycode(Input::Keycode code)
{
return xkb_keycode_t(unsigned(code) + 8);
}
/*
* Lookup table for keys eventually generating characters
*/
struct Mapping
{
xkb_keycode_t xkb;
char const xkb_name[7];
Input::Keycode code;
char const ascii { 0 }; /* predefined non-printable */
};
Mapping printable[] = {
{ 10, "<AE01>", Input::KEY_1 },
{ 11, "<AE02>", Input::KEY_2 },
{ 12, "<AE03>", Input::KEY_3 },
{ 13, "<AE04>", Input::KEY_4 },
{ 14, "<AE05>", Input::KEY_5 },
{ 15, "<AE06>", Input::KEY_6 },
{ 16, "<AE07>", Input::KEY_7 },
{ 17, "<AE08>", Input::KEY_8 },
{ 18, "<AE09>", Input::KEY_9 },
{ 19, "<AE10>", Input::KEY_0 },
{ 20, "<AE11>", Input::KEY_MINUS },
{ 21, "<AE12>", Input::KEY_EQUAL },
{ 24, "<AD01>", Input::KEY_Q },
{ 25, "<AD02>", Input::KEY_W },
{ 26, "<AD03>", Input::KEY_E },
{ 27, "<AD04>", Input::KEY_R },
{ 28, "<AD05>", Input::KEY_T },
{ 29, "<AD06>", Input::KEY_Y },
{ 30, "<AD07>", Input::KEY_U },
{ 31, "<AD08>", Input::KEY_I },
{ 32, "<AD09>", Input::KEY_O },
{ 33, "<AD10>", Input::KEY_P },
{ 34, "<AD11>", Input::KEY_LEFTBRACE },
{ 35, "<AD12>", Input::KEY_RIGHTBRACE },
{ 38, "<AC01>", Input::KEY_A },
{ 39, "<AC02>", Input::KEY_S },
{ 40, "<AC03>", Input::KEY_D },
{ 41, "<AC04>", Input::KEY_F },
{ 42, "<AC05>", Input::KEY_G },
{ 43, "<AC06>", Input::KEY_H },
{ 44, "<AC07>", Input::KEY_J },
{ 45, "<AC08>", Input::KEY_K },
{ 46, "<AC09>", Input::KEY_L },
{ 47, "<AC11>", Input::KEY_SEMICOLON },
{ 48, "<AC12>", Input::KEY_APOSTROPHE },
{ 49, "<TLDE>", Input::KEY_GRAVE }, /* left of "1" <AE01> */
{ 51, "<BKSL>", Input::KEY_BACKSLASH }, /* left of <RTRN> (pc105) / above <RTRN> (pc104) */
{ 52, "<AB01>", Input::KEY_Z },
{ 53, "<AB02>", Input::KEY_X },
{ 54, "<AB03>", Input::KEY_C },
{ 55, "<AB04>", Input::KEY_V },
{ 56, "<AB05>", Input::KEY_B },
{ 57, "<AB06>", Input::KEY_N },
{ 58, "<AB07>", Input::KEY_M },
{ 59, "<AB08>", Input::KEY_COMMA },
{ 60, "<AB09>", Input::KEY_DOT },
{ 61, "<AB10>", Input::KEY_SLASH },
{ 65, "<SPCE>", Input::KEY_SPACE },
{ 94, "<LSGT>", Input::KEY_102ND }, /* right of <LFSH> (pc105) */
{ 63, "<KPMU>", Input::KEY_KPASTERISK },
{ 79, "<KP7>", Input::KEY_KP7 },
{ 80, "<KP8>", Input::KEY_KP8 },
{ 81, "<KP9>", Input::KEY_KP9 },
{ 82, "<KPSU>", Input::KEY_KPMINUS },
{ 83, "<KP4>", Input::KEY_KP4 },
{ 84, "<KP5>", Input::KEY_KP5 },
{ 85, "<KP6>", Input::KEY_KP6 },
{ 86, "<KPAD>", Input::KEY_KPPLUS },
{ 87, "<KP1>", Input::KEY_KP1 },
{ 88, "<KP2>", Input::KEY_KP2 },
{ 89, "<KP3>", Input::KEY_KP3 },
{ 90, "<KP0>", Input::KEY_KP0 },
{ 91, "<KPDL>", Input::KEY_KPDOT },
{ 106, "<KPDV>", Input::KEY_KPSLASH },
};
Mapping non_printable[] = {
{ 9, "<ESC>", Input::KEY_ESC, 27 },
{ 22, "<BKSP>", Input::KEY_BACKSPACE, 8 },
{ 23, "<TAB>", Input::KEY_TAB, 9 },
{ 36, "<RTRN>", Input::KEY_ENTER, 10 }, /* XXX we use newline not carriage return as X11 */
{ 104, "<KPEN>", Input::KEY_KPENTER, 10 }, /* XXX we use newline not carriage return as X11 */
{ 119, "<DELE>", Input::KEY_DELETE, 127 },
};
struct Dead_keysym
{
xkb_keysym_t xkb;
unsigned utf32;
} dead_keysym[] = {
{ XKB_KEY_dead_grave, 0x0300 },
{ XKB_KEY_dead_acute, 0x0301 },
{ XKB_KEY_dead_circumflex, 0x0302 },
{ XKB_KEY_dead_tilde, 0x0303 }, /* aliases: dead_perispomeni */
{ XKB_KEY_dead_macron, 0x0304 },
{ XKB_KEY_dead_breve, 0x0306 },
{ XKB_KEY_dead_abovedot, 0x0307 },
{ XKB_KEY_dead_diaeresis, 0x0308 },
{ XKB_KEY_dead_hook, 0x0309 },
{ XKB_KEY_dead_abovering, 0x030A },
{ XKB_KEY_dead_doubleacute, 0x030B },
{ XKB_KEY_dead_caron, 0x030C },
{ XKB_KEY_dead_doublegrave, 0x030F },
{ XKB_KEY_dead_invertedbreve, 0x0311 },
{ XKB_KEY_dead_abovecomma, 0x0313 }, /* aliases: dead_psili */
{ XKB_KEY_dead_abovereversedcomma, 0x0314 }, /* aliases: dead_dasia */
{ XKB_KEY_dead_horn, 0x031B },
{ XKB_KEY_dead_belowdot, 0x0323 },
{ XKB_KEY_dead_belowdiaeresis, 0x0324 },
{ XKB_KEY_dead_belowring, 0x0325 },
{ XKB_KEY_dead_belowcomma, 0x0326 },
{ XKB_KEY_dead_cedilla, 0x0327 },
{ XKB_KEY_dead_ogonek, 0x0328 },
{ XKB_KEY_dead_belowcircumflex, 0x032d },
{ XKB_KEY_dead_belowtilde, 0x0330 },
{ XKB_KEY_dead_belowmacron, 0x0331 },
{ XKB_KEY_dead_stroke, 0x0338 },
{ XKB_KEY_dead_belowbreve, 0x032E },
{ XKB_KEY_dead_iota, 0x0345 }, /* aliases: GREEK YPOGEGRAMMENI */
// { XKB_KEY_dead_voiced_sound, 0x03 },
// { XKB_KEY_dead_semivoiced_sound, 0x03 },
// { XKB_KEY_dead_currency, 0x03 },
};
}
#endif /* _XKB_MAPPING_H_ */