Last active
June 7, 2018 13:44
-
-
Save kyo-ago/2b7ba15a68793792f941188498e29ea8 to your computer and use it in GitHub Desktop.
Karabiner-Elementsのcomplex_modificationsを展開するやつ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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