summaryrefslogtreecommitdiff
path: root/lib/python/qmk/keymap.py
blob: 3927f791a6bd3a3d1485daed562506f2bb6aac24 (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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
"""Functions that help you work with QMK keymaps.
"""
import os
from traceback import format_exc
import re
import glob

import qmk.path
import qmk.makefile
from qmk.errors import NoSuchKeyboardError

# The `keymap.c` template to use when a keyboard doesn't have its own
DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H

/* THIS FILE WAS GENERATED!
 *
 * This file was generated by qmk-compile-json. You may or may not want to
 * edit it directly.
 */

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
__KEYMAP_GOES_HERE__
};
"""


def template(keyboard):
    """Returns the `keymap.c` template for a keyboard.

    If a template exists in `keyboards/<keyboard>/templates/keymap.c` that
    text will be used instead of `DEFAULT_KEYMAP_C`.

    Args:
        keyboard
            The keyboard to return a template for.
    """
    template_name = 'keyboards/%s/templates/keymap.c' % keyboard

    if os.path.exists(template_name):
        with open(template_name, 'r') as fd:
            return fd.read()

    return DEFAULT_KEYMAP_C


def generate(keyboard, layout, layers):
    """Returns a keymap.c for the specified keyboard, layout, and layers.

    Args:
        keyboard
            The name of the keyboard

        layout
            The LAYOUT macro this keymap uses.

        layers
            An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
    """
    layer_txt = []
    for layer_num, layer in enumerate(layers):
        if layer_num != 0:
            layer_txt[-1] = layer_txt[-1] + ','
        layer_keys = ', '.join(layer)
        layer_txt.append('\t[%s] = %s(%s)' % (layer_num, layout, layer_keys))

    keymap = '\n'.join(layer_txt)
    keymap_c = template(keyboard)

    return keymap_c.replace('__KEYMAP_GOES_HERE__', keymap)


def write(keyboard, keymap, layout, layers):
    """Generate the `keymap.c` and write it to disk.

    Returns the filename written to.

    Args:
        keyboard
            The name of the keyboard

        keymap
            The name of the keymap

        layout
            The LAYOUT macro this keymap uses.

        layers
            An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
    """
    keymap_c = generate(keyboard, layout, layers)
    keymap_path = qmk.path.keymap(keyboard)
    keymap_dir = os.path.join(keymap_path, keymap)
    keymap_file = os.path.join(keymap_dir, 'keymap.c')

    if not os.path.exists(keymap_dir):
        os.makedirs(keymap_dir)

    with open(keymap_file, 'w') as keymap_fd:
        keymap_fd.write(keymap_c)

    return keymap_file

def find_keymaps(base_path, revision = "", community = False):
    """ Find the available keymaps for a keyboard and revision pair.

    Args:
        base_path: The base path of the keyboard.

        revision: The keyboard's revision.

        community: Set to True for the layouts under layouts/community.

    Returns:
        a list with whe keymaps's names
    """
    path_wildcard = os.path.join(base_path, "**", "keymap.c")
    if community:
        path_regex = re.compile(r"^" + re.escape(base_path) + "(\S+)" + os.path.sep + "keymap\.c")
    else:
        path_regex = re.compile(r"^" + re.escape(base_path) + "(?:" + re.escape(revision) + os.path.sep + ")?keymaps" + os.path.sep + "(\S+)" + os.path.sep + "keymap\.c")
    names = [path_regex.sub(lambda name: name.group(1), path) for path in glob.iglob(path_wildcard, recursive = True) if path_regex.search(path)]
    return names

def list_keymaps(keyboard_name):
    """ List the available keymaps for a keyboard.

    Args:
        keyboard_name: the keyboards full name with vendor and revision if necessary, example: clueboard/66/rev3

    Returns:
        a list with the names of the available keymaps
    """
    if os.path.sep in keyboard_name:
        keyboard, revision = os.path.split(os.path.normpath(keyboard_name))
    else:
        keyboard = keyboard_name
        revision = ""

    # parse all the rules.mk files for the keyboard
    rules_mk = qmk.makefile.get_rules_mk(keyboard, revision)
    names = list()

    if rules_mk:
        # get the keymaps from the keyboard's directory
        kb_base_path = os.path.join(os.getcwd(), "keyboards", keyboard) + os.path.sep
        names = find_keymaps(kb_base_path, revision)

        # if community layouts are supported, get them
        if "LAYOUTS" in rules_mk:
            for layout in rules_mk["LAYOUTS"]["value"].split():
                cl_base_path = os.path.join(os.getcwd(), "layouts", "community", layout) + os.path.sep
                names = names + find_keymaps(cl_base_path, revision, community = True)

    names.sort()
    return names