-
Notifications
You must be signed in to change notification settings - Fork 1
/
zmk-chorded.py
175 lines (147 loc) · 5.14 KB
/
zmk-chorded.py
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import csv
import utils
# Limit how many rows to process since microcontrollers such as nice nano have quite limited memory
# You'll need to lower this if you get "region `RAM' overflowed errors" when compiling
limit = 100
# How long in ms you have to press the combo keys together, you can be pretty relaxed here if combo is its own unique key
combo_timeout = 100
# You need to map all of your keys here since zmk combos are offset based
# You can use an empty string ("") for anything other than letters, punctuation and combo positions such that it adds up to the total numbers of keys on your keyboard, check the example below
key_positions = [
["W", "L", "Y", "P", "B", "Z", "F", "O", "U", "'"],
["C", "R", "S", "T", "G", "M", "N", "E", "I", "A"],
["Q", "J", "V", "D", "K", "X", "H", ";", ",", "."],
["COMBO_ALT1", "COMBO_ALT2", "COMBO_SFT", "COMBO"],
]
# 42-key keyboard example
# key_positions = [
# ["", "W", "L", "Y", "P", "B", "Z", "F", "O", "U", "'", ""],
# ["", "C", "R", "S", "T", "G", "M", "N", "E", "I", "A", ""],
# ["", "Q", "J", "V", "D", "K", "X", "H", ";", ",", ".", ""],
# ["", "COMBO_ALT1", "COMBO_ALT2", "COMBO_SFT", "COMBO", ""],
# ]
trigger_keys = ["COMBO"]
shifted_keys = ["COMBO_SFT"]
alt_keys = [["COMBO_ALT1"], ["COMBO_ALT2"], ["COMBO_ALT1", "COMBO_ALT2"]]
key_map = {
"'": "QUOT",
";": "SEMI",
",": "COMMA",
".": "DOT",
" ": "SPC",
"@": "AT",
"?": "QUESTION",
"←": "BSPC",
}
key_positions = [item for items in key_positions for item in items]
seen = {}
output = ""
macros = """#define MACRO(NAME, BINDINGS) \\
macro_##NAME: macro_##NAME { \\
compatible = "zmk,behavior-macro"; \\
#binding-cells = <0>; \\
wait-ms = <0>; \\
tap-ms = <10>; \\
bindings = <BINDINGS>; \\
};
"""
combos = (
"""#define COMBO(NAME, BINDINGS, KEYPOS) \\
combo_##NAME { \\
timeout-ms = <"""
+ str(combo_timeout)
+ """>; \\
bindings = <BINDINGS>; \\
key-positions = <KEYPOS>; \\
layers = <0>; \\
};
"""
)
line_no = 0
key_positions_map = {}
for i, key in enumerate(key_positions):
key_positions_map[key] = i
seen_positions = {}
def translate_keys(abbr):
result = []
for k in abbr:
k = k.upper()
if k in key_positions_map:
result.append(key_positions_map[k])
else:
raise Exception(
f'Unable to find key position for {k}, is it in "key_positions"?'
)
# ensure there is no duplicate positions used since zmk doesn't check
result.sort()
result = [str(i) for i in result]
name = " ".join(result)
if name in seen_positions:
raise Exception(
f"Error: duplicate combos on lines {seen_positions[name]} and {line_no}"
)
seen_positions[name] = line_no
return result
def translate_macro(word, capitalize=False):
result = []
for i, k in enumerate(word):
k = k.upper()
kp = "&kp "
if capitalize and i == 0:
kp += "LS("
if k in key_map:
kp += key_map[k]
else:
kp += k
if capitalize and i == 0:
kp += ")"
result.append(kp)
return result
print("Processing abbr.tsv")
with open("abbr.tsv") as file:
file = csv.reader(file, delimiter="\t")
for p in [";", ",", "."]:
name = f"c_{key_map[p]}"
macro = translate_macro(f"←{p} ")
positions = translate_keys([p] + trigger_keys)
macros += f'MACRO({name}, {" ".join(macro)})\n'
combos += f'COMBO({name}, ¯o_{name}, {" ".join(positions)})\n'
for line in file:
line_no += 1
if limit != 0 and line_no > limit:
print(f"Stopping at line {limit} due to limit setting")
break
if line[1]:
abbr = line.pop(1)
if abbr in seen:
raise Exception(
f'Error line {line_no}: already used trigger "{abbr}" for word "{seen[abbr]}"'
)
combinations = utils.find_all_combinations(abbr)
for a in combinations:
seen[a] = line[0]
for i, word in enumerate(line):
if not word:
continue
alt = []
if i != 0:
alt = alt_keys[i - 1]
name = f'c_{abbr}{"_" * i}'.replace("'", "_")
macro = translate_macro(word + " ")
positions = translate_keys(list(abbr) + trigger_keys + alt)
macros += f'MACRO({name}, {" ".join(macro)})\n'
combos += f'COMBO({name}, ¯o_{name}, {" ".join(positions)})\n'
# shifted
positions = translate_keys(
list(abbr) + trigger_keys + alt + shifted_keys
)
macro = translate_macro(word + " ", True)
macros += f'MACRO(s_{name}, {" ".join(macro)})\n'
combos += f'COMBO(s_{name}, ¯o_s_{name}, {" ".join(positions)})\n'
print("writing macros.dtsi")
with open("macros.dtsi", "w") as file:
file.write(macros)
with open("combos.dtsi", "w") as file:
file.write(combos)
print("writing combos.dtsi")
print("done")