summaryrefslogtreecommitdiff
path: root/lib/python/qmk/cli/generate/keyboard_h.py
blob: b9e89032b9e2885f7f3edc76cc8982bf030dedb1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
"""Used by the make system to generate keyboard.h from info.json.
"""
from pathlib import Path

from milc import cli

from qmk.path import normpath
from qmk.info import info_json
from qmk.commands import dump_lines
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.constants import COL_LETTERS, ROW_LETTERS, GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE


def _generate_layouts(keyboard, kb_info_json):
    """Generates the layouts macros.
    """
    if 'matrix_size' not in kb_info_json:
        cli.log.error(f'{keyboard}: Invalid matrix config.')
        return []

    col_num = kb_info_json['matrix_size']['cols']
    row_num = kb_info_json['matrix_size']['rows']

    lines = []
    for layout_name, layout_data in kb_info_json['layouts'].items():
        if layout_data['c_macro']:
            continue

        if not all('matrix' in key_data for key_data in layout_data['layout']):
            cli.log.debug(f'{keyboard}/{layout_name}: No or incomplete matrix data!')
            continue

        layout_keys = []
        layout_matrix = [['KC_NO'] * col_num for _ in range(row_num)]

        for index, key_data in enumerate(layout_data['layout']):
            row, col = key_data['matrix']
            identifier = f'k{ROW_LETTERS[row]}{COL_LETTERS[col]}'

            if row >= row_num or col >= col_num:
                key_name = key_data.get('label', identifier)
                if row >= row_num:
                    cli.log.error(f'{keyboard}/{layout_name}: Matrix row for key {index} ({key_name}) is {row} but must be less than {row_num}')

                if col >= col_num:
                    cli.log.error(f'{keyboard}/{layout_name}: Matrix column for key {index} ({key_name}) is {col} but must be less than {col_num}')

                return []

            layout_matrix[row][col] = identifier
            layout_keys.append(identifier)

        lines.append('')
        lines.append(f'#define {layout_name}({", ".join(layout_keys)}) {{ \\')

        rows = ', \\\n'.join(['\t {' + ', '.join(row) + '}' for row in layout_matrix])
        rows += ' \\'
        lines.append(rows)
        lines.append('}')

    for alias, target in kb_info_json.get('layout_aliases', {}).items():
        lines.append('')
        lines.append(f'#ifndef {alias}')
        lines.append(f'#   define {alias} {target}')
        lines.append('#endif')

    return lines


def _generate_keycodes(kb_info_json):
    """Generates keyboard level keycodes.
    """
    if 'keycodes' not in kb_info_json:
        return []

    lines = []
    lines.append('enum keyboard_keycodes {')

    for index, item in enumerate(kb_info_json.get('keycodes')):
        key = item["key"]
        if index == 0:
            lines.append(f'  {key} = QK_KB_0,')
        else:
            lines.append(f'  {key},')

    lines.append('};')

    for item in kb_info_json.get('keycodes', []):
        key = item["key"]
        for alias in item.get("aliases", []):
            lines.append(f'#define {alias} {key}')

    return lines


@cli.argument('-i', '--include', nargs='?', arg_only=True, help='Optional file to include')
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate keyboard.h for.')
@cli.subcommand('Used by the make system to generate keyboard.h from info.json', hidden=True)
def generate_keyboard_h(cli):
    """Generates the keyboard.h file.
    """
    # Build the info.json file
    kb_info_json = info_json(cli.args.keyboard)

    keyboard_h = cli.args.include
    dd_layouts = _generate_layouts(cli.args.keyboard, kb_info_json)
    dd_keycodes = _generate_keycodes(kb_info_json)
    valid_config = dd_layouts or keyboard_h

    # Build the layouts.h file.
    keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '#include "quantum.h"']

    keyboard_h_lines.append('')
    keyboard_h_lines.append('// Layout content')
    if dd_layouts:
        keyboard_h_lines.extend(dd_layouts)
    if keyboard_h:
        keyboard_h_lines.append(f'#include "{Path(keyboard_h).name}"')

    keyboard_h_lines.append('')
    keyboard_h_lines.append('// Keycode content')
    if dd_keycodes:
        keyboard_h_lines.extend(dd_keycodes)

    # Protect against poorly configured keyboards
    if not valid_config:
        keyboard_h_lines.append('#error("<keyboard>.h is required unless your keyboard uses data-driven configuration. Please rename your keyboard\'s header file to <keyboard>.h")')

    # Show the results
    dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet)