qmk_firmware

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

keycodes.py (6576B)


      1 """Used by the make system to generate keycodes.h from keycodes_{version}.json
      2 """
      3 from milc import cli
      4 
      5 from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
      6 from qmk.commands import dump_lines
      7 from qmk.path import normpath
      8 from qmk.keycodes import load_spec
      9 
     10 
     11 def _translate_group(group):
     12     """Fix up any issues with badly chosen values
     13     """
     14     if group == 'modifiers':
     15         return 'modifier'
     16     if group == 'media':
     17         return 'consumer'
     18     return group
     19 
     20 
     21 def _render_key(key):
     22     width = 7
     23     if 'S(' in key:
     24         width += len('S()')
     25     if 'A(' in key:
     26         width += len('A()')
     27     if 'RCTL(' in key:
     28         width += len('RCTL()')
     29     if 'ALGR(' in key:
     30         width += len('ALGR()')
     31     return key.ljust(width)
     32 
     33 
     34 def _render_label(label):
     35     label = label.replace("\\", "(backslash)")
     36     return label
     37 
     38 
     39 def _generate_ranges(lines, keycodes):
     40     lines.append('')
     41     lines.append('enum qk_keycode_ranges {')
     42     lines.append('// Ranges')
     43     for key, value in keycodes["ranges"].items():
     44         lo, mask = map(lambda x: int(x, 16), key.split("/"))
     45         hi = lo + mask
     46         define = value.get("define")
     47         lines.append(f'    {define.ljust(30)} = 0x{lo:04X},')
     48         lines.append(f'    {(define + "_MAX").ljust(30)} = 0x{hi:04X},')
     49     lines.append('};')
     50 
     51 
     52 def _generate_defines(lines, keycodes):
     53     lines.append('')
     54     lines.append('enum qk_keycode_defines {')
     55     lines.append('// Keycodes')
     56     for key, value in keycodes["keycodes"].items():
     57         lines.append(f'    {value.get("key")} = {key},')
     58 
     59     lines.append('')
     60     lines.append('// Alias')
     61     for key, value in keycodes["keycodes"].items():
     62         temp = value.get("key")
     63         for alias in value.get("aliases", []):
     64             lines.append(f'    {alias.ljust(10)} = {temp},')
     65 
     66     lines.append('};')
     67 
     68 
     69 def _generate_helpers(lines, keycodes):
     70     lines.append('')
     71     lines.append('// Range Helpers')
     72     for value in keycodes["ranges"].values():
     73         define = value.get("define")
     74         lines.append(f'#define IS_{define}(code) ((code) >= {define} && (code) <= {define + "_MAX"})')
     75 
     76     # extract min/max
     77     temp = {}
     78     for key, value in keycodes["keycodes"].items():
     79         group = value.get('group', None)
     80         if not group:
     81             continue
     82         if group not in temp:
     83             temp[group] = [0xFFFF, 0]
     84         key = int(key, 16)
     85         if key < temp[group][0]:
     86             temp[group][0] = key
     87         if key > temp[group][1]:
     88             temp[group][1] = key
     89 
     90     lines.append('')
     91     lines.append('// Group Helpers')
     92     for group, codes in temp.items():
     93         lo = keycodes["keycodes"][f'0x{codes[0]:04X}']['key']
     94         hi = keycodes["keycodes"][f'0x{codes[1]:04X}']['key']
     95         lines.append(f'#define IS_{_translate_group(group).upper()}_KEYCODE(code) ((code) >= {lo} && (code) <= {hi})')
     96 
     97     lines.append('')
     98     lines.append('// Switch statement Helpers')
     99     for group, codes in temp.items():
    100         lo = keycodes["keycodes"][f'0x{codes[0]:04X}']['key']
    101         hi = keycodes["keycodes"][f'0x{codes[1]:04X}']['key']
    102         name = f'{_translate_group(group).upper()}_KEYCODE_RANGE'
    103         lines.append(f'#define {name.ljust(35)} {lo} ... {hi}')
    104 
    105 
    106 def _generate_aliases(lines, keycodes):
    107     # Work around ChibiOS ch.h include guard
    108     if 'CH_H' in [value['key'] for value in keycodes['aliases'].values()]:
    109         lines.append('')
    110         lines.append('#undef CH_H')
    111 
    112     lines.append('')
    113     lines.append('// Aliases')
    114     for key, value in keycodes["aliases"].items():
    115         define = _render_key(value.get("key"))
    116         val = _render_key(key)
    117         if 'label' in value:
    118             lines.append(f'#define {define} {val} // {_render_label(value.get("label"))}')
    119         else:
    120             lines.append(f'#define {define} {val}')
    121 
    122     lines.append('')
    123     for key, value in keycodes["aliases"].items():
    124         for alias in value.get("aliases", []):
    125             lines.append(f'#define {alias} {value.get("key")}')
    126 
    127 
    128 def _generate_version(lines, keycodes, prefix=''):
    129     version = keycodes['version']
    130     major, minor, patch = map(int, version.split('.'))
    131 
    132     bcd = f'0x{major:02d}{minor:02d}{patch:04d}'
    133 
    134     lines.append('')
    135     lines.append(f'#define QMK_{prefix}KEYCODES_VERSION "{version}"')
    136     lines.append(f'#define QMK_{prefix}KEYCODES_VERSION_BCD {bcd}')
    137     lines.append(f'#define QMK_{prefix}KEYCODES_VERSION_MAJOR {major}')
    138     lines.append(f'#define QMK_{prefix}KEYCODES_VERSION_MINOR {minor}')
    139     lines.append(f'#define QMK_{prefix}KEYCODES_VERSION_PATCH {patch}')
    140 
    141 
    142 @cli.argument('-v', '--version', arg_only=True, required=True, help='Version of keycodes to generate.')
    143 @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
    144 @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
    145 @cli.subcommand('Used by the make system to generate keycodes.h from keycodes_{version}.json', hidden=True)
    146 def generate_keycodes(cli):
    147     """Generates the keycodes.h file.
    148     """
    149 
    150     # Build the keycodes.h file.
    151     keycodes_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '// clang-format off']
    152 
    153     keycodes = load_spec(cli.args.version)
    154 
    155     _generate_version(keycodes_h_lines, keycodes)
    156     _generate_ranges(keycodes_h_lines, keycodes)
    157     _generate_defines(keycodes_h_lines, keycodes)
    158     _generate_helpers(keycodes_h_lines, keycodes)
    159 
    160     # Show the results
    161     dump_lines(cli.args.output, keycodes_h_lines, cli.args.quiet)
    162 
    163 
    164 @cli.argument('-v', '--version', arg_only=True, required=True, help='Version of keycodes to generate.')
    165 @cli.argument('-l', '--lang', arg_only=True, required=True, help='Language of keycodes to generate.')
    166 @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
    167 @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
    168 @cli.subcommand('Used by the make system to generate keymap_{lang}.h from keycodes_{lang}_{version}.json', hidden=True)
    169 def generate_keycode_extras(cli):
    170     """Generates the header file.
    171     """
    172 
    173     # Build the header file.
    174     keycodes_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '#include "keycodes.h"', '// clang-format off']
    175 
    176     keycodes = load_spec(cli.args.version, cli.args.lang)
    177 
    178     _generate_version(keycodes_h_lines, keycodes, f'{cli.args.lang.upper()}_')
    179     _generate_aliases(keycodes_h_lines, keycodes)
    180 
    181     # Show the results
    182     dump_lines(cli.args.output, keycodes_h_lines, cli.args.quiet)