Building on what's provided by karabiner.ts, I'm generating my Karabiner-Elements karabiner.json
config programmatically. To fit my use cases, I added utilities to
- create simple and function key modifications
- add modifications to multiple profiles and optionally devices within
This post goes more into my motivations for building this and has more general research on Karabiner-Elements.
karabiner.ts
deserves a shoutout for providing a fantastic developer experience. It allows you to concisely create modifications with auto-completion thanks to very through TypeScript typings. Given that and it's high-level of parity with Karabiner features, I recommend it highly!
Also check out the other external Karabiner JSON generators.
Prerequisites:
- Install Karabiner-Elements
- In the Karabiner-Elements UI, create profiles with names that exactly match what you'll be writing
- Optional: Get the
product_id
andvendor_id
for any devices you'll want to modify- Profile devices don't auto-generate, one workaround is to add a simple modification in the UI for that profile device
- Fork this repo, run
npm install
Then, make customizations in the my-config
directory.
Commands in package.json
:
# Generate test output in the my-config/test-output directory
npm run test
# Overwrite root karabiner.json file with my config
npm run write
You'd probably want to take some time to explore and make your modifications before running the write
command!
The majority of the files in the my-config
directory utilize karabiner.ts
to create simple modifications. Taking a look at my-config/simple.ts
might be a good place to start.
The my-config/myconfig.ts
file uses what's in the utils
directory to finalize modifications. Here's a reduced sample that writes caps_lock
to escape
, my preferred function key assignments, and a complex modification that runs the karabiner cli to switch a profile:
import { toComplexProfile, toSimpleProfile } from '../utils/kts-wrappers';
import { Writer } from '../utils/writer';
import { func } from './func';
import { selectLayerProfile } from './select-profile';
import { capsToEscape } from './simple';
export const MY_DEVICE = { product_id: 24, vendor_id: 42 };
export const MY_PROFILE_NAME = 'Main';
export const configWriter = new Writer();
/**
* Modify keys only for MY_DEVICE
*/
configWriter.makeSimpleMods(
MY_PROFILE_NAME,
toSimpleProfile(capsToEscape),
MY_DEVICE
);
/**
* Modify keys only for MY_DEVICE
*/
configWriter.makeFunctionMods(
MY_PROFILE_NAME,
toSimpleProfile(func),
MY_DEVICE
);
/**
* complex_modifications apply to all devices
*/
configWriter.makeComplexMods(
MY_PROFILE_NAME,
toComplexProfile(selectLayerProfile)
);
These functions are imported from utils/kts-wrappers.ts
.
toComplexProfile
is a light wrapper to build an array of complex rules.toSimpleProfile
has a little more going on. Sincesimple_modifications
aren't provided out of the box withkarabiner.ts
, this function takes a single mod or array of mods, peels out the unneeded complex properties ("description", "manipulators", "type", etc), and returns an array of simple objects like what's shown below:
[
{
"from": {
"key_code": "SOME_KEY"
},
"to": {
"key_code": "SOME_OTHER_KEY"
}
}
]
Note: There are limits on the capabilities of simple_modifications
. Documentation is lacking on them, so proceed with caution.
The Writer
class (imported from utils/writer.ts
) is used in lieu of writeToProfile
provided by karabiner.ts
. Initializing an instance of Writer
puts your current Karabiner config into memory. The methods provided by an instance of Writer
modify that instance - nothing is "saved" until one of the write...
functions is called.
After your modifications are in place, you'll probably want to test them out. Use writeToFile
to output a JSON file to a given path:
import path from 'path';
import { Writer } from '../utils/writer';
export const configWriter = new Writer();
// ... make your modifications ...
const testOutputPath = path.resolve(
'.',
'my-config',
'test-output',
'_root.json'
);
configWriter.writeToFile(testOutputPath);
Use overwriteRootConfig()
to finish the job. It writes the in-memory modifications to disk at the default karabiner.json
path:
configWriter.overwriteRootConfig();
This function automatically generates a backup.
Note: The time-stamp for the backup file format is what's provided by Date.now()
: Returns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).
Example: ~/.config/karabiner/automatic_backups/karabiner_1690062724842.json
(This is different from Karabiner's YYYYMMDD
format.)
If for some reason you want to create just a back up the root config, you can run:
configWriter.backupRootConfig();