{"id":23469064,"url":"https://github.com/al-ce/karaml","last_synced_at":"2025-04-14T15:35:33.127Z","repository":{"id":152286215,"uuid":"605350903","full_name":"al-ce/karaml","owner":"al-ce","description":"Write and update your Karabiner-Elements config in YAML","archived":false,"fork":false,"pushed_at":"2024-12-21T21:25:42.000Z","size":498,"stargazers_count":27,"open_issues_count":4,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-10T05:46:05.915Z","etag":null,"topics":["karabiner","karabiner-configurator","karabiner-elements","keyboard-layout","macos","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/al-ce.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-02-23T01:08:12.000Z","updated_at":"2024-12-25T14:35:20.000Z","dependencies_parsed_at":null,"dependency_job_id":"f1af21c1-dade-47af-8d81-7f63940a47b8","html_url":"https://github.com/al-ce/karaml","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/al-ce%2Fkaraml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/al-ce%2Fkaraml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/al-ce%2Fkaraml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/al-ce%2Fkaraml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/al-ce","download_url":"https://codeload.github.com/al-ce/karaml/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248906923,"owners_count":21181245,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["karabiner","karabiner-configurator","karabiner-elements","keyboard-layout","macos","python"],"created_at":"2024-12-24T14:59:01.551Z","updated_at":"2025-04-14T15:35:33.103Z","avatar_url":"https://github.com/al-ce.png","language":"Python","funding_links":["https://github.com/sponsors/tekezo"],"categories":[],"sub_categories":[],"readme":"# karaml 🍮\n\n**karaml** (**_Kara_**biner in ya**_ml_**) lets you write and maintain a\nvirtual layers-based [Karabiner-Elements](https://karabiner-elements.pqrs.org/) keyboard customization\nconfiguration in YAML. It uses Python to translate the YAML into\nKarabiner-compatible JSON.\n\nkaraml is based on the philosophy of [mxstbr](https://github.com/mxstbr)'s\nlayer-centered Karabiner [config](https://github.com/mxstbr/karabiner), and my\nthanks goes to to [mxstbr](https://github.com/mxstbr) and\n[yqrashawn](https://github.com/yqrashawn/) for the inspiration for this\nproject. Immense thanks to [tekezo](https://github.com/tekezo) for\nKarabiner-Elements (consider\n[sponsoring/donating](https://github.com/sponsors/tekezo)).\n\n```yaml\n# Default layer, does not require activation\n/base/:\n  caps_lock: [escape, /nav/]  # Escape when tapped, /nav/ layer when held\n  # Separate modifiers and keycodes with a pipe `|`\n  # Lowercase modifiers = left, uppercase = right\n  oc | n: /nav/               # Tap left opt + left ctrl + n to toggle /nav/\n  O  | w: \u003co-backspace\u003e       # right opt + w to left opt + Backspace\n\n  # Enter when tapped, Left Control when held, lazy flag, any optional modifiers\n  (x) | enter:\n    - enter\n    - left_control\n    - null # No event when released\n    - [+lazy]\n\n  j+k: [escape, button1] # j+k to escape when tapped, left click when held\n\n  # option (either side) + o/O to create new line below/above\n  a | o: m |right + return\n  a | O: up + m | right + return\n\n  a | h: string(hello world) # Send multiple chars without concatenating with +\n\n  # backspace/left on tap, MacOS/Kitty hints on hold/release, depending on frontmost app\n  c | h: {\n      # Notification with Karabiner-Style popup\n      unless Terminal$ kitty$:\n        [backspace, \"notify(idh, My MacOS Shortcut Hints)\", \"notifyOff(idh)\"],\n      # Notification with AppleScript popup\n      if Terminal$ kitty$: [left, \"shnotify(My Kitty Shortcuts, ==Kitty==)\"],\n    }\n\n  # Other syntax for modifiers is also supported\n  ms   a: app(Alacritty) # left_command + left_shift + a to launch Alacritty\n  \u003cms-c\u003e: app(CotEditor) # left_command + left_shift + c to launch CotEditor\n\n  # You can use Unicode symbols for modifiers instead of letters\n  ⌘ ⇧    | g: string(lazygit)    # command + shift + g to send string 'lazygit'\n  ⌃› ‹⌥  | s: string(git status) # right_control + left_option + s to send string 'git status'\n  ☆      | o: /open/             # hyper + o to toggle /open/ layer\n\n  # Utilize user-defined aliases\n  tab  : [tab, ⁙]      # tab to left opt, ctrl, and shift when held\n  ⁙ | ⏎: screen_saver  # left opt, ctrl, shift, and enter starts screen saver\n\n  # Utilize user-defined templates\n  ⌘ ⇧ | f: rectangle(fullscreen)\n\n# condition 'nav_layer' must be true for the following maps\n/nav/:\n  (x) | h: left # vim navigation with any optional mods\n  (x) | j: down\n  (x) | k: up\n  (x) | l: right\n\n  s: [app(Safari), /sys/]     # Launch Safari on tap, /sys/ layer when held\n  g: open(https://github.com) # Open link in default browser\n\n# etc.\n? /sys/\n  System Layer\n: # ↑ inserts a description for the layer into karabiner.json\n\n  o | m: [mute, null, mute] # To mute if held/key down, unmute if released/key up\n  k  : play_or_pause\n  \"}\": fastforward\n  u  : shell(open -b com.apple.ScreenSaver.Engine) # Start Screen Saver\n\n# User defined aliases\naliases:\n  ⁙: ⌥ ⌃ ⇧\n  ⏎: return_or_enter\n  screen_saver: shell(open -b com.apple.ScreenSaver.Engine)\n\n# User defined templates\ntemplates:\n  rectangle: open -g \"rectangle-pro://execute-action?name=%s\"\n\n\n# JSON integration - any Karabiner JSON can be added here\njson:\n  - {\n      description: \"Right Shift to ) if tapped, Shift if held\",\n      from: { key_code: right_shift },\n      to: { key_code: right_shift, lazy: true },\n      to_if_alone: { key_code: 9, modifiers: [shift] },\n      type: basic,\n    }\n```\n\n## ✨ Features\n\n- Complex modifications on a single line of YAML so you can create and update keymaps quickly\n- Events for when a key is tapped, held, and released defined by position in an\n  array (rather than k/v pairs)\n- Simple schema for requiring mandatory or optional modifiers in a pseudo-Vim\n  style\n- Multiple frontmost-app conditional remaps in a single YAML map\n- Aliases for symbols, shifted keys, and complex names (e.g.\n  `grave_accent_and_tilde` → `grave`, `left_shift` + `[` → `{` )\n- Define your own aliases for keycodes and modifiers!\n- Template for app launchers, shell commands,\n  notifications, and more by default\n- Define your own templates for shell commands!\n- Accepts regular Karabiner JSON in an 'appendix' table so all cases Karaml\n  can't or doesn't plan to handle can still be in one config\n- Automatically update your `karabiner.json` or write to the complex\n  modifications folder and import with the Karabiner GUI - no need to handle\n  any files other than your `.yaml` config\n- Checks and formatting hints for your `.yaml` file - karaml will try not to\n  let you upload a config that doesn't create a working modification, and tell\n  you why (no more hunting for typos or missing/extraneous commas in a large JSON object)\n\n## ❓ Why this project\n\nThe `karabiner.json` file can be hard to manage and visualize as it grows, as\nthe JSON format requires a lot of lines, carefully managed opening and closing\nquotes, brackets, and commas, and repetitive conditional logic.\n\n**karaml** tries to simplify this by providing a more readable, maintainable,\nand easy to adjust format in YAML. This means making some trade-offs in\ncompleteness of features, but I try to come close with the goal of balancing\nfeatures and configuration simplicity. To prevent the need for maintaining\nmultiple configurations if karaml falls short of your needs, karaml also\nsupports an 'appendix' table for any Karabiner-compatible JSON that can't be\nexpressed in karaml.\n\n### Why YAML?\n\n- Easy to maintain: no mandatory quotes, but quotes can simplify escaping\n  troublesome characters; minimal or no use of brackets around keys/values\n- [Easy to learn](https://learnxinyminutes.com/docs/yaml/) and easy to read\n- A superset of JSON which allows us to fall back to JSON if wanted/needed\n- Lets you leave comments for descriptions or notes to yourself!\n\n## ⚡️ Quickstart\n\nIf you're unfamiliar with YAML, take a look at this handy\n[guide](https://learnxinyminutes.com/docs/yaml/).\n\nAfter [installing](#installation), take the [sample YAML\nconfiguration](./docs/sample_configuration.yaml) and use it as a template for\nyour own. The sample config has comments to explain karaml syntax.\n\nFor more detailed explanations, follow the [configuration\nguide](/docs/config_guide.md) and read the [YAML\nConfiguration](#️-yaml-configuration) section of this document.\n\nFor a cleaner, less commented on example, see one of my configurations\n[here](./docs/al-ce_config.yaml). Follow the [usage instructions](#-usage)\nbelow to convert your karaml config to Karabiner-JSON with the command-line\ntool (written in Python).\n\n## ⚙️ YAML Configuration\n\n### Basic Mapping and Layer Structure\n\nAll keys belong to a layer. At minimum, a map requires a `from: to` structure\nand must be in a layer:\n\n```yaml\n/layer_name/:\n  from_key: to_key\n```\n\nThis is equivalent to:\n\n```json\n{\n  \"from\": { \"key_code\": \"from_key\" },\n  \"to\": { \"key_code\": \"to_key\" } \"type\": \"basic\",\n  \"conditions\": [ { \"name\": \"layer_name_layer\", \"type\": \"variable_if\", \"value\": 1 } ]\n}\n```\n\nIn your karaml config, you need to add a `/base/` layer, but only as a matter\nof convention. Maps in the `/base/` layer don't check for conditions.\n\nTo add additional events or options to your maps, put them in a YAML array or\nsequence.\n\n```yaml\n/layer_name/: # All mappings below require the 'layer_name' layer enabled\n  from_key(s): [when_tapped, when_held, when_released, [to_opts], { params }]\n```\n\nAs shown above, you can add as many or as few items to the array as you like.\n\n### Enabling Layers\n\nIn the 'to' part of the map, enable layers with `/layer_name/`. In the first\nposition, the layer will be tap-toggled by the 'from'. In the second position,\nthe layer will be enabled when the 'from' key is held.\n\n```yaml\n/base/:\n  oc | n: /nav/ # enabled/disabled on tap\n  caps_lock: [escape, /nav/] # enabled when held\n\n# The maps indented in this layer will only work when the layer is enabled\n/nav/:\n  h: left\n  j: down\n  k: up\n  l: right\n```\n\n### Modifiers\n\nTo add modifiers to a primary key, follow the format `\u003cmodifiers-primary_key\u003e`.\nWrap optional modifiers in parens.\n\nTo add modifiers to a primary key, there are a few available formats. The\ngeneral format is to have modifiers followed by a delimiter followed by\nprimary keys. Multiple modifiers can have whitespace between them if they\nhelp readability, or they can be joined without any whitespace. Optional\nmodifiers are indicated by wrapping them in parens.\n\nThe original format for modifiers was `\u003cmodifiers-primary_key\u003e`, but others\nhave been added to give the user a choice in whatever format they find most\nreadable.\n\nThe following are all examples of the mapping for `left_control` +\n`left_shift` + `a` to open the Terminal app:\n\n```yaml\n/base/:\n  \u003ccs-a\u003e   : app(Terminal)  # A vim-ish syntax\n  cs-a     : app(Terminal)  # Angle brackets are optional\n  c s  - a : app(Terminal)  # Whitespace is flexible\n  c s  | a : app(Terminal)  # Use a pipe or a dash as delimiter\n  c s    a : app(Terminal)  # Or use the final whitespace as the delimiter\n  'c s | a': app(Terminal)  # Quotes may help you with escape characters\n```\n\nThe guides and the remainder of the `README` (mostly) use the\n`modifiers | primary_key` format, with the pipe char `|` as the delimiter.\n\nWhether the optional set in parens comes first or last doesn't matter, e.g.\n`(c)os | g` and `os(c) | g` are both valid. But a single set of optional\nmodifiers in parens must be to the right or left of _all_ mandatory modifiers\n(if there are any mandatory modifiers).\n\nSingle letters are one way of specifying modifiers. Left side modifiers use\nlowercase, right side modifiers use uppercase.\n\n| karaml alias | key_code       | --- | karaml alias | key_code        |\n| ------------ | -------------- | --- | ------------ | --------------- |\n| `c`          | `left_control` | --- | `C`          | `right_control` |\n| `s`          | `left_shift`   | --- | `S`          | `right_shift`   |\n| `o`          | `left_option`  | --- | `O`          | `right_option`  |\n| `m`          | `left_command` | --- | `M`          | `right_command` |\n| `r`          | `control`      | --- | `R`          | `control`       |\n| `h`          | `shift`        | --- | `H`          | `shift`         |\n| `a`          | `option`       | --- | `A`          | `option`        |\n| `g`          | `command`      | --- | `G`          | `command`       |\n| `f`          | `fn`           | --- | `F`          | `fn`            |\n| `l`          | `caps_lock`    | --- | `L`          | `caps_lock`     |\n| `x`          | `any`          | --- | `X`          | `any`           |\n\n\nExamples:\n\n- `c      | h` → `left_ctrl` + `h`\n- `(x)    | h` → `h` (with any optional modifiers)\n- `mOC(s) | h` → `left_cmd` + `right_opt` + `right_ctrl` + `left_shift` (optional) + `h`\n- `(s)mOC | h` → (same as above)\n- `g(arh) | h` → `cmd` (mandatory) + `option` + `control` + `shift` (optional) + `h`\n- `(arh)g | h` → (same as above)\n\nThe mnemonics for the less-obvious modifiers are:\n\n- `m`: `M`ac\n- `r`: cont`R`ol\n- `h`: s`H`ift\n- `a`: `A`lt\n- `g`: `G`ui\n\nAn alternative system for modifiers uses Unicode symbols: ⌘ ⌥ ⌃ ⇧\nThe Unicode symbols `‹` and `›` denote left and right side modifiers.\n\n| Unicode symbol | key_code       |\n| -------------- | -------------- |\n| `⌘`            | `command` |\n| `⌥`            | `option` |\n| `⌃`            | `control` |\n| `⇧`            | `shift` |\n| `‹⌘`            | `left_command` |\n| `‹⌥`            | `left_option` |\n| `‹⌃`            | `left_control` |\n| `‹⇧`            | `left_shift` |\n| `⌘›`            | `right_command` |\n| `⌥›`            | `right_option` |\n| `⌃›`            | `right_control` |\n| `⇧›`            | `right_shift` |\n\nExamples:\n\n- `⌘   ⇧  |  g` → `command` + `shift` + `g`\n- `⌘  (⇧) |  g` → `command` + `shift` (optional) + `g`\n- `‹⌃     |  h` → `left_ctrl` + `h`\n- ` ⌃› ‹⌥ |  h` → `left_opt` + `right_ctrl` + `h`\n\nOf course you can use another valid syntax for the Unicode characters as well,\ne.g. `\u003c⌘⇧-g\u003e` or `⌘⇧ g`, etc.\n\nIf you need an easy way to type these Unicode characters, you can use a\ntext-expander or snippet tool like Typinator, the built in expanders in Raycast\nor Alfred, TextExpander, etc. Or, set your own alias with the [user-defined\naliases](#defining-your-own-aliases) feature.\n\nOr, see this suggestion in this Karabiner [issue](https://github.com/pqrs-org/Karabiner-Elements/issues/2949#issuecomment-1318516074).\n\n### Key-Code Aliases\n\nYou can follow the explicit mapping for any key (e.g. `s | 1` → `!`), or use\nthese available aliases. Be mindful in your YAML config that some characters\nneed to be escaped or wrapped in quotes to be recognized as strings.\n\n| karaml alias | Karabiner key_code            |\n| ------------ | ----------------------------- |\n| `enter`        | `return_or_enter`               |\n| `CR`           | `return_or_enter`               |\n| `ESC`          | `escape`                        |\n| `backspace`    | `delete_or_backspace`           |\n| `BS`           | `delete_or_backspace`           |\n| `delete`       | `delete_forward`                |\n| `space`        | `spacebar`                      |\n| ` ` (a space)  | `spacebar`                      |\n| `SPC`          | `spacebar`                      |\n| `spc`          | `spacebar`                      |\n| `-`            | `hyphen`                        |\n| `underscore`   | `hyphen` + `shift`                |\n| `_`           | `hyphen` + `shift`                |\n| `=`            | `equal_sign`                    |\n| `plus`         | `equal_sign` + `shift`            |\n| `(`            | `9` + `shift`                     |\n| `)`            | `0` + `shift`                     |\n| `[`            | `open_bracket`                  |\n| `{`            | `open_bracket` + `shift`          |\n| `]`            | `close_bracket`                 |\n| `}`            | `close_bracket` + `shift`         |\n| `\\`          | `backslash`                   |\n| \\|           | `backslash` + `shift`             |\n| `;`            | `semicolon`                     |\n| `:`            | `semicolon` + `shift`             |\n| `'`            | `quote`                         |\n| `\"`            | `quote` + `shift`                 |\n| `grave`        | `grave_accent_and_tilde`        |\n| `            | `grave_accent_and_tilde`        |\n| `~`            | `grave_accent_and_tilde` + `shift` |\n| `,`            | `comma`                         |\n| `\u003c`            | `comma` + `shift`                 |\n| `.`            | `period`                        |\n| `\u003e`            | `period` + `shift`                |\n| `/`            | `slash`                         |\n| `?`            | `slash` + `shift`                 |\n| `!`            | `1` + `shift`                     |\n| `@`            | `2` + `shift`                     |\n| `#`            | `3` + `shift`                     |\n| `$`            | `4` + `shift`                     |\n| `%`            | `5` + `shift`                     |\n| `^`            | `6` + `shift`                     |\n| `\u0026`            | `7` + `shift`                     |\n| `*`           | `8` + `shift`                      |\n| `up`           | `up_arrow`                      |\n| `down`         | `down_arrow`                    |\n| `left`         | `left_arrow`                    |\n| `right`        | `right_arrow`                   |\n| `↑`            | `up_arrow`                      |\n| `↓`            | `down_arrow`                    |\n| `←`            | `left_arrow`                    |\n| `→`            | `right_arrow`                   |\n| `pgup`         | `page_up`                       |\n| `pgdn`         | `page_down`                     |\n| `kp-`          | `keypad_hyphen`                 |\n| `kp*`          | `keypad_asterisk`               |\n| `kp/`          | `keypad_slash`                  |\n| `kp=`          | `keypad_equal_sign`             |\n| `kp.`          | `keypad_period`                 |\n| `kp,`          | `keypad_comma`                  |\n| `kpenter`      | `keypad_enter`                  |\n| `kp1`          | `keypad_1`                      |\n| `kp2`          | `keypad_2`                      |\n| `kp3`          | `keypad_3`                      |\n| `kp4`          | `keypad_4`                      |\n| `kp5`          | `keypad_5`                      |\n| `kp6`          | `keypad_6`                      |\n| `kp7`          | `keypad_7`                      |\n| `kp8`          | `keypad_8`                      |\n| `kp9`          | `keypad_9`                      |\n| `kp0`          | `keypad_0`                      |\n| `kpnum`        | `keypad_num_lock`               |\n| `⌘`         | `left_command`                     |\n| `‹⌘`        | `left_command`                     |\n| `⌘›`        | `right_command`                    |\n| `⌥`         | `left_option`                      |\n| `‹⌥`        | `left_option`                      |\n| `⌥›`        | `right_option`                     |\n| `⌃`         | `left_control`                     |\n| `‹⌃`        | `left_control`                     |\n| `⌃›`        | `right_control`                    |\n| `⇧`         | `left_shift`                       |\n| `‹⇧`        | `left_shift`                       |\n| `⇧›`        | `right_shift`                      |\n| `lcmd`         | `left_command`                     |\n| `rcmd`         | `right_command`                    |\n| `lopt`         | `left_option`                      |\n| `ropt`         | `right_option`                     |\n| `lctrl`        | `left_control`                     |\n| `rctrl`        | `right_control`                    |\n| `lshift`       | `left_shift`                       |\n| `rshift`       | `right_shift`                      |\n\n\n\n#### Mutli-Modifier aliases:\n\n| karaml alias | Karabiner key_code                                              |\n| ------------ | --------------------------------------------------------------- |\n| `hyper`        | `right_shift` + `right_option` + `right_command` + `right_control`      |\n| `ultra`        | `right_shift` + `right_option` + `right_command` + `right_control` + `fn` |\n| `super`        | `right_shift` + `right_command` + `right_control`                     |\n| `☆`          |  `shift` + `option` + `command` + `control` + `fn` |\n\n\n#### _MISSING ALIASES_:\n\n- `+`, since it's used to join multiple key codes and I need\n  to figure out a way around that. Use `plus` in the meantime\n- `kp+` for the same reason, so use `kpplus` as an alternate\n\n\n### Defining your own aliases\n\nYou can define your own aliases for *singular events* by adding a top level \nkey named `aliases` anywhere in your config file. This YAML map will be merged\nwith the default aliases.\n\nAn alias has the following format:\n\n```yaml\naliases:            # Include this top-level key anywhere in your config\n  alias_name: /{modifiers (optional)} {delimiter (optional)} {key_code}/\n```\n\nExample:\n\n```yaml\naliases:\n\n  # No modifier\n  ⏎: return_or_enter\n  screen_saver: shell(open -b com.apple.ScreenSaver.Engine) # Start Screen Saver\n  # With modifiers (any of the following)\n  tilde: s | grave_accent_and_tilde  # left_shift + grave\n  tilde: ⇧ | grave_accent_and_tilde  # left_shift + grave\n\n  ⁙: o c | s  # With an explicit delimiter (`|` or `-`), the final `s` is\n              # interpreted as the # key code it represents,\n              # So this is an alias for `left_option` + `left_control` + `s`\n\n  ⁙: o c s    # Without an explicit delimiter, this alias is all mods, since\n              # `s` is one of the single-character modifier aliases.\n              # So this is an alias for `left_option` + `left_control` + `left_shift`\n\n  ⁙: ⌥ ⌃ ⇧    # An alias of all modifier symbols is added to the modifier\n              # alias dict regardless of its syntax\n\n/base/:\n  ⌘ | ⏎: app(WezTerm)\n  tilde: '`'\n  '`'  : tilde\n  ⁙ | ⏎: screen_saver\n\n/sys:\n  m | s: screen_saver\n```\n\nIn the above example, the Unicode character `⏎` can be used as an alias for\n`return_or_enter`, and `tilde` can be used as an alias for a tilde `~`\ncharacter, and `screen_saver` is aliased to a shell command to enable the\nscreen saver.\n\n\nNow these can be used in any layer in the config. In the example,\n`⌘ | ⏎` (`return_or_enter` with the `command` modifier) is used to launch\nWezTerm, and `tilde` key's usual function has been reversed so that now you\nhave to press shift + the backtick character to get a backtick instead of vice\nversa. And finally, the `left_command` and `s` key combination is used to start\nthe screen saver if the `/sys/` layer is active.\n\nAbove, `⁙` is an alias for `left_option` + `left_control` + `left_shift` that\nwe can use as a modifier. In the `/base/` layer, we use it and the `⏎` alias\nfor `return_or_enter` to make a rule for starting the screen saver, which also\nuses the alias `screen_saver` that we defined above. It's a rule composed\nentirely of user-defined aliases!\n\n\nThe purpose is to let you visualize your config in whatever way you find most\nreadable. Say for example you're creating a layer for your window-management\ncommands. Instead of a messy series of mappings like `\u003ccoms-1\u003e`, `\u003ccoms-2`, or\na series of long and similar shell commands, now you can create aliases like\n`window_eights`, `window_center`, `move_to_space_1` etc. This makes for a\ncleaner config and makes it easier to manage when you want to change the\nkeybindings.\n\nCurrently, only single-character aliases for modifier aliases are supported.\nWe're working on a way to support multi-character aliases for modifier aliases!\n\n#### Rules for defining your own *modifier* aliases\n\nIf all the key codes in an alias are valid modifiers, then the alias may be\ntreated as a (multi-)modifier alias and added to the dict of modifiers aliases.\n\nIf the alias is composed entirely of modifier *symbol* characters (⌥, ⌘, ⇧,\n⌃, etc.), then it will be added to the dict of modifier aliases, regardless of\nthe syntax used to define it.\n\nIf the alias is composed entirely of single characters that are all valid\nmodifier aliases (see the [modifiers](#modifiers) section), *AND* there are no\nexplicit delimiters in the alias definition, then it will be added to the dict\nof modifier aliases. Otherwise, the final character will be treated as the\nkey code it represents. See the examples above.\n\n\n### Simultaneous from-keys, multiple to-events, requiring multiple layers\n\nTo get simultaneous from events or multiple to events, i.e. in Karabiner-JSON:\n\n```json\n{\n  \"from\": {\n    \"simultaneous\": [{ \"key_code\": \"j\" }, { \"key_code\": \"k\" }]\n  },\n  \"to\": [{ \"key_code\": \"h\" }, { \"key_code\": \"l\" }]\n}\n```\n\nJoin valid key codes or aliases in any part of the YAML map with a `+`. This is\n'whitespace agnostic', so `j+k`, `j + k`, and `j+k + l` etc. are all valid.\n\n```yaml\n/base/:\n  j+k: h+l\n  c | j + k: [escape, \"/nav/ + notify(idn, Nav Layer on!)\", \"notifyOff(idn)\"]\n```\n\nThis also applies to joining layers if you want to enable two layers at once.\nNote that whichever layer comes later in the config will take priority if\nthere are conflicting keys. In the following example, the `s` of the `/sys/`\nlayer will take priority over the `s` of the `/fn/` layer, and the\nnon-conflicting keys will all work as intended.\n\n```yaml\n/base/:\n  \u003ca-caps_lock\u003e: [null, /fn/ + /sys/]\n/fn/:\n  a: f1\n  s: f2\n  d: f3\n/sys/:\n  e: volume_down\n  r: volume_up\n  s: app(System Preferences)\n```\n\nAlternatively, for sending multiple singe characters, you can use `string()`.\nSee: [string special event function](#strings)\n\n### Templates for common actions\n\nkaraml provides some templates for common-use actions. Some are just\n[to.shell_command](https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/to/shell-command/)\nevents in disguise. You can define your own templates for shell scripts - see\n[user-defined templates](#user-defined-shell-command-templates)\n\nYou can use these as if they were to.events, e.g.:\n\n```yaml\n/base/:\n  o | g: open(https://github.com)\n\n```\n\n| Function                                      | Description                                           |\n| --------------------------------------------- | ----------------------------------------------------- |\n| [app](#app-launchers)                         | Launch an app from the Applications folder            |\n| [open](#open-browser-link)                    | Open a URL in your default browser                    |\n| [shell](#shell-commands)                      | Run a shell command                                   |\n| [input](#input-sources)                       | Switch input source                                   |\n| [mouse](#mouse-movement)                      | Move the mouse cursor in the x or y directions        |\n| [mousePos](#set-mouse-cursor-position)        | Move the mouse cursor to a specific position          |\n| [notify](#notifications-applescript-style)    | Trigger a Karabiner-Elements notification             |\n| [notifyOff](#notifications-applescript-style) | Turn off a Karabiner-Elements notification            |\n| [shnotify](#notifications-applescript-style)  | Trigger a notification in macOS's Notification Center |\n| [softFunc](#software-functions)               | Karabiner-Elements software function                  |\n| [sticky](#sticky-modifiers)                   | Set a sticky modifier                                 |\n| [string](#strings)                            | Send a sequence of characters                         |\n| [var](#variables)                             | Set a value for a variable/condition                  |\n\n#### App Launchers\n\n`app(app_name)`\n\nPass an app name (as it appears in your Applications folder) as an argument to\n`app()`. I like using these in my `/nav/` layer for quick access, but you might\nwant these in a standalone `/apps/` layer.\n\n```yaml\n/nav/:\n  f: app(Firefox)\n```\n\n#### Open Browser Link\n\n`open(url)`\n\nOpen a URL with your default browser.\n\n```yaml\n/base/:\n  o | g: open(https://github.com)\n```\n\n#### Shell Commands\n\n`shell(shell command)`\n\nPass a [shell\ncommand](https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/to/shell-command/)\nas an argument to `shell()`. This is what the app() and open() 'functions' are\nactually doing under the hood. Please suggest other useful shorthands of shell\ncommands that we could add!\n\n```yaml\n/sys/:\n  grave: shell(open ~)\n```\n\n#### Input Sources\n\n```\ninput(lang_regex)\ninput({\"language\": \"regex\", \"input_source_id\": \"regex\", \"input_mode_id\": \"regex\" })\n```\n\nEither pass a language regex as an argument to `input()` to select the first\navailable input source that matches the regex, or pass a string representing a\nJSON object with all the valid Karabiner fields as specified\n[here](https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/to/select-input-source/).\n\n```yaml\n/sys/:\n  o | k + e:\n    input(en) # Set English input source\n    # Set a Greek keyboard based on source id\n  o | k + g:\n    'input({\n    \"input_source_id\": \"com.apple.keylayout.GreekPolytonic\",\n    \"language\": \"el\"\n    })' # note the use of single quotes around the rhs to escape\n    # double quotes and commas\n```\n\n#### Mouse Movement\n\n`mouse(action, speed|multiplier)`\n\nUsing the [to.mouse_key\nevent](https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/to/mouse-key/),\nyou can pass a two arguments that represent a key/value pair of movement/speed,\nor the speed_multiplier key and its multiplier value. If you want to assign\nmultiple mouse events to the mapping, you can pass a string representing a JSON\nobject matching the Karabiner specs, just like with input sources.\n\n```yaml\n/mouse/:\n  m: mouse(x, -2000)\n  n: mouse(y, 2000)\n  e: mouse(y, -2000)\n  i: mouse(x, 2000)\n  c | m: mouse(horizontal_wheel, 100)\n  c | n: mouse(vertical_wheel, -100)\n  c | e: mouse(vertical_wheel, 100)\n  c | i: mouse(horizontal_wheel, -100)\n  s: mouse(speed_multiplier, 2.5)\n```\n\n#### Set Mouse Cursor Position\n\n`mousePos(x, y, screen)`\n\nWraps around the [to.software_function.set_mouse_cursor_position](https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/to/software_function/set_mouse_cursor_position/)\nevent. The `x` and `y` arguments are mandatory, the `screen` argument is\noptional, and all three must be integers. The `screen` argument is the screen\nnumber, starting from 0. If you don't specify a screen, the cursor will be\nmoved to the same screen as the current mouse position.\n\n```yaml\n/mouse/:\n  \"0\": mousePos(0, 0)\n  ⌥ | 2: mousePos(500, 500, 1)\n```\n\n#### Notifications (Karabiner Style)\n\n`notify(id, message)`\n`notifyOff(id)`\n\nShorthand for [to.set_notification_message](https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/to/set-notification-message/)\n\nThe id is the reference for updating the notification with the message on\nsubsequent calls.\n\n```yaml\n/sys/:\n  s:\n    - app(System Preferences)\n    - /sys/ + notify(sysNotification,\"System Layer Enabled\")\n    - notifyOff(sysNotification)\n```\n\nThere are a few ways to disable a notification. The easiest to use and remember\nis to pass the notification id as the only arg to `notifyOff()`.\n\n```yaml\nnotifyOff(id)\n```\n\nOtherwise, you can pass an empty string or `null` as the message arg to `notify()`.\n\n```yaml\nnotify(id, null)\nnotify(id, \"\")\n```\n\n#### Notifications (AppleScript Style)\n\n`shnotify(msg, title, subtitle, sound)`\n\n`shnotify({\"msg\": \"message\", \"title\": \"title\", \"subtitle\": \"subtitle\", \"sound\": \"sound\"})`\n\nDisplays a notification using AppleScript by running a shell command in the\nformat:\n\n```bash\nosascript -e 'display notification \"message\" with title \"title\" subtitle \"subtitle\" sound name \"sound\"'\n```\n\nSee the scriping documentation [here](https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/DisplayNotifications.html).\n\nThere are two ways to pass arguments to `shnotify()`. The first is to pass\npositional arguments in the order of message, title, subtitle, and sound.\n\n```yaml\n/sys/:\n  o | 1: shnotify(text) # message only\n  o | 2: shnotify(text, title) # message and title\n  o | 3: shnotify(text, title, subtitle) # message, title, and subtitle\n  o | 4: shnotify(text, title, subtitle, sound) # message, title, subtitle, sound\n  o | 5: shnotify(text, null, null, sound) # message and sound only\n```\n\nThe second is to pass a string representing a JSON object with all the valid\nAppleScript notification fields.\n\n```yaml\n/sys/:\n  o | 6: 'shnotify({\n    \"msg\": \"text\",\n    \"title\": \"title\",\n    \"subtitle\": \"subtitle\",\n    \"sound\": \"sound\"\n    })'\n  # With a dict, you can omit any fields you don't want to use\n  o | 7: 'shnotify({\"msg\": \"text\", \"sound\": \"sound\"})'\n```\n\nSee the [sample configuration](#sample-configuration) for an example with\nkeyboard input source switching.\n\nSound names can be found at `/System/Library/Sounds/`\nFor more advanced notifications, try [terminal-notifier](https://github.com/julienXX/terminal-notifier) or [alerter](https://github.com/vjeantet/alerter) and pass the command as a `shell()` command.\n\n#### Software Functions\n\n`softFunc( {\"function name\": nested_dict} )`\n\nTakes a string representing a JSON object with all the valid Karabiner fields\nas specified\n[here](https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/to/soft-function/).\n\n```yaml\n/mouse/:\n  \"p+1\": 'softFunc(\"set_mouse_cursor_position\": {\"x\": 0, \"y\": 0, \"screen\": 0 })'\n```\n\n#### Sticky Modifiers\n\n`sticky(modifier, toggle)`\n\nPass two arguments: the modifier to be held on the next keypress (must be a\nvalid modifier key code), and whether to toggle the modifier, turn it on, or\nturn it off (`on`, `off`, or `toggle`).\n\n```yaml\n/base/:\n  ⌥ | ['right_shift: sticky(left_shift, toggle)', right_shift]\n  ⌘ | ['right_shift: sticky(left_shift, on)', right_shift]\n  ⌃ | ['right_shift: sticky(left_shift, off)', right_shift]\n```\n\n#### Strings\n\n`string(a string of valid key codes or aliases)`\n\nYou could map a key to type out a string of characters using the `+` joining method, e.g. `git checkout` like so...\n\n```yaml\n/base/:\n  a | g: g+i+t+space+c+h+e+c+k+o+u+t\n```\n\n...but for simplicity, you can use `string()` (though the above method is valid):\n\n```yaml\n/base/:\n  a | g: string(git checkout )\n```\n\nThe string must contain valid key codes or valid single-character aliases.\nSo, `[`, `?`, a blank space for `spacebar`, etc. will get interpreted\nas characters, and `spacebar`, `kp-`, `left` etc. will get interpreted as a\nliteral string of chars, not their alias counterparts.\n\nYou can use a combination if you want to send non-single character events:\n\n```yaml\n/base/:\n  a | g: string(git checkout main) + enter\n```\n\n#### Variables\n\nThe `var()` function takes two arguments: a variable name and a value (0 or 1).\nSee the [to.set_variable\ndocumentation](https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/to/set-variable/)\n\nkaraml intends variables to be handled as layers and automates some mappings\nunder the hood to streamline toggling and enabling layers, but the `var()`\nfunction allows more granular control.\n\n```yaml\n/base/:\n  o | s:\n    var(sys_layer, 1) # This does not automatically create a corresponding\n    # toggle-off mapping. Don't get stuck in another layer!\n```\n\n\n### User Defined Shell Command Templates\n\nThis feature copies much of the design of the template feature in\n[GokuRakuJoudo](https://github.com/yqrashawn/GokuRakuJoudo/blob/master/tutorial.md#predefined-templates),\nthough not as fully featured.\n\nYou can define your own templates for shell commands in the `templates` section\nof your configuration file. This may be useful if you want to create a lot of\nmaps for similar long and complex shell commands.\n\n```yaml\ntemplates:\n  template_name: \"command template %s || another command %s\"\n\n/base/:\n  ⌥ | 1: template(arg1, arg2)\n```\n\nAll `%s` will be replaced by the arguments passed to any instances of a\ntemplate in your layers. You can have as many args as you want, but karaml\nwill check that the amount of args passed to a template instance matches the\namount of `%s` in the template definition.\n\nAs an example of a good use case, here is a template for triggering\n[Rectangle Pro](https://rectangleapp.com/) actions using [urls](https://github.com/rxhanson/RectanglePro-Community#programmatically-triggering-window-sizes--positions).\n\n```yaml\ntemplates:\n  rectangle: open -g \"rectangle-pro://execute-action?name=%s\"\n\n/rectangle/:\n  ⌘ ⇧ | 1: rectangle( fullscreen       )\n  ⌘ ⇧ | 2: rectangle( left-half        )\n  ⌘ ⇧ | 3: rectangle( right-half       )\n  ⌘ ⇧ | 4: rectangle( next-display     )\n  ⌘ ⇧ | 5: rectangle( previous-display )\n  # etc.\n```\n\nThis looks much cleaner than having a few dozen lines of the same long\ncommand.\n\nNote that the `templates` map in your configuration file is loaded *BEFORE*\nyour `aliases` map (regardless of where you place those maps in your config),\nso you can use templates in your aliases, but you can't use aliases in your\ntemplates.\n\n```yaml\ntemplates:\n  rectangle: open -g \"rectangle-pro://execute-action?name=%s\"\n\naliases:\n  fullscreen : rectangle( fullscreen )\n  left-half  : rectangle( left-half  )\n  right-half : rectangle( right-half )\n\n/rectangle/:\n  ⌘ ⇧ | 1: fullscreen\n  ⌘ ⇧ | 2: left-half\n  ⌘ ⇧ | 3: right-half\n  # etc.\n```\n\n### Layer descriptions\n\nIf no description is provided for a layer, karaml will add a default one to the\n`karabiner.json` file in the form of `{layer name} layer`. You can override\nthis by adding some text to your layer's key *after* the layer (or layers).\n\n```yaml\n/mouse/     Mouse Movements:  # Excess whitespace is ignored\n  m       : mouse(x, -2000)\n  n       : mouse(y, 2000)\n  e       : mouse(y, -2000)\n  i       : mouse(x, 2000)\n\n# Using YAML's complex-key syntax for multi-line keys\n? /symnum/\n\n  Numpad + Symbols Layer\n:\n  (x) j: \"0\"\n  (x) k: \",\"\n\n# Layers with multiple conditions can also be split across multiple lines\n? /symnum/ +\n  /nav/\n\n  Sym \u0026 Nav Multi-layer\n:\n  m   : left\n  n   : down\n  e   : up\n  i   : right\n```\nThe parser will replace all the newline chars in the complex key with a space\nand parse it as if it were a single line, as in the `/mouse/` layer example.\n\nThis is an unconventional way to use YAML, but since a major part of karaml's\ndesign is to make a compact Karabiner-Elements configuration file and avoid\ndictionaries and excess brackets where possible and sensible, \nthis was a good compromise.\n\nThe advantage of this over simply adding YAML comments near the layer is that\nthe description makes it into the `karabiner.json` file and is displayed in\nthe Karabiner-Elements GUI.\n\n***NOTE***: The only restriction on layer descriptions is that they must not\ncontain a forward slash (`/`). This was done to simplify the parsing of\nmuti-conditional layers. karaml will raise an error if it detects a forward\nslash in a layer description.\n\n\n### Frontmost-App Conditions\n\nTo set frontmost-app conditions, the from-key part of your map remains the\nsame, but your value becomes a dictionary (instead of the usual single string\nor list/sequence).\n\n```yaml\n/layer/:\n  from-key:\n    {\n      if regex regex ...: [when-tapped, when-held, etc.],\n      unless regex regex ...: [etc.],\n    }\n```\n\nThe dictionary's keys must be in the form of either `if regex regex ...` or\n`unless regex regex ...` where `regex` is a regex expression that matches the\nbundle identifier of the app you want to match (read the [Karabiner\ndocs](https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/conditions/frontmost-application/)\nfor more info). karaml will yell at you if your conditional term isn't `if` or\n`unless`. Every following substring separated by a space will be interpreted\nby karaml as a list (YAML supports lists in keys, but Python doesn't, so use\na string here).\n\nThe dictionary's values are your usual karaml map values. Each k/v pair\nrepresents a new map that includes the `frontmost_app_if/unless` conditional.\n\nYou can create as many unique keys as you want here, all of which will be tied\nto the original 'from' key. Mind your regex and your capitalization!\n\n```yaml\n/base/:\n  c | u: { unless Terminal$ iterm2$ kitty$: g-backspace }\n  a | O: { unless Terminal$ iterm2$ kitty$: up + g-right + enter }\n  a | o: { unless Terminal$ iterm2$ kitty$: g-right + enter }\n  a | g: {\n      # types 'git ' when tapped, 'checkout' when held if any of these apps are focused\n      if Terminal$ iterm2$ kitty$: [string(git ), string(checkout)],\n      if CotEditor$: g | l, # 'go to line' alternate shortcut if CotEditor is focused\n    }\n```\n\n### Global parameters\n\nAdd a `parameters` map to your `.yaml` file to set global parameters for your\nprofile. These parameters will be applied to all mappings in the profile unless\noverridden by a mapping's own parameters object.\n\n```yaml\nparameters:\n  {\n    \"basic.to_if_alone_timeout_milliseconds\": 100,\n    \"basic.to_if_held_down_threshold_milliseconds\": 101,\n    basic.to_delayed_action_delay_milliseconds: 150,\n    \"basic.simultaneous_threshold_milliseconds\": 75,\n    \"mouse_motion_to_scroll.speed\": 100,\n  }\n\n/base/:\n  # ...\n```\n\n### Profile Name and Complex-Modification Title\n\nAdd a `profile` map to your `.yaml` file to set the profile name and a `title`\nmap to set the complex-modification title. Both are optional, and you can leave\nthem in your config even if you don't use the profile-creation or the\ncomplex-modification writing feature. If you don't provide either, karaml will\ngenerate default placeholders before it writes the file.\n\n```yaml\n# If a profile name is not provided, one will be generated from the\n# current Unix timestamp\nprofile_name: Karaml Config\n\ntitle: KaramlRules\n```\n\n### JSON Extension Map\n\nAdd a `json` map to the `.yaml` file to include Karabiner-compatible JSON. This\nis intended for cases where karaml doesn't support a feature you need, or when\nyou think the JSON looks easier to read than the karaml syntax, but you still\nwant to use karaml for the rest of your config.\n\nBe mindful of the slight differences in syntax between YAML and JSON. Namely,\nquotes are optional unless needed for escaping characters, elements of the\n`json` map are separated as sequence item (a properly indented dash `-`\nfollowed by a space), and no commas are used to separate those items.\n\n**_WARNING!_**: in the layer mappings, karaml 'inspects' your configuration for\nproper formatting - not just whether you wrote it in a syntax karaml can\nintrepret, but also whether you used valid key codes, valid modifiers, etc.\nCurrently, karaml doesn't support these 'health checks' for the JSON extension map,\nso karaml will just append whatever you put in there to the rule-set.\n\n```yaml\n/base/:\n  # ...\n\n/nav/:\n  # ...\n\njson:\n  - {\n      # No need for quotes since there are no chars that need escaping\n      description: Right Control to \u003e if tapped and control if held,\n      from: { key_code: right_control },\n      to: { key_code: right_control, lazy: true },\n      to_if_alone: { key_code: period, modifiers: [right_control] },\n      type: basic,\n    }\n  - {\n      # Quotes needed in the description because of the comma.\n      # We can also add quotes just if we want to\n      \"description\": \"Left Control to \u003c if tapped, control if held\",\n      \"from\": { \"key_code\": \"left_control\" },\n      \"to\": { \"key_code\": \"left_control\", \"lazy\": true },\n      \"to_if_alone\": { \"key_code\": \"comma\", \"modifiers\": [\"left_control\"] },\n      \"type\": \"basic\",\n    }\n```\n\n## 🐍️ Python backend for handling your files\n\nThe program reads your `.yaml` config with the PyYAML module, interprets your\nlayers and keybindings, checking that they are all well-formed along the way,\nand creates 'KaramlizedKey' objects with all the relevant attributes (from\nevents, to events, options, modifiers, etc.) that are eventually converted into\nKarabiner-compatible JSON.\n\nkaraml then either updates your existing `karabiner.json ` file or your\ncomplex-modifications folder.\n\n- If you elect to update `karabiner.json` directly:\n  - karaml makes a copy of the current `karabiner.json` file and adds that copy\n    to `~/.config/karabiner/automatic_backups/` as\n    `karabiner.backup.TIMESTAMP.json`, every time (thirty of my ~6000 line\n    Karabiner JSON backups add up to ~10MB, so check in every once in a while)\n  - karaml then searches the `profiles` list in `karabiner.json` for a profile\n    name that matches the one in your config (or generates a new one if you\n    didn't specify it - add one!). If it finds a match, it will replace that\n    profile with the new config generated from the `.yaml` file, otherwise it\n    just appends a new profile to the list\n- If you elect to add your karaml config as a ruleset to the\n  complex-modifications folder:\n  - If you passed the `-c` flag in the command line, karaml expects a filename\n    to write to. If you're using the CLI menu, karaml will default to\n    creating/overwriting `karaml_complex_mods.json`. If the file already\n    exists, you'll get a confirmation prompt. karaml doesn't backup your\n    complex-mods files\n  - If you have a `title` map in your `.yaml` file, karaml will use that as the\n    title of your complex-modification ruleset. If you don't, karaml will title\n    your ruleset as `KaramlRules`\n\n## 🎨 About the Design\n\nUnderstanding a standard Karabiner configuration is useful for leveraging the\nmost out of karaml, but at its core karaml is designed to reduce events to\neither `when_tapped` and `when_held` actions (also `when_released`, but that's\nautomated when layers are enabled when held).\n\n`when_tapped` is mapped to either `to` or `to_if_alone`, and `when_held` is\nmapped to either `to` or `to_if_held_down` depending on whether the map enables\na layer, a modifier, or a notification ('non-chatty' events) when held.\n\nkaraml was designed around toggling layers or enabling modifiers when a key is\nheld down, and letting the key have some other function when tapped. I found\nthat, for my typing style, a 100ms `to_if_alone_timeout_milliseconds` setting,\nmapping the 'when tapped' function to `to_if_alone` and the 'when held'\nfunctions to a `to` dictionary (instead of `to_if_held_down`) provided\nimmediate switching between layers or enabling of modifier keys, since the `to`\ndictionary doesn't wait for the `to_if_held_down_threshold_milliseconds` timeout. This means\nsending the `to` event even when `to_if_alone` is sent , but so long as the\nevent is 'harmless', or not 'chatty', i.e. it is a layer, a modifier, or a\nnotification, and so long as we set the `lazy` opt when necessary (e.g. for my\nmapping of `enter` → `control` when held), this doesn't cause any significant\nside effects.\n\nWhen a 'chatty' event would have side-effects, or when sending an event on 'if\nheld down' following the relevant parameters is an explicit goal (rather than\njust a way of distinguishing a tap from a hold), karaml can handle that by\nmaking the distinction between 'chatty/harmless' events and otherwise. In these\ncases, karml **_will_** place the 'when-held' event in the `to_if_held_down`\ndictionary to prevent 'chatter' caused by events in the `to` dictionary.\n\nIn short, karaml is opinionated about layers and modifiers, and is designed to\nact with zero delay given that most maps can be handled by reaching another\nlayer via tap-to-toggle or hold-to-enable. With this in mind, it tries to\nreduce the need to manually specify 'companion' mappings for layer toggling,\ne.g. for an 'x sets y = 1 if variable y = 0' mapping, karaml will automatically\ngenerate a 'x sets y = 0 if variable y = 1' mapping, and for 'x sets y = 1 if x\nis held down', karaml will automatically generate 'x sets y = 0 if x is\nreleased'.\n\nI try to handle as many other cases as possible in a concise manner in the\nkaraml style, but as a fallback, a `json:` map can be added to the config to\nintegrate a regular Karabiner JSON config (with minor YAML modifications). In\nfact, you could just use karaml as a way to write a regular Karabiner config in\nYAML but with less commas and little to no quotation marks.\n\n## 🔩 Requirements\n\n- Python \u003e= 3.10\n\nThis program was written while using Karabiner-Elements 14.11.0 and MacOS\n12.5.1\n\n## 📦 Installation\n\nClone this repo and install with `pip` in your terminal:\n\n```bash\ngit clone https://github.com/al-ce/karaml.git\ncd karaml\npip install .\n```\n\n## 🚀 Usage\n\nAfter you've made a well-formed karaml config, open your terminal app.\n\nThe `karaml` command requires one positional argument: the name of the YAML\nfile with your karaml config in the current folder (or the relative/absolute\npath to that file).\n\n```bash\nkaraml my_karaml_config.yaml\n```\n\nIf your karaml maps aren't mapped correctly, the program will raise an\nexception and try to give you some information about what went wrong. The error\nsystem needs improvement - working on it!\n\n### CLI prompt + modes\n\nWithout any optional arguments, you will be prompted to make a configuration\nchoice:\n\n```\n$ karaml my_karaml_config.yaml\nReading from: my_karaml_config.yaml...\n\n1. Update karabiner.json with my_karaml_config.yaml\n2. Update complex modifications folder with my_karamlconfig_.yaml.\nWrites to: karaml_complex_mods.json\n3. Quit\n```\n\n`1.` updates your `karabiner.json` file directly, either creating a new profile\nor updating an existing one depending on the `profile_name` key in your config.\nBefore every update, karaml makes a backup copy of your previous\n`karabiner.json` in the `automatic_backups` folder. This can add up! But I\ndidn't want to risk a bug in the program destroying your config.\n\n`2.` writes a JSON file to your karabiner complex modifications folder which\nyou can import with the Karabiner-Elements GUI. Then you can enable or disable layers\nindividually, but you should enable them all at once to ensure your layer and mapping\npriority is setup as intended. The file is named `karaml_complex_mods.json` by\ndefault, but you can change this in your karaml config with the `title` key,\n(and by doing so, you can easily switch between rulesets).\n\n`3.` quits the program.\n\n#### -k mode\n\nBy passing the `-k` flag, you will bypass the usual CLI prompt and karaml will\nupdate your karabiner.json file directly (as always, creating a backup\nbeforehand).\n\n```bash\nkaraml my_karaml_config.yaml -k\n```\n\n#### -c mode\n\nBy passing the `-c` flag, you will bypass the usual CLI prompt and karaml will\nupdate your complex modifications folder directly. If you're updating an old\ncomplex rule set, you'll have to remove the old complex modifications and\nre-enable the updated ones, as you would normally. No backup files will be\ncreated for complex modifications, but you will get a confirmation prompt to\nconfirm the overwrite/update. If you do not provide a `title` map in your YAML\nconfig, karaml will default to writing/updating the 'Karaml rules' ruleset.\n\n```bash\nkaraml my_karaml_config.yaml -c\n```\n\n#### -d (debug) mode\n\nIf there are malformed maps in your config, by default karaml prints you an\nerror message and quits without making any changes to `karbiner.json`.\nBy adding the `-d` flag, you can see the full stack trace of the error.\nIf you're making a map that should work, either because it's a feature you\nthink should be implemented or because you found a bug, please add the stack\ntrace in an issue!\n\n## 🪲 Known Issues / Bugs / Limitations\n\n- Can't toggle layer in 'when-tapped' position if also set in 'when-held'\nposition (e.g. `\u003ca-f\u003e: [/fn/, /fn/]` only enables the `/fn/` layer when held)\n- `from.simultaneous_options` not yet supported (this one will be tricky)\n- `to_delayed_action` not yet supported\n- `halt` option for `to_if_held_down` and `to_if_alone` not yet supported\n- ~~Can't add multiple shell-based templates in one mapping (see this [issue](https://github.com/al-ce/karaml/issues/3))~~\n\n## 🌱 TODOs\n\n- More condition types (`device_if`, etc.)\n- More helpful configuration error messages\n\n## 🔭 Alternatives\n\n- @mxstbr's [Karabiner configuration\n  generator](https://github.com/mxstbr/karabiner) written in TypeScript\n- [Goku](https://github.com/yqrashawn/GokuRakuJoudo)\n- [karabiner.ts](https://github.com/evan-liu/karabiner.ts)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fal-ce%2Fkaraml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fal-ce%2Fkaraml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fal-ce%2Fkaraml/lists"}