diff options
Diffstat (limited to 'rules')
-rw-r--r-- | rules/0042-option_symbols.part | 187 | ||||
-rw-r--r-- | rules/0043-option_compat.part | 8 | ||||
-rw-r--r-- | rules/0044-option_types.part | 10 | ||||
-rw-r--r-- | rules/compat/0041-option_symbols.part | 12 | ||||
-rwxr-xr-x | rules/generate-options-symbols.py | 283 | ||||
-rw-r--r-- | rules/meson.build | 45 |
6 files changed, 323 insertions, 222 deletions
diff --git a/rules/0042-option_symbols.part b/rules/0042-option_symbols.part deleted file mode 100644 index d190a632..00000000 --- a/rules/0042-option_symbols.part +++ /dev/null @@ -1,187 +0,0 @@ -! option = symbols - altwin:menu = +altwin(menu) - altwin:menu_win = +altwin(menu_win) - altwin:meta_alt = +altwin(meta_alt) - altwin:alt_win = +altwin(alt_win) - altwin:ctrl_win = +altwin(ctrl_win) - altwin:ctrl_rwin = +altwin(ctrl_rwin) - altwin:ctrl_alt_win = +altwin(ctrl_alt_win) - altwin:meta_win = +altwin(meta_win) - altwin:left_meta_win = +altwin(left_meta_win) - altwin:hyper_win = +altwin(hyper_win) - altwin:alt_super_win = +altwin(alt_super_win) - altwin:swap_lalt_lwin = +altwin(swap_lalt_lwin) - altwin:swap_alt_win = +altwin(swap_alt_win) - altwin:prtsc_rwin = +altwin(prtsc_rwin) - caps:capslock = +capslock(capslock) - caps:numlock = +capslock(numlock) - caps:shiftlock = +capslock(shiftlock) - caps:swapescape = +capslock(swapescape) - caps:escape = +capslock(escape) - caps:escape_shifted_capslock = +capslock(escape_shifted_capslock) - caps:escape_shifted_compose = +capslock(escape_shifted_compose) - caps:backspace = +capslock(backspace) - caps:super = +capslock(super) - caps:hyper = +capslock(hyper) - caps:menu = +capslock(menu) - caps:none = +capslock(none) - caps:ctrl_modifier = +capslock(ctrl_modifier) - ctrl:nocaps = +ctrl(nocaps) - ctrl:lctrl_meta = +ctrl(lctrl_meta) - ctrl:swapcaps = +ctrl(swapcaps) - ctrl:hyper_capscontrol = +ctrl(hyper_capscontrol) - ctrl:grouptoggle_capscontrol = +ctrl(grouptoggle_capscontrol) - ctrl:ac_ctrl = +ctrl(ac_ctrl) - ctrl:aa_ctrl = +ctrl(aa_ctrl) - ctrl:rctrl_ralt = +ctrl(rctrl_ralt) - ctrl:menu_rctrl = +ctrl(menu_rctrl) - ctrl:ralt_rctrl = +ctrl(ralt_rctrl) - ctrl:swap_lalt_lctl = +ctrl(swap_lalt_lctl) - ctrl:swap_ralt_rctl = +ctrl(swap_ralt_rctl) - ctrl:swap_lwin_lctl = +ctrl(swap_lwin_lctl) - ctrl:swap_rwin_rctl = +ctrl(swap_rwin_rctl) - ctrl:swap_lalt_lctl_lwin = +ctrl(swap_lalt_lctl_lwin) - compose:ralt = +compose(ralt) - compose:lwin = +compose(lwin) - compose:lwin-altgr = +compose(lwin-altgr) - compose:rwin = +compose(rwin) - compose:rwin-altgr = +compose(rwin-altgr) - compose:menu = +compose(menu) - compose:menu-altgr = +compose(menu-altgr) - compose:lctrl = +compose(lctrl) - compose:lctrl-altgr = +compose(lctrl-altgr) - compose:rctrl = +compose(rctrl) - compose:rctrl-altgr = +compose(rctrl-altgr) - compose:caps = +compose(caps) - compose:caps-altgr = +compose(caps-altgr) - compose:102 = +compose(102) - compose:102-altgr = +compose(102-altgr) - compose:ins = +compose(ins) - compose:paus = +compose(paus) - compose:prsc = +compose(prsc) - compose:sclk = +compose(sclk) - keypad:oss = +keypad(oss) - keypad:legacy = +keypad(legacy) - keypad:legacy_wang = +keypad(legacy_wang) - keypad:oss_wang = +keypad(oss_wang) - keypad:future = +keypad(future) - keypad:future_wang = +keypad(future_wang) - keypad:hex = +keypad(hex) - keypad:atm = +keypad(atm) - keypad:pointerkeys = +keypad(pointerkeys) - kpdl:dot = +kpdl(dot) - kpdl:comma = +kpdl(comma) - kpdl:dotoss = +kpdl(dotoss) - kpdl:dotoss_latin9 = +kpdl(dotoss_latin9) - kpdl:commaoss = +kpdl(commaoss) - kpdl:momayyezoss = +kpdl(momayyezoss) - kpdl:kposs = +kpdl(kposs) - kpdl:semi = +kpdl(semi) - nbsp:none = +nbsp(none) - nbsp:level2 = +nbsp(level2) - nbsp:level3 = +nbsp(level3) - nbsp:level3n = +nbsp(level3n) - nbsp:level4 = +nbsp(level4) - nbsp:level4n = +nbsp(level4n) - nbsp:level4nl = +nbsp(level4nl) - nbsp:zwnj2 = +nbsp(zwnj2) - nbsp:zwnj2zwj3 = +nbsp(zwnj2zwj3) - nbsp:zwnj2zwj3nb4 = +nbsp(zwnj2zwj3nb4) - nbsp:zwnj2nb3 = +nbsp(zwnj2nb3) - nbsp:zwnj2nb3zwj4 = +nbsp(zwnj2nb3zwj4) - nbsp:zwnj2nb3nnb4 = +nbsp(zwnj2nb3nnb4) - nbsp:zwnj3zwj4 = +nbsp(zwnj3zwj4) - eurosign:e = +eurosign(e) - eurosign:E = +eurosign(E) - eurosign:2 = +eurosign(2) - eurosign:4 = +eurosign(4) - eurosign:5 = +eurosign(5) - rupeesign:4 = +rupeesign(4) - esperanto:qwerty = +epo(qwerty) - esperanto:dvorak = +epo(dvorak) - esperanto:colemak = +epo(colemak) - parens:swap_brackets = +parens(swap_brackets) - japan:nicola_f_bs = +jp(nicola_f_bs) - japan:hztg_escape = +jp(hztg_escape) - korean:ralt_hangul = +kr(ralt_hangul) - korean:rctrl_hangul = +kr(rctrl_hangul) - korean:ralt_hanja = +kr(ralt_hanja) - korean:rctrl_hanja = +kr(rctrl_hanja) - grab:debug = +grab(debug) - srvrkeys:none = +srvrkeys(none) - terminate:ctrl_alt_bksp = +terminate(ctrl_alt_bksp) - apple:alupckeys = +macintosh_vndr/apple(alupckeys) - apple:jp_pc106 = +macintosh_vndr/apple(jp_pc106) - apple:jp_oadg109a = +macintosh_vndr/apple(jp_oadg109a) - solaris:sun_compat = +sun_vndr/solaris(sun_compat) - shift:breaks_caps = +shift(breaks_caps) - shift:both_capslock = +shift(both_capslock) - shift:both_capslock_cancel = +shift(both_capslock_cancel) - shift:both_shiftlock = +shift(both_shiftlock) - lv2:lsgt_switch = +level2(lsgt_switch) -// Third-level choosers: - lv3:switch = +level3(switch) - lv3:ralt_switch = +level3(ralt_switch) - lv3:ralt_switch_multikey = +level3(ralt_switch_multikey) - lv3:lalt_switch = +level3(lalt_switch) - lv3:alt_switch = +level3(alt_switch) - lv3:menu_switch = +level3(menu_switch) - lv3:win_switch = +level3(win_switch) - lv3:lwin_switch = +level3(lwin_switch) - lv3:rwin_switch = +level3(rwin_switch) - lv3:enter_switch = +level3(enter_switch) - lv3:caps_switch = +level3(caps_switch) - lv3:bksl_switch = +level3(bksl_switch) - lv3:lsgt_switch = +level3(lsgt_switch) - lv3:caps_switch_latch = +level3(caps_switch_latch) - lv3:bksl_switch_latch = +level3(bksl_switch_latch) - lv3:lsgt_switch_latch = +level3(lsgt_switch_latch) - lv3:4_switch_isolated = +level3(4_switch_isolated) - lv3:9_switch_isolated = +level3(9_switch_isolated) -// Fifth-level choosers: - lv5:caps_switch = +level5(caps_switch) - lv5:lsgt_switch = +level5(lsgt_switch) - lv5:ralt_switch = +level5(ralt_switch) - lv5:menu_switch = +level5(menu_switch) - lv5:rctrl_switch = +level5(rctrl_switch) - lv5:lsgt_switch_lock = +level5(lsgt_switch_lock) - lv5:ralt_switch_lock = +level5(ralt_switch_lock) - lv5:lwin_switch_lock = +level5(lwin_switch_lock) - lv5:rwin_switch_lock = +level5(rwin_switch_lock) - grp:switch = +group(switch) - grp:lswitch = +group(lswitch) - grp:win_switch = +group(win_switch) - grp:lwin_switch = +group(lwin_switch) - grp:rwin_switch = +group(rwin_switch) - grp:rctrl_switch = +group(rctrl_switch) - grp:menu_switch = +group(menu_switch) - grp:caps_switch = +group(caps_switch) - grp:ctrls_toggle = +group(ctrls_toggle) - grp:caps_toggle = +group(caps_toggle) - grp:shift_caps_toggle = +group(shift_caps_toggle) - grp:caps_select = +group(caps_select) - grp:win_menu_select = +group(win_menu_select) - grp:ctrl_select = +group(ctrl_select) - grp:alt_caps_toggle = +group(alt_caps_toggle) - grp:menu_toggle = +group(menu_toggle) - grp:lwin_toggle = +group(lwin_toggle) - grp:rwin_toggle = +group(rwin_toggle) - grp:lshift_toggle = +group(lshift_toggle) - grp:rshift_toggle = +group(rshift_toggle) - grp:lctrl_toggle = +group(lctrl_toggle) - grp:rctrl_toggle = +group(rctrl_toggle) - grp:lalt_toggle = +group(lalt_toggle) - grp:sclk_toggle = +group(sclk_toggle) - grp:lctrl_lwin_toggle = +group(lctrl_lwin_toggle) - grp:lctrl_lwin_rctrl_menu = +group(lctrl_lwin_rctrl_menu) - grp:lctrl_lalt_toggle = +group(lctrl_lalt_toggle) - grp:rctrl_ralt_toggle = +group(rctrl_ralt_toggle) - grp:ctrl_alt_toggle = +group(ctrl_alt_toggle) - grp:ctrl_alt_toggle_bidir = +group(ctrl_alt_toggle_bidir) - grp:lctrl_lshift_toggle = +group(lctrl_lshift_toggle) - grp:ctrl_shift_toggle = +group(ctrl_shift_toggle) - grp:ctrl_shift_toggle_bidir = +group(ctrl_shift_toggle_bidir) - grp:lalt_lshift_toggle = +group(lalt_lshift_toggle) - grp:ralt_rshift_toggle = +group(ralt_rshift_toggle) - grp:alt_shift_toggle = +group(alt_shift_toggle) - grp:alt_shift_toggle_bidir = +group(alt_shift_toggle_bidir) diff --git a/rules/0043-option_compat.part b/rules/0043-option_compat.part deleted file mode 100644 index 87cf7658..00000000 --- a/rules/0043-option_compat.part +++ /dev/null @@ -1,8 +0,0 @@ -! option = compat - grp_led:num = +grp_led(num) - grp_led:caps = +grp_led(caps) - grp_led:scroll = +grp_led(scroll) - mod_led:compose = +mod_led(compose) - japan:kana_lock = +japan(kana_lock) - caps:shiftlock = +caps(shiftlock) - grab:break_actions = +grab(break_actions) diff --git a/rules/0044-option_types.part b/rules/0044-option_types.part deleted file mode 100644 index ce7ebe55..00000000 --- a/rules/0044-option_types.part +++ /dev/null @@ -1,10 +0,0 @@ -! option = types - caps:internal = +caps(internal) - caps:internal_nocancel = +caps(internal_nocancel) - caps:shift = +caps(shift) - caps:shift_nocancel = +caps(shift_nocancel) - numpad:pc = +numpad(pc) - numpad:mac = +numpad(mac) - numpad:microsoft = +numpad(microsoft) - numpad:shift3 = +numpad(shift3) - custom:types = +custom diff --git a/rules/compat/0041-option_symbols.part b/rules/compat/0041-option_symbols.part index 89bc6859..088a5236 100644 --- a/rules/compat/0041-option_symbols.part +++ b/rules/compat/0041-option_symbols.part @@ -1,8 +1,8 @@ -! option = symbols - grp:shift_caps_switch = +group(caps_select) - grp:win_menu_switch = +group(win_menu_select) - grp:lctrl_rctrl_switch = +group(ctrl_select) +! option = symbols + grp:shift_caps_switch = +group(caps_select) + grp:win_menu_switch = +group(win_menu_select) + grp:lctrl_rctrl_switch = +group(ctrl_select) // Delete the above three aliases in July 2027. - ctrl:swapcaps_hyper = +ctrl(hyper_capscontrol) - ctrl:swapcaps_and_switch_layout = +ctrl(swapcaps)+group(lctrl_toggle) + ctrl:swapcaps_hyper = +ctrl(hyper_capscontrol) + ctrl:swapcaps_and_switch_layout = +ctrl(swapcaps)+group(lctrl_toggle) // Delete the above two aliases in September 2027. diff --git a/rules/generate-options-symbols.py b/rules/generate-options-symbols.py new file mode 100755 index 00000000..fcd3906a --- /dev/null +++ b/rules/generate-options-symbols.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 +# +# This file is formatted with python black +# +# This file parses the base.xml and base.extras.xml file and prints out the option->symbols +# mapping compatible with the rules format. See the meson.build file for how this is used. + +import argparse +import sys +import xml.etree.ElementTree as ET + +from typing import Iterable +from dataclasses import dataclass +from pathlib import Path + +XKB_CONFIG_ROOT: "XkbConfigRoot" + +skip = ( + # Special type that exists but doesn't really exist + "custom:types", + # These are all defined in 0036-layoutoption_symbols.part + "misc:apl", + "misc:typo", + "lv3:ralt_alt", + "grp:toggle", + "grp:alts_toggle", + "grp:alt_altgr_toggle", + "grp:alt_space_toggle", + "grp:win_space_toggle", + "grp:ctrl_space_toggle", + "grp:rctrl_rshift_toggle", + "grp:shifts_toggle", +) + + +def error(msg): + print(f"ERROR: {msg}", file=sys.stderr) + print("Aborting now") + sys.exit(1) + + +@dataclass +class Directive: + option: "Option" + filename: str + section: str + + @property + def name(self) -> str: + return self.option.name + + def __str__(self) -> str: + return f"{self.filename}({self.section})" + + +@dataclass +class DirectiveSet: + option: "Option" + keycodes: Directive | None + compat: Directive | None + geometry: Directive | None + symbols: Directive | None + types: Directive | None + + @property + def is_empty(self) -> bool: + return all( + x is None + for x in ( + self.keycodes, + self.compat, + self.geometry, + self.symbols, + self.types, + ) + ) + + def for_section(self, section: str) -> Directive | None: + return { + "xkb_keycodes": self.keycodes, + "xkb_compatibility": self.compat, + "xkb_geometry": self.geometry, + "xkb_symbols": self.symbols, + "xkb_types": self.types, + }[section] + + +@dataclass +class XkbConfigRoot: + keycodes: Path + compat: Path + geometry: Path + symbols: Path + types: Path + + @property + def directories(self) -> Iterable[Path]: + yield self.keycodes + yield self.compat + yield self.geometry + yield self.symbols + yield self.types + + @property + def section_headers(self) -> Iterable[str]: + for h in [ + "xkb_keycodes", + "xkb_compatibility", + "xkb_geometry", + "xkb_symbols", + "xkb_types", + ]: + yield h + + @classmethod + def for_basedir(cls, basedir: Path) -> "XkbConfigRoot": + return cls( + keycodes=basedir / "keycodes", + compat=basedir / "compat", + geometry=basedir / "geometry", + symbols=basedir / "symbols", + types=basedir / "types", + ) + + +@dataclass +class Option: + """ + Wrapper around a single option -> symbols rules file entry. Has the properties + name and directive where the directive consists of the XKB symbols file name + and corresponding section, usually composed in the rules file as: + name = +directive + """ + + name: str + + def __lt__(self, other) -> bool: + return self.name < other.name + + @property + def directive(self) -> Directive: + f, s = self.name.split(":") + return Directive(self, f, s) + + +def resolve_option(option: Option) -> DirectiveSet: + directives: dict[str, Directive | None] = { + s: None for s in XKB_CONFIG_ROOT.section_headers + } + directive = option.directive + filename, section = directive.filename, directive.section + for subdir, section_header in zip( + XKB_CONFIG_ROOT.directories, XKB_CONFIG_ROOT.section_headers + ): + if not (subdir / filename).exists(): + # Some of our foo:bar entries map to a baz_vndr/foo file + for vndr in subdir.glob("*_vndr"): + vndr_path = vndr / filename + if vndr_path.exists(): + filename = vndr_path.relative_to(subdir).as_posix() + break + else: + continue + + # Now check if the target file actually has that section + f = subdir / filename + with open(f) as fd: + found = any(f'{section_header} "{section}"' in line for line in fd) + if found: + directives[section_header] = Directive(option, filename, section) + + return DirectiveSet( + option=option, + keycodes=directives["xkb_keycodes"], + compat=directives["xkb_compatibility"], + geometry=directives["xkb_geometry"], + symbols=directives["xkb_symbols"], + types=directives["xkb_types"], + ) + + +def options(rules_xml) -> Iterable[Option]: + """ + Yields all Options from the given XML file + """ + tree = ET.parse(rules_xml) + root = tree.getroot() + + def fetch_subelement(parent, name): + sub_element = parent.findall(name) + if sub_element is not None and len(sub_element) == 1: + return sub_element[0] + return None + + def fetch_text(parent, name): + sub_element = fetch_subelement(parent, name) + if sub_element is None: + return None + return sub_element.text + + def fetch_name(elem): + try: + ci_element = ( + elem + if elem.tag == "configItem" + else fetch_subelement(elem, "configItem") + ) + name = fetch_text(ci_element, "name") + assert name is not None + return name + except AssertionError as e: + endl = "\n" # f{} cannot contain backslashes + e.args = ( + f"\nFor element {ET.tostring(elem).decode('utf-8')}\n{endl.join(e.args)}", + ) + raise + + for option in root.iter("option"): + yield Option(fetch_name(option)) + + +def main(): + global XKB_CONFIG_ROOT + + parser = argparse.ArgumentParser(description="Generate the evdev keycode lists.") + parser.add_argument( + "--xkb-config-root", + help="The XKB base directory", + default=Path("."), + type=Path, + ) + parser.add_argument( + "--rules-section", + choices=["xkb_symbols", "xkb_compatibility", "xkb_types"], + help="The rules section to generate", + default="xkb_symbols", + ) + parser.add_argument( + "files", nargs="+", help="The base.xml and base.extras.xml files" + ) + ns = parser.parse_args() + + XKB_CONFIG_ROOT = XkbConfigRoot.for_basedir(ns.xkb_config_root) + + all_options = [] + for f in ns.files: + os = list(options(f)) + all_options.extend(os) + + directives = (resolve_option(o) for o in sorted(all_options) if o.name not in skip) + + def check_and_map(directive): + assert ( + not directive.is_empty + ), f"Option {directive.option} does not resolve to any section" + + return directive.for_section(ns.rules_section) + + filtered = ( + x + for x in filter( + lambda y: y is not None, + map(check_and_map, directives), + ) + ) + + header = { + "xkb_symbols": "symbols", + "xkb_compatibility": "compat", + "xkb_types": "types", + }[ns.rules_section] + + print(f"! option = {header}") + for d in filtered: + assert d is not None + print(f" {d.name:30s} = +{d}") + + if ns.rules_section == "xkb_types": + print(f" {'custom:types':30s} = +custom") + + +if __name__ == "__main__": + main() diff --git a/rules/meson.build b/rules/meson.build index 514d03d9..509206dc 100644 --- a/rules/meson.build +++ b/rules/meson.build @@ -2,6 +2,9 @@ install_data('README', 'xkb.dtd', 'xfree98', install_dir: dir_xkb_rules) +base_xml = files('base.xml') +base_extras_xml = files('base.extras.xml') + # the actual rules files are generated from a list of parts in a very # specific order parts = [ @@ -30,13 +33,33 @@ parts = [ '0038-layout2option_symbols.part', '0039-layout3option_symbols.part', '0040-layout4option_symbols.part', - '0042-option_symbols.part', - '0043-option_compat.part', - '0044-option_types.part', + # 0042-option_symbols.part is generated from base{.extras}.xml + # 0043-option_compat.part is generated from base{.extras}.xml + # 0044-option_types.part is generated from base{.extras}.xml ] -# generated compat parts -rules_compat_generated = [] +rules_parts_generated = [] + +generate_rules_options_symbols = find_program('generate-options-symbols.py') +generated = [ + ['0042-option_symbols.part', 'xkb_symbols'], + ['0043-option_compat.part', 'xkb_compatibility'], + ['0044-option_types.part', 'xkb_types'], +] +foreach g: generated + filename = g[0] + section = g[1] + part = custom_target(filename, + build_by_default: true, + command: [generate_rules_options_symbols, + '--rules-section=@0@'.format(section), + '--xkb-config-root', meson.project_source_root(), + base_xml, base_extras_xml], + output: filename, + capture: true, + install: false) + rules_parts_generated += [part] +endforeach if get_option('compat-rules') # non-generated compat parts @@ -85,7 +108,7 @@ if get_option('compat-rules') ], output: ml_s_file, install: false) - rules_compat_generated += [ml_s] + rules_parts_generated += [ml_s] mlv_s_file = lvl_mlv_s['@0@'.format(lvl)] mlv_s = custom_target(mlv_s_file, @@ -99,7 +122,7 @@ if get_option('compat-rules') ], output: mlv_s_file, install: false) - rules_compat_generated += [mlv_s] + rules_parts_generated += [mlv_s] endforeach endif # compat-rules @@ -127,9 +150,9 @@ foreach ruleset: ['base', 'evdev'] build_by_default: true, command: [ merge_py, - rules_parts + rules_compat_generated, + rules_parts + rules_parts_generated, ], - depends: rules_compat_generated, + depends: rules_parts_generated, output: ruleset, capture: true, install: true, @@ -137,7 +160,7 @@ foreach ruleset: ['base', 'evdev'] # Third: the xml files, simply copied from the base*.xml files ruleset_xml = configure_file(output: '@0@.xml'.format(ruleset), - input: 'base.xml', + input: base_xml, copy: true, install: true, install_dir: dir_xkb_rules) @@ -148,7 +171,7 @@ foreach ruleset: ['base', 'evdev'] endif configure_file(output: '@0@.extras.xml'.format(ruleset), - input: 'base.extras.xml', + input: base_extras_xml, copy: true, install: true, install_dir: dir_xkb_rules) |