Skip to content

Instantly share code, notes, and snippets.

@kyo-ago
Last active June 7, 2018 13:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kyo-ago/2b7ba15a68793792f941188498e29ea8 to your computer and use it in GitHub Desktop.
Save kyo-ago/2b7ba15a68793792f941188498e29ea8 to your computer and use it in GitHub Desktop.
Karabiner-Elementsのcomplex_modificationsを展開するやつ
let fs = require('fs');
let karabinerJsonPath = `${process.env.HOME}/.config/karabiner/karabiner.json`;
let conditionAppMap = {
browsers: [
"^com\\.google\\.Chrome$",
"^org\\.mozilla\\.firefox$",
"^com\\.apple\\.Safari$",
],
chrome: [
"^com\\.google\\.Chrome$",
],
jetbrains: [
"^com\\.jetbrains\\.",
],
};
let toConditionApp = (condition) => {
if (conditionAppMap[condition]) {
return {
"type": "frontmost_application_if",
"bundle_identifiers": conditionAppMap[condition]
};
}
if (condition.match(/^!/) && conditionAppMap[condition.replace(/^!/, '')]) {
return {
"type": "frontmost_application_unless",
"bundle_identifiers": [ conditionAppMap[condition.replace(/^!/, '')] ]
};
}
throw new Error(`Unknown ConditionAppMap "${condition}"`);
};
let conditionDeviceMap = {
barocco: {
"vendor_id": 1241,
"product_id": 323
},
apple: {
"vendor_id": 1452,
"product_id": 629
},
};
let toConditionDevice = (condition) => {
if (conditionDeviceMap[condition]) {
return {
"type": "device_if",
"identifiers": [ conditionDeviceMap[condition] ]
};
}
if (condition.match(/^!/) && conditionDeviceMap[condition.replace(/^!/, '')]) {
return {
"type": "device_unless",
"identifiers": [ conditionDeviceMap[condition.replace(/^!/, '')] ]
};
}
throw new Error(`Unknown ConditionDevice "${condition}"`);
};
let modifierMap = {
"shift": "shift",
"cmd": "command",
"com": "command",
"opt": "option",
"alt": "alt",
"ctrl": "control",
"*": "any",
};
let fromModifier = (base, short) => {
let result = base || {};
let keys = short.split('-').map((key) => modifierMap[key] || key);
result.key_code = keys.pop();
if (!keys.length) {
return result;
}
result.modifiers = result.modifiers || {};
if (keys.includes('any')) {
result.modifiers.optional = ['any'];
keys = keys.filter((key) => key !== 'any');
}
if (keys.find((key) => key.includes('?'))) {
let optional = keys
.filter((key) => key.includes('?'))
.map((key) => key.replace('?', ''));
result.modifiers.optional = (result.modifiers.optional || []).concat(optional);
keys = keys.filter((key) => !key.includes('?'));
}
if (!keys.length) {
return result;
}
result.modifiers.mandatory = keys;
return result;
};
let toModifierMap = {
'(': {
key: '9',
mod: 'shift'
},
')': {
key: '0',
mod: 'shift'
},
'{': {
key: 'open_bracket',
mod: 'shift'
},
'}': {
key: 'close_bracket',
mod: 'shift'
},
'<': {
key: 'comma',
mod: 'shift'
},
'>': {
key: 'period',
mod: 'shift'
},
'"': {
key: 'quote',
mod: 'shift'
},
"'": {
key: 'quote',
},
",": {
key: 'comma',
},
".": {
key: 'period',
},
" ": {
key: 'spacebar',
},
"=": {
key: 'equal_sign',
},
};
let toModifier = (base, short) => {
let results = short.split(/,/).filter((short) => short).reduce((base, short) => {
if (!short.match(/^'.+?'$/)) {
let to = fromModifier({}, short);
if (to.modifiers) {
to.modifiers = to.modifiers.mandatory;
}
return base.concat(to);
}
let results = short
.replace(/^'(.+?)'$/, '$1')
.split(/(?:)/)
.map((key) => {
if (!toModifierMap[key]) {
if (key.toLowerCase() === key) {
return { key_code: key };
}
return {
key_code: key.toLowerCase(),
modifiers: [ "shift" ]
};
}
if (!toModifierMap[key]['mod']) {
return { key_code: toModifierMap[key]['key'] };
}
return {
key_code: toModifierMap[key]['key'],
modifiers: [ toModifierMap[key]['mod'] ]
};
});
return base.concat(results);
}, []);
return (base || []).concat(results);
};
let toConditionLanguage = (lang) => ({
"type": "input_source_if",
"input_sources": [
{ "language": lang }
]
});
let ruleMaker = (rule) => {
if (rule[':manipulators']) {
rule.manipulators = (rule.manipulators || []).concat(rule[':manipulators']);
delete rule[':manipulators'];
}
let manipAttrs = Object.keys(rule).filter((key) => key.match(/^:/)).reduce((base, cur) => {
base[cur] = rule[cur];
delete rule[cur];
return base;
}, {});
rule.manipulators = rule.manipulators.map((manip) => {
if ("string" === typeof manip) {
let kv = manip.split(':');
manip = {};
manip[':' + kv.shift().trim()] = kv.join("S:").trim();
}
manip = Object.assign({}, manip, manipAttrs);
manip.type = manip.type || "basic";
if (manip[':app']) {
manip.conditions = (manip.conditions || []).concat(toConditionApp(manip[':app']));
delete manip[':app'];
}
if (manip[':device']) {
manip.conditions = (manip.conditions || []).concat(toConditionDevice(manip[':device']));
delete manip[':device'];
}
if (manip[':lang']) {
manip.conditions = (manip.conditions || []).concat(toConditionLanguage(manip[':lang']));
delete manip[':lang'];
}
if (manip[':from']) {
manip.from = fromModifier(manip.from, manip[':from']);
delete manip[':from'];
}
if (manip[':to']) {
manip.to = toModifier(manip.to, manip[':to']);
delete manip[':to'];
}
Object.keys(manip).filter((key) => key.match(/^:/)).forEach((key) => {
manip.from = fromModifier(manip.from, key.replace(/^:/, ''));
manip.to = toModifier(manip.to, manip[key]);
delete manip[key];
});
return manip;
});
return rule;
};
let rules = fs.readdirSync(__dirname)
.filter((file) => file.match(/\.json$/))
.filter((file) => !file.match(/^private/))
.map((file) => {
let path = `${__dirname}/${file}`;
try {
let json = eval(`(${fs.readFileSync(path)})`);
return (json.rules || (json.length ? json : [json])).map(ruleMaker);
} catch(e) {
console.error(e.message, path);
}
})
.filter((json) => json)
.reduce((base, cur) => base.concat(cur), [])
;
let karabinerJson = require(karabinerJsonPath);
karabinerJson.profiles
.filter((profile) => profile.selected)
.forEach((profile) => profile.complex_modifications.rules = rules)
;
fs.writeFileSync(karabinerJsonPath, JSON.stringify(karabinerJson, "", " "));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment