UNPKG

karabiner.ts-greg-mods

Version:
414 lines (413 loc) 13.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HrmBuilder = exports.qwertyLayout = exports.HrmKeyboardLayout = void 0; exports.getSideOfKey = getSideOfKey; exports.hrm = hrm; // Home row mods manipulator layer(s). // // Flaws & Limitations: // // * Can’t chain modifiers to get a chord. You need to press the mod keys // simultaneously to get two or three modifiers. // * If you want to remap some chords such as `r⌥+r` to `⏎`, you need to // define them as a manipulator in the HRM builder and as a standalone // manipulator. const karabiner_ts_1 = require("karabiner.ts"); const mod_tap_1 = require("./mod-tap"); const mod_tap_layer_1 = require("./mod-tap-layer"); const karabiner_extra_1 = require("./karabiner-extra"); /** * Generates all unique ordered pairs from an array or iterable. */ function getDoubles(keys) { const arr = Array.from(keys); const out = []; for (let i = 0; i < arr.length; i++) { for (let j = 0; j < arr.length; j++) { if (i !== j) { out.push([arr[i], arr[j]]); } } } return out; } /** * Generates all unique ordered triples from an array or iterable. */ function getTriples(keys) { const arr = Array.from(keys); const out = []; for (let i = 0; i < arr.length; i++) { for (let j = 0; j < arr.length; j++) { for (let k = 0; k < arr.length; k++) { if (i !== j && i !== k && j !== k) { out.push([arr[i], arr[j], arr[k]]); } } } } return out; } class HrmKeyboardLayout { leftHandKeys; rightHandKeys; constructor(leftHandKeys, rightHandKeys) { this.leftHandKeys = leftHandKeys.map((k) => (0, karabiner_ts_1.getKeyWithAlias)(k)); this.rightHandKeys = rightHandKeys.map((k) => (0, karabiner_ts_1.getKeyWithAlias)(k)); } } exports.HrmKeyboardLayout = HrmKeyboardLayout; const qwertyLeftHandKeys = [ "q", "w", "e", "r", "t", "a", "s", "d", "f", "g", "z", "x", "c", "v", "b", "escape", "1", "2", "3", "4", "5", "tab", "caps_lock", ]; const qwertyRightHandKeys = [ "y", "u", "i", "o", "p", "[", "]", "\\", "h", "j", "k", "l", ";", "'", "n", "m", ",", ".", "/", "␣", "⏎", "6", "7", "8", "9", "0", "hyphen", "equal_sign", "delete_or_backspace", "left_arrow", "up_arrow", "down_arrow", "right_arrow", "page_up", "page_down", ]; exports.qwertyLayout = new HrmKeyboardLayout(qwertyLeftHandKeys, qwertyRightHandKeys); /** * Given a key and a keyboard layout, returns which side ('left' | 'right') the key is on, * or null if the key is not found in either hand. */ function getSideOfKey(key, layout) { if (layout.leftHandKeys.includes(key)) { return "left"; } else if (layout.rightHandKeys.includes(key)) { return "right"; } else { return null; } } class SmartModifierMap { elems = new Map(); get(modifierParam) { let modifiers = (0, karabiner_ts_1.parseModifierParam)(modifierParam); if (!modifiers || modifiers.length != 1) { throw new Error(`Expected a single modifier, but got ${JSON.stringify(modifierParam)}.`); } let modifier = modifiers[0]; let elems = this.elems.get(modifier) ?? []; if ((0, karabiner_extra_1.isSidedMod)(modifier)) { const unsidedModifier = (0, karabiner_extra_1.getUnsidedMod)(modifier); elems = elems.concat(this.elems.get(unsidedModifier) ?? []); } return elems; } add(modifierParam, elem) { let modifiers = (0, karabiner_ts_1.parseModifierParam)(modifierParam); if (!modifiers || modifiers.length != 1) { throw new Error(`Expected a single modifier, but got ${JSON.stringify(modifierParam)}.`); } let modifier = modifiers[0]; this.elems.set(modifier, (this.elems.get(modifier) ?? []).concat([elem])); return this; } } class HrmBuilder { // A map of keys to their modifiers, e.g., a ⇒ ⌃, s ⇒ ⌥, d ⇒ ⇧. hrmKeys; keyboardLayout; // Manipulators that override the default mod-tap behavior. smartManipulatorMap = new SmartModifierMap(); smartKeyOverrideMap = new SmartModifierMap(); isLazy = false; chosenHoldTapStrategy = "permissive-hold"; isChordalHold = true; hasTriples = true; simultaneousThresholdMs; tappingTermMs; constructor(hrmKeys, layout = exports.qwertyLayout) { this.hrmKeys = hrmKeys; this.keyboardLayout = layout; } /** * Sets whether the modifiers are lazy. */ lazy(isLazy) { this.isLazy = isLazy; return this; } /** * Sets the hold tap strategy for the builder. */ holdTapStrategy(strategy) { this.chosenHoldTapStrategy = strategy; return this; } /** * Enables or disables chordal hold. */ chordalHold(isChordalHold) { this.isChordalHold = isChordalHold; return this; } /** * Sets whether the builder should generate 3-key chords. */ triples(hasTriples) { this.hasTriples = hasTriples; return this; } /** * Sets the simultaneous threshold in milliseconds. * * Simultaneous threshold is the maximum time in milliseconds Karabiner will wait for a simultaneous * chord to appear for doubles and triples. */ simultaneousThreshold(simultaneousThresholdMs) { this.simultaneousThresholdMs = simultaneousThresholdMs; return this; } /** * Sets the tapping term in milliseconds. */ tappingTerm(tappingTermMs) { this.tappingTermMs = tappingTermMs; return this; } build() { // Tuple manipulators need to come first, so that a more generic condition // doesn't trigger first. let manipulators = this.tupleManipulators(); manipulators = manipulators.concat(this.singleManipulators()); return manipulators; } smartManipulator(mod, manipulator) { if ("build" in manipulator) { for (const m of manipulator.build()) this.smartManipulatorMap.add(mod, m); } else { this.smartManipulatorMap.add(mod, manipulator); } return this; } smartManipulators(mod, ...manipulators) { for (const manipulator of manipulators) { this.smartManipulator(mod, manipulator); } return this; } /** * Adds some keys that override the default hold-tap strategy. */ keys(mod, keys, strategy) { for (const key of keys) { this.smartKeyOverrideMap.add(mod, [ (0, karabiner_ts_1.getKeyWithAlias)(key), strategy, ]); } return this; } /** * Creates all chorded modifiers for the home row mods. */ tupleManipulators() { const hrmKeys = this.hrmKeys; let manipulators = []; for (const side of karabiner_extra_1.sides) { const sideKeys = Array.from(hrmKeys.keys()).filter((k) => (0, karabiner_extra_1.getSideOfMod)(hrmKeys.get(k)) === side); if (this.hasTriples) { for (const [first, second, third] of getTriples(sideKeys)) { let mt = (0, mod_tap_1.modTap)() .from([first, second, third], [], "any") .modifiers((0, karabiner_ts_1.toKey)(hrmKeys.get(first), [ hrmKeys.get(second), hrmKeys.get(third), ])) .permissive(true); if (this.simultaneousThresholdMs) { mt.simultaneousThreshold(this.simultaneousThresholdMs); } if (this.tappingTermMs) { mt.tappingTerm(this.tappingTermMs); } manipulators = manipulators.concat(mt.build()); } } for (const [first, second] of getDoubles(sideKeys)) { let mt = (0, mod_tap_1.modTap)() .from([first, second], [], "any") .modifiers((0, karabiner_ts_1.toKey)(hrmKeys.get(first), hrmKeys.get(second))) .permissive(true); if (this.simultaneousThresholdMs) { mt.simultaneousThreshold(this.simultaneousThresholdMs); } if (this.tappingTermMs) { mt.tappingTerm(this.tappingTermMs); } manipulators = manipulators.concat(mt.build()); } } return (0, karabiner_ts_1.withCondition)((0, karabiner_ts_1.ifVar)(karabiner_extra_1.layerVarName).unless())(manipulators); } singleManipulators() { let manipulators = []; for (const [hrmKey, hrmMod] of this.hrmKeys.entries()) { manipulators = manipulators.concat(this.singleManipulatorLayer(hrmKey, hrmMod)); } return manipulators; } singleManipulatorLayer(hrmKey, hrmMod) { const side = (0, karabiner_extra_1.getSideOfMod)(hrmMod); if (side === null) { throw new Error(`Expected a sided modifier, but got ${JSON.stringify(hrmMod)}.`); } let smartManipulators = this.smartManipulatorMap.get(hrmMod); let mtLayer = (0, mod_tap_layer_1.modTapLayer)(hrmKey, hrmMod) .allowAnyModifiers() .lazy(this.isLazy); for (const sm of smartManipulators) { const smFrom = (0, karabiner_extra_1.getFromKeyCodeFromBasicManipulator)(sm); if (smFrom === null) { throw new Error(`Expected a from key code in a smart manipulator but got ${JSON.stringify(sm)}.`); } if (!(0, karabiner_extra_1.isFromAndToKeyCode)(smFrom)) { throw new Error(`Expected a from and to key code in a smart manipulator but got ${JSON.stringify(smFrom)}.`); } const smSide = getSideOfKey(smFrom, this.keyboardLayout); if (smSide === null) { throw new Error(`Could not determine the side of the smart manipulator key, ${JSON.stringify(smFrom)}.`); } if (this.isChordalHold && smSide === side) { mtLayer.slowManipulators(sm); } else { switch (this.chosenHoldTapStrategy) { case "permissive-hold": mtLayer.permissiveHoldManipulators(sm); break; case "hold-on-other-key-press": mtLayer.holdOnOtherKeyPressManipulator(sm); break; case "slow": mtLayer.slowManipulators(sm); break; default: throw this.chosenHoldTapStrategy; } } } let keyOverrides = this.smartKeyOverrideMap.get(hrmMod); for (const [key, strategy] of keyOverrides) { switch (strategy) { case "permissive-hold": mtLayer.permissiveHoldKey(key); break; case "hold-on-other-key-press": mtLayer.holdOnOtherKeyPressKeys([key]); break; case "slow": mtLayer.slowKeys([key]); break; default: throw strategy; } } let smartKeys = side === "left" ? this.keyboardLayout.rightHandKeys : this.keyboardLayout.leftHandKeys; let slowKeys = side === "left" ? this.keyboardLayout.leftHandKeys : this.keyboardLayout.rightHandKeys; switch (this.chosenHoldTapStrategy) { case "permissive-hold": mtLayer.permissiveHoldKeys(...smartKeys); break; case "hold-on-other-key-press": mtLayer.holdOnOtherKeyPressKeys(smartKeys); break; case "slow": mtLayer.slowKeys(smartKeys); break; default: throw this.chosenHoldTapStrategy; } if (this.isChordalHold) { mtLayer.slowKeys(slowKeys); } else { switch (this.chosenHoldTapStrategy) { case "permissive-hold": mtLayer.permissiveHoldKeys(...slowKeys); break; case "hold-on-other-key-press": mtLayer.holdOnOtherKeyPressKeys(slowKeys); break; case "slow": mtLayer.slowKeys(slowKeys); break; default: throw this.chosenHoldTapStrategy; } } // Let the primary mouse button benefit from modifiers. mtLayer.holdOnOtherKeyPressManipulator((0, karabiner_ts_1.mapPointingButton)("button1").to((0, karabiner_ts_1.toPointingButton)("button1", hrmMod))); if (this.tappingTermMs) { mtLayer.tappingTerm(this.tappingTermMs); } return mtLayer.build().manipulators; } } exports.HrmBuilder = HrmBuilder; function hrm(hrmKeys, layout = exports.qwertyLayout) { return new HrmBuilder(hrmKeys, layout); }