qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

via2json.py (6258B)


      1 """Generate a keymap.c from a configurator export.
      2 """
      3 import json
      4 import re
      5 
      6 from milc import cli
      7 
      8 import qmk.keyboard
      9 import qmk.path
     10 from qmk.info import info_json
     11 from qmk.json_encoders import KeymapJSONEncoder
     12 from qmk.commands import dump_lines
     13 from qmk.keymap import generate_json
     14 
     15 
     16 def _find_via_layout_macro(keyboard_data):
     17     """Assume layout macro when only 1 is available
     18     """
     19     layouts = list(keyboard_data['layouts'].keys())
     20     return layouts[0] if len(layouts) == 1 else None
     21 
     22 
     23 def _convert_macros(via_macros):
     24     via_macros = list(filter(lambda f: bool(f), via_macros))
     25     if len(via_macros) == 0:
     26         return list()
     27     split_regex = re.compile(r'(}\,)|(\,{)')
     28     macro_group_regex = re.compile(r'({.+?})')
     29     macros = list()
     30     for via_macro in via_macros:
     31         # Split VIA macro to its elements
     32         macro = split_regex.split(via_macro)
     33         # Remove junk elements (None, '},' and ',{')
     34         macro = list(filter(lambda f: False if f in (None, '},', ',{') else True, macro))
     35         macro_data = list()
     36         for m in macro:
     37             if '{' in m or '}' in m:
     38                 # Split macro groups
     39                 macro_groups = macro_group_regex.findall(m)
     40                 for macro_group in macro_groups:
     41                     # Remove whitespaces and curly braces from around group
     42                     macro_group = macro_group.strip(' {}')
     43 
     44                     macro_action = 'tap'
     45                     macro_keycodes = []
     46 
     47                     if macro_group[0] == '+':
     48                         macro_action = 'down'
     49                         macro_keycodes.append(macro_group[1:])
     50                     elif macro_group[0] == '-':
     51                         macro_action = 'up'
     52                         macro_keycodes.append(macro_group[1:])
     53                     else:
     54                         macro_keycodes.extend(macro_group.split(',') if ',' in macro_group else [macro_group])
     55 
     56                     # Remove the KC prefixes
     57                     macro_keycodes = list(map(lambda s: s.replace('KC_', ''), macro_keycodes))
     58 
     59                     macro_data.append({"action": macro_action, "keycodes": macro_keycodes})
     60             else:
     61                 # Found text
     62                 macro_data.append(m)
     63         macros.append(macro_data)
     64 
     65     return macros
     66 
     67 
     68 def _fix_macro_keys(keymap_data):
     69     macro_no = re.compile(r'MACRO0?\(([0-9]{1,2})\)')
     70     for i in range(0, len(keymap_data)):
     71         for j in range(0, len(keymap_data[i])):
     72             kc = keymap_data[i][j]
     73             m = macro_no.match(kc)
     74             if m:
     75                 keymap_data[i][j] = f'MC_{m.group(1)}'
     76     return keymap_data
     77 
     78 
     79 def _via_to_keymap(via_backup, keyboard_data, keymap_layout):
     80     # Check if passed LAYOUT is correct
     81     layout_data = keyboard_data['layouts'].get(keymap_layout)
     82     if not layout_data:
     83         cli.log.error(f'LAYOUT macro {keymap_layout} is not a valid one for keyboard {cli.args.keyboard}!')
     84         return None
     85 
     86     layout_data = layout_data['layout']
     87     sorting_hat = list()
     88     for index, data in enumerate(layout_data):
     89         sorting_hat.append([index, data['matrix']])
     90 
     91     sorting_hat.sort(key=lambda k: (k[1][0], k[1][1]))
     92 
     93     pos = 0
     94     for row_num in range(0, keyboard_data['matrix_size']['rows']):
     95         for col_num in range(0, keyboard_data['matrix_size']['cols']):
     96             if pos >= len(sorting_hat) or sorting_hat[pos][1][0] != row_num or sorting_hat[pos][1][1] != col_num:
     97                 sorting_hat.insert(pos, [None, [row_num, col_num]])
     98             else:
     99                 sorting_hat.append([None, [row_num, col_num]])
    100             pos += 1
    101 
    102     keymap_data = list()
    103     for layer in via_backup['layers']:
    104         pos = 0
    105         layer_data = list()
    106         for key in layer:
    107             if sorting_hat[pos][0] is not None:
    108                 layer_data.append([sorting_hat[pos][0], key])
    109             pos += 1
    110         layer_data.sort()
    111         layer_data = [kc[1] for kc in layer_data]
    112         keymap_data.append(layer_data)
    113 
    114     return keymap_data
    115 
    116 
    117 @cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to')
    118 @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
    119 @cli.argument('filename', type=qmk.path.FileType('r'), arg_only=True, help='VIA Backup JSON file')
    120 @cli.argument('-kb', '--keyboard', type=qmk.keyboard.keyboard_folder, completer=qmk.keyboard.keyboard_completer, arg_only=True, required=True, help='The keyboard\'s name')
    121 @cli.argument('-km', '--keymap', arg_only=True, default='via2json', help='The keymap\'s name')
    122 @cli.argument('-l', '--layout', arg_only=True, help='The keymap\'s layout')
    123 @cli.subcommand('Convert a VIA backup json to keymap.json format.')
    124 def via2json(cli):
    125     """Convert a VIA backup json to keymap.json format.
    126 
    127     This command uses the `qmk.keymap` module to generate a keymap.json from a VIA backup json. The generated keymap is written to stdout, or to a file if -o is provided.
    128     """
    129     # Load the VIA backup json
    130     with cli.args.filename.open('r') as fd:
    131         via_backup = json.load(fd)
    132 
    133     keyboard_data = info_json(cli.args.keyboard)
    134 
    135     # Find appropriate layout macro
    136     keymap_layout = cli.args.layout if cli.args.layout else _find_via_layout_macro(keyboard_data)
    137     if not keymap_layout:
    138         cli.log.error(f"Couldn't find LAYOUT macro for keyboard {cli.args.keyboard}. Please specify it with the '-l' argument.")
    139         return False
    140 
    141     # Get keycode array
    142     keymap_data = _via_to_keymap(via_backup, keyboard_data, keymap_layout)
    143     if not keymap_data:
    144         cli.log.error(f'Could not extract valid keycode data from VIA backup matching keyboard {cli.args.keyboard}!')
    145         return False
    146 
    147     # Convert macros
    148     macro_data = list()
    149     if via_backup.get('macros'):
    150         macro_data = _convert_macros(via_backup['macros'])
    151 
    152         # Replace VIA macro keys with JSON keymap ones
    153         keymap_data = _fix_macro_keys(keymap_data)
    154 
    155     # Generate the keymap.json
    156     keymap_json = generate_json(cli.args.keymap, cli.args.keyboard, keymap_layout, keymap_data, macro_data)
    157 
    158     keymap_lines = [json.dumps(keymap_json, cls=KeymapJSONEncoder, sort_keys=True)]
    159     dump_lines(cli.args.output, keymap_lines, cli.args.quiet)