Encontré Goku (ver respuesta de furins) y algunas otras herramientas pero ninguna parecía más fácil que editar el JSON. Al final escribí mis propios scripts que compartiré aquí.
Hay dos archivos de Python:
decompose.py
toma un JSON de karabiner y lo divide en muchos archivos y directorios, para que puedas editar una regla a la vez.
compose.py
toma estos directorios y los enrolla en un único JSON que karabiner aceptará.
Mantengo tanto el JSON único como los directorios descompuestos (así como el script) en mi repo de dotfiles. Cuando quiero alterar un keybind, creo un directorio para él (el método más fácil es copiar uno existente y modificarlo) y luego hago una codificación/decodificación de ida y vuelta. Por ejemplo..: python compose.py; python decompose.py; python compose.py
.
Después de esto mi primer paso es comprobar git diff
para ver si mi nueva regla tiene algún error que lo haya roto todo. Si es así, tengo muchas opciones para revertir los cambios a través de git
y lo intento de nuevo. Una vez hecho esto, pruebo el keybind con Karabiner-EventViewer y su caso de uso previsto, y lo confirmo.
Los scripts son obviamente limitados y poco prácticos, porque son para uso personal (y creo que la solución "adecuada" para esto sería simplemente no usar un Mac, har har). Sugiero empezar con un JSON conocido que funcione como Atajos estilo PC para que puedas ver cómo funcionan las normas existentes. Funcionan razonablemente bien, pero algunas advertencias:
- Cada regla va en un directorio. El nombre del directorio se construye a partir de un número (para evitar que las reglas cambien de orden y confundan a git) y el nombre de la regla. Obviamente, los nombres de las reglas pueden tener caracteres especiales, pero los directorios no, por lo que hay una extraña normalización que tuve que hacer con eso. Puede ser un poco frágil si haces cosas extravagantes con el nombre de la regla.
- Uso PyCharm que tiene su propio interceptor de atajos de teclado. Debido a esto, todas las reglas están en la lista negra de PyCharm en la etapa de composición/descomposición.
compose.py:
import json
from pathlib import Path
p_decomposed = Path('decomposed/')
# Load scaffold
p_scaffold = p_decomposed / 'scaffold.json'
with p_scaffold.open() as f:
scaffold = json.load(f)
# Load rules
p_rules = p_decomposed / 'rules'
for p_rule in sorted(p_rules.iterdir()):
if p_rule.stem.startswith('.'):
continue
print(p_rule)
p_rule_json = p_rule / 'rule.json'
with p_rule_json.open() as f:
rule = json.load(f)
p_manipulators = p_rule / 'manipulators'
for p_manipulator in sorted(p_manipulators.iterdir()):
with p_manipulator.open() as f:
j = json.load(f)
rule['manipulators'].append(j)
profiles = scaffold['profiles']
first_prof = profiles[0]
complex_mods = first_prof['complex_modifications']
rules = complex_mods['rules']
rules.append(rule)
p_composed = Path('karabiner.json')
with p_composed.open('w') as f:
json.dump(scaffold, f, indent=4)
descomponer.py:
import json
from pathlib import Path
with open('karabiner.json') as f:
j = json.load(f)
profiles = j['profiles']
first_prof = profiles[0]
complex_mods = first_prof['complex_modifications']
rules = complex_mods['rules']
# Dump everything except the rules into a "scaffold file"
complex_mods['rules'] = []
with open('decomposed/scaffold.json', 'w') as f:
json.dump(j, f, indent=4)
def normalize_rule_name(raw_name):
"""
Normalize rule name by removing special characters, to make it suitable
for use as a file name.
"""
lowered = raw_name.lower()
filtered = ''
for c in lowered:
if c.isalnum():
filtered += c
else:
filtered += '-'
while '--' in filtered:
filtered = filtered.replace('--', '-')
if filtered.endswith('-'):
filtered = filtered[:-1]
return filtered
def blacklist_pycharm(manipulator):
pattern = "^com\\.jetbrains\\."
if 'conditions' not in manipulator:
return
for c in manipulator['conditions']:
if c.get('type', '') != 'frontmost_application_unless':
continue
if pattern not in c['bundle_identifiers']:
c['bundle_identifiers'].append(pattern)
def process_manipulator(manipulator):
"""
Gets applied to every manipulator before dumping it to a file.
"""
result = dict(manipulator)
blacklist_pycharm(result)
return result
# Dump each rule to a separate file
for n, rule in enumerate(rules):
# Normalize name
desc = rule['description']
desc_norm = normalize_rule_name(desc)
# Pull out manipulators and save the rest
manipulators = rule['manipulators']
rule['manipulators'] = []
p_rule = Path('decomposed/rules') / f'{n:03d}_{desc_norm}' / 'rule.json'
p_rule.parent.mkdir(exist_ok=True)
with p_rule.open('w') as f:
json.dump(rule, f, indent=4)
# Dump each manipulator
p_manipulators = p_rule.parent / 'manipulators'
p_manipulators.mkdir(exist_ok=True)
for i, manipulator in enumerate(manipulators):
p_m = p_manipulators / f'{i+1}.json'
with p_m.open('w') as f:
json.dump(process_manipulator(manipulator), f, indent=4)