From 43a8118d2ef1d6920c9dd0ea28d4a161fbdaf174 Mon Sep 17 00:00:00 2001 From: Christian Helmuth Date: Fri, 23 Aug 2019 15:58:23 +0200 Subject: [PATCH] tool: generate input_filter config from XKB Issue #3483 --- tool/xkb2ifcfg/.gitignore | 1 + tool/xkb2ifcfg/Makefile | 36 ++ tool/xkb2ifcfg/README | 74 +++ tool/xkb2ifcfg/genode.cc | 43 ++ tool/xkb2ifcfg/main.cc | 842 +++++++++++++++++++++++++++++++++++ tool/xkb2ifcfg/util.h | 48 ++ tool/xkb2ifcfg/xkb_mapping.h | 167 +++++++ 7 files changed, 1211 insertions(+) create mode 100644 tool/xkb2ifcfg/.gitignore create mode 100644 tool/xkb2ifcfg/Makefile create mode 100644 tool/xkb2ifcfg/README create mode 100644 tool/xkb2ifcfg/genode.cc create mode 100644 tool/xkb2ifcfg/main.cc create mode 100644 tool/xkb2ifcfg/util.h create mode 100644 tool/xkb2ifcfg/xkb_mapping.h diff --git a/tool/xkb2ifcfg/.gitignore b/tool/xkb2ifcfg/.gitignore new file mode 100644 index 000000000..8925956fd --- /dev/null +++ b/tool/xkb2ifcfg/.gitignore @@ -0,0 +1 @@ +/xkb2ifcfg diff --git a/tool/xkb2ifcfg/Makefile b/tool/xkb2ifcfg/Makefile new file mode 100644 index 000000000..96bef91e3 --- /dev/null +++ b/tool/xkb2ifcfg/Makefile @@ -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 diff --git a/tool/xkb2ifcfg/README b/tool/xkb2ifcfg/README new file mode 100644 index 000000000..30a20eceb --- /dev/null +++ b/tool/xkb2ifcfg/README @@ -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 + right of Left Shift as additional 105th key +- PC105 has the Backslash key left of Return + +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 +! +! 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 + diff --git a/tool/xkb2ifcfg/genode.cc b/tool/xkb2ifcfg/genode.cc new file mode 100644 index 000000000..3047ec5a9 --- /dev/null +++ b/tool/xkb2ifcfg/genode.cc @@ -0,0 +1,43 @@ +/* + * \brief Genode utility support + * \author Christian Helmuth + * \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 + +#include "util.h" + + +void Genode::Console::_out_string(char const *str) +{ + if (!str) + _out_string(""); + 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()); +} diff --git a/tool/xkb2ifcfg/main.cc b/tool/xkb2ifcfg/main.cc new file mode 100644 index 000000000..045053e7e --- /dev/null +++ b/tool/xkb2ifcfg/main.cc @@ -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 +#include +#include +#include +#include +#include +#include + +/* Genode includes */ +#include +#include +#include + +#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(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 + void generate(char const *name, FUNC const &func) + { + Genode::retry( + [&] () { + 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 +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 +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 \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 _keysyms; + + /* + * Numpad keys are remapped in input_filter if numlock=off, so we + * always assume numlock=on to handle KP1 etc. correctly. + */ + Constructible> _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(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(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(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 &seq; + + Guard(std::vector &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 &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 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 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 shift(_state); + _keycode_xml_printable(xml, keycode); +} + + +void Main::_keycode_xml_printable_altgr(Xml_generator &xml, xkb_keycode_t keycode) +{ + Pressed altgr(_state); + _keycode_xml_printable(xml, keycode); +} + + +void Main::_keycode_xml_printable_capslock(Xml_generator &xml, xkb_keycode_t keycode) +{ + Locked capslock(_state); + _keycode_xml_printable(xml, keycode); +} + + +void Main::_keycode_xml_printable_shift_altgr(Xml_generator &xml, xkb_keycode_t keycode) +{ + Pressed shift(_state); + Pressed altgr(_state); + _keycode_xml_printable(xml, keycode); +} + + +void Main::_keycode_xml_printable_shift_capslock(Xml_generator &xml, xkb_keycode_t keycode) +{ + Locked capslock(_state); + Pressed shift(_state); + _keycode_xml_printable(xml, keycode); +} + + +void Main::_keycode_xml_printable_altgr_capslock(Xml_generator &xml, xkb_keycode_t keycode) +{ + Locked capslock(_state); + Pressed altgr(_state); + _keycode_xml_printable(xml, keycode); +} + + +void Main::_keycode_xml_printable_shift_altgr_capslock(Xml_generator &xml, xkb_keycode_t keycode) +{ + Locked capslock(_state); + Pressed shift(_state); + Pressed altgr(_state); + _keycode_xml_printable(xml, keycode); +} + + +int Main::_generate() +{ + ::printf("\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
(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; } +} diff --git a/tool/xkb2ifcfg/util.h b/tool/xkb2ifcfg/util.h new file mode 100644 index 000000000..2486275cc --- /dev/null +++ b/tool/xkb2ifcfg/util.h @@ -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 +#include + + +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_ */ diff --git a/tool/xkb2ifcfg/xkb_mapping.h b/tool/xkb2ifcfg/xkb_mapping.h new file mode 100644 index 000000000..9d8d0b404 --- /dev/null +++ b/tool/xkb2ifcfg/xkb_mapping.h @@ -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 + +/* Genode includes */ +#include + + +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, "", Input::KEY_1 }, + { 11, "", Input::KEY_2 }, + { 12, "", Input::KEY_3 }, + { 13, "", Input::KEY_4 }, + { 14, "", Input::KEY_5 }, + { 15, "", Input::KEY_6 }, + { 16, "", Input::KEY_7 }, + { 17, "", Input::KEY_8 }, + { 18, "", Input::KEY_9 }, + { 19, "", Input::KEY_0 }, + { 20, "", Input::KEY_MINUS }, + { 21, "", Input::KEY_EQUAL }, + + { 24, "", Input::KEY_Q }, + { 25, "", Input::KEY_W }, + { 26, "", Input::KEY_E }, + { 27, "", Input::KEY_R }, + { 28, "", Input::KEY_T }, + { 29, "", Input::KEY_Y }, + { 30, "", Input::KEY_U }, + { 31, "", Input::KEY_I }, + { 32, "", Input::KEY_O }, + { 33, "", Input::KEY_P }, + { 34, "", Input::KEY_LEFTBRACE }, + { 35, "", Input::KEY_RIGHTBRACE }, + + { 38, "", Input::KEY_A }, + { 39, "", Input::KEY_S }, + { 40, "", Input::KEY_D }, + { 41, "", Input::KEY_F }, + { 42, "", Input::KEY_G }, + { 43, "", Input::KEY_H }, + { 44, "", Input::KEY_J }, + { 45, "", Input::KEY_K }, + { 46, "", Input::KEY_L }, + { 47, "", Input::KEY_SEMICOLON }, + { 48, "", Input::KEY_APOSTROPHE }, + + { 49, "", Input::KEY_GRAVE }, /* left of "1" */ + { 51, "", Input::KEY_BACKSLASH }, /* left of (pc105) / above (pc104) */ + + { 52, "", Input::KEY_Z }, + { 53, "", Input::KEY_X }, + { 54, "", Input::KEY_C }, + { 55, "", Input::KEY_V }, + { 56, "", Input::KEY_B }, + { 57, "", Input::KEY_N }, + { 58, "", Input::KEY_M }, + { 59, "", Input::KEY_COMMA }, + { 60, "", Input::KEY_DOT }, + { 61, "", Input::KEY_SLASH }, + + { 65, "", Input::KEY_SPACE }, + { 94, "", Input::KEY_102ND }, /* right of (pc105) */ + + { 63, "", Input::KEY_KPASTERISK }, + { 79, "", Input::KEY_KP7 }, + { 80, "", Input::KEY_KP8 }, + { 81, "", Input::KEY_KP9 }, + { 82, "", Input::KEY_KPMINUS }, + { 83, "", Input::KEY_KP4 }, + { 84, "", Input::KEY_KP5 }, + { 85, "", Input::KEY_KP6 }, + { 86, "", Input::KEY_KPPLUS }, + { 87, "", Input::KEY_KP1 }, + { 88, "", Input::KEY_KP2 }, + { 89, "", Input::KEY_KP3 }, + { 90, "", Input::KEY_KP0 }, + { 91, "", Input::KEY_KPDOT }, + { 106, "", Input::KEY_KPSLASH }, + }; + + Mapping non_printable[] = { + { 9, "", Input::KEY_ESC, 27 }, + { 22, "", Input::KEY_BACKSPACE, 8 }, + { 23, "", Input::KEY_TAB, 9 }, + { 36, "", Input::KEY_ENTER, 10 }, /* XXX we use newline not carriage return as X11 */ + { 104, "", Input::KEY_KPENTER, 10 }, /* XXX we use newline not carriage return as X11 */ + { 119, "", 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_ */