Skip to content

Instantly share code, notes, and snippets.

@willwade
Last active September 18, 2023 10:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save willwade/b82bc984e5ef3d883812f7b2b7ba866c to your computer and use it in GitHub Desktop.
Save willwade/b82bc984e5ef3d883812f7b2b7ba866c to your computer and use it in GitHub Desktop.
this takes the directory of xkb keyboard maps found in linux at /usr/share/X11/xkb/symbols - and converts them to a relaykeys format e.g. https://github.com/AceCentre/RelayKeys/blob/master/cli_keymaps/de_keymap.json
import os
import re
import json
import unicodedata
def xkb_to_unicode(key_code):
# Add your custom mapping here
custom_mapping = {
"adiaeresis": "ä",
"odiaeresis": "ö",
"udiaeresis": "ü",
"space": " ",
"Tab": "\t",
"exclamation_mark": "!",
"double_quotes": "\"",
"hash": "#",
"dollar": "$",
"percent": "%",
"ampersand": "&",
"single_quote": "'",
"left_parenthesis": "(",
"right_parenthesis": ")",
"asterisk": "*",
"plus": "+",
"comma": ",",
"minus": "-",
"period": ".",
"slash": "/",
"colon": ":",
"semicolon": ";",
"less_than": "<",
"equals": "=",
"greater_than": ">",
"question_mark": "?",
"at": "@",
"left_square_bracket": "[",
"backslash": "\\",
"right_square_bracket": "]",
"caret": "^",
"underscore": "_",
"grave_accent": "`",
"left_curly_brace": "{",
"vertical_bar": "|",
"right_curly_brace": "}",
"tilde": "~",
'Abreve': 'ă',
'Acircumflex': 'â',
'Adiaeresis': 'ä',
'AE': 'æ',
'B': 'B',
'C': 'C',
'Cacute': 'ć',
'Ccaron': 'č',
'D': 'D',
'Down': '↓',
'E': 'E',
'ENG': 'ŋ',
'ETH': 'ð',
'Ecaron': 'ě',
'Ediaeresis': 'ë',
'End': '⌦',
'EuroSign': '€',
'Home': '⌂',
'Iacute': 'í',
'Icircumflex': 'î',
'Insert': '⎀',
'Left': '←',
'Lstroke': 'ł',
'Nacute': 'ń',
'Next': '⎗',
'NoSymbol': '⎋',
'OE': 'œ',
'Oacute': 'ó',
'Odiaeresis': 'ö',
'Odoubleacute': 'ő',
'Oslash': 'ø',
'Prior': '⎖',
'Racute': 'ŕ',
'Redo': '⎘',
'Return': '⎆',
'Right': '→',
'SCHWA': 'ə',
'Sacute': 'ś',
'Scaron': 'š',
'Scedilla': 'ş',
'THORN': 'þ',
'Tcedilla': 'ţ',
}
# Check if the string is a Unicode character
unicode_match = re.match(r'U([0-9A-Fa-f]+)', key_code)
if unicode_match:
unicode_code = int(unicode_match.group(1), 16)
return chr(unicode_code)
return custom_mapping.get(key_code, key_code)
def generate_modifier_mapping(num_symbols):
base_modifiers = [
[], # Level 1: No modifiers
["LSHIFT"], # Level 2: Left Shift
["LALT"], # Level 3: Left Alt (or AltGr)
["LCTRL"], # Level 4: Left Control
["RSHIFT"], # Level 5: Right Shift
["RALT"], # Level 6: Right Alt (or AltGr)
["RCTRL"], # Level 7: Right Control
["LSHIFT", "LALT"], # Level 8: Left Shift + Left Alt
["LSHIFT", "LCTRL"], # Level 9: Left Shift + Left Control
["LALT", "LCTRL"], # Level 10: Left Alt + Left Control
["RSHIFT", "RALT"], # Level 11: Right Shift + Right Alt
["RSHIFT", "RCTRL"], # Level 12: Right Shift + Right Control
["RALT", "RCTRL"], # Level 13: Right Alt + Right Control
["LSHIFT", "RSHIFT"], # Level 14: Left Shift + Right Shift
["LALT", "RALT"], # Level 15: Left Alt + Right Alt
["LCTRL", "RCTRL"], # Level 16: Left Control + Right Control
]
# Generate a mapping based on the number of symbols
modifier_mapping = {}
for i in range(num_symbols):
modifier_mapping[i] = base_modifiers[i % len(base_modifiers)]
return modifier_mapping
def parse_xkb_to_json(xkb_file_path):
json_data = {}
# Read the XKB file
with open(xkb_file_path, 'r') as f:
xkb_data = f.read()
# Dynamically generate the modifier_mapping based on the number of symbols for the first key
first_key_match = re.search(r"key <(.*?)> { \[ (.*?) \] };", xkb_data)
if first_key_match:
num_symbols = len(first_key_match.group(2).split(", "))
modifier_mapping = generate_modifier_mapping(num_symbols)
# Parse each line in the XKB layout data
for line in xkb_data.strip().split("\n"):
match = re.search(r"key <(.*?)> { \[ (.*?) \] };", line)
if match:
key_code_list = match.group(2).split(", ")
# Remove extra spaces from key codes
key_code_list = [key_code.strip() for key_code in key_code_list]
# Inside parse_xkb_to_json function
for index, key_code in enumerate(key_code_list):
modifiers = modifier_mapping.get(index, [])
# Use the first key code as the base for the JSON entry
base_key_code = key_code_list[0].strip()
# Convert XKB symbol names to Unicode characters
key_code = xkb_to_unicode(key_code)
base_key_code = xkb_to_unicode(base_key_code)
json_data[key_code] = [base_key_code, modifiers]
return json_data
def format_json(json_data):
formatted_json = "{\n"
# Sort keys so that numbers and letters appear first
sorted_keys = sorted(json_data.keys(), key=lambda x: (not x.isalnum(), x))
for key in sorted_keys:
value = json_data[key]
# Special handling for the double quote character
if key == '"':
key = '\\"'
formatted_line = f'\t"{key}": {json.dumps(value)},\n'
formatted_json += formatted_line
formatted_json = formatted_json.rstrip(",\n") + "\n}"
return formatted_json
# Loop through all files in the 'symbols' directory
for xkb_file in os.listdir('symbols'):
xkb_file_path = os.path.join('symbols', xkb_file)
# Skip directories
if os.path.isdir(xkb_file_path):
continue
# Convert XKB layout to JSON format
json_data = parse_xkb_to_json(xkb_file_path)
# Save JSON data to a file
output_file_path = os.path.join('output_json', f"{xkb_file}.json")
# Format the JSON string with line breaks and tabs
formatted_json = format_json(json_data)
with open(output_file_path, 'w') as json_file:
json_file.write(formatted_json)
print(f"JSON data for {xkb_file} has been saved to {output_file_path}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment