{"id":28142258,"url":"https://github.com/urob/zmk-adaptive-key","last_synced_at":"2025-05-14T19:20:02.145Z","repository":{"id":268163487,"uuid":"903510384","full_name":"urob/zmk-adaptive-key","owner":"urob","description":"A ZMK module for adaptive-key behavior ","archived":false,"fork":false,"pushed_at":"2025-04-01T15:10:10.000Z","size":29,"stargazers_count":11,"open_issues_count":3,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-01T16:29:09.016Z","etag":null,"topics":["zmk","zmk-module"],"latest_commit_sha":null,"homepage":"","language":"C","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/urob.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,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-14T19:32:29.000Z","updated_at":"2025-04-01T15:10:14.000Z","dependencies_parsed_at":null,"dependency_job_id":"616371cc-a889-4ece-9569-3ca266653dc2","html_url":"https://github.com/urob/zmk-adaptive-key","commit_stats":null,"previous_names":["urob/zmk-adaptive-key"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urob%2Fzmk-adaptive-key","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urob%2Fzmk-adaptive-key/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urob%2Fzmk-adaptive-key/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urob%2Fzmk-adaptive-key/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/urob","download_url":"https://codeload.github.com/urob/zmk-adaptive-key/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254209843,"owners_count":22032898,"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":["zmk","zmk-module"],"created_at":"2025-05-14T19:19:59.970Z","updated_at":"2025-05-14T19:20:02.051Z","avatar_url":"https://github.com/urob.png","language":"C","funding_links":[],"categories":["Community firmware Modules and Behaviors","Behaviors"],"sub_categories":["Custom Behaviors"],"readme":"# ZMK-ADAPTIVE-KEY\n\nThis module adds a `adaptive-key` behavior to ZMK. Some highlights compared to\nexisting alternatives:\n\n- Works as a module without the need to patch ZMK.\n- Configurable `dead-keys` property to turn any keycode into a dead key.\n- Simple \"inline\" macro specification to bind behavior sequences.\n- `min-prior-idle-ms` and `max-prior-idle` timeout properties that can vary by\n  trigger.\n- Correct handling of explicit modifiers.\n\n## Usage\n\nTo load the module, add the following entries to `remotes` and `projects` in\n`config/west.yml`.\n\n```yaml\nmanifest:\n  remotes:\n    - name: zmkfirmware\n      url-base: https://github.com/zmkfirmware\n    - name: urob\n      url-base: https://github.com/urob\n  projects:\n    - name: zmk\n      remote: urob # or zmkfirmware, see comment below\n    revision: v0.2+fix-child-nodes\n      import: app/west.yml\n    - name: zmk-adaptive-key\n      remote: urob\n      revision: v0.2 # set to same as ZMK version above\n  self:\n    path: config\n```\n\n**Important:** The `zephyr` remote used by upstream ZMK currently contains a bug\nthat under certain circumstances causes the build to fail. You will need to\npatch yours if your build fails with an error message like:\n\n```\nERROR: /behavior/leader-key POST_KERNEL 31 \u003c /behaviors/foo POST_KERNEL 49\n```\n\nThe simplest way to getting the patch is to use my `zmk` remote, as configured\nin above manifest. This will automatically build against a patched version of\nZephyr. Alternatively, you can use upstream ZMK and directly overwrite Zephyr by\nadding\n[these lines](https://github.com/urob/zmk-adaptive-key/blob/433dab18883462f6f5b34baa30f1991345d864e8/tests/west.yml#L12-L36)\nto your `west.yml` manifest. In either case, if you are building using Github\nActions, you may need to clear your cache (in the left sidebar on the `Actions`\ntab) for the changes to take effect.\n\n## Configuration\n\nAn `adaptive-key` defines \"trigger\" conditions on the _last_ keycode pressed\nprior to pressing the behavior. If any trigger condition matches, a behavior\nbound to that trigger is invoked. If no trigger condition matches, a default\nbehavior is invoked.\n\n### `trigger` properties\n\nTriggers are defined as child nodes of an adapative-key instance and are checked\nin order of their definition. Triggers have two _required properties_:\n\n- **`trigger-keys`**: A list of keycodes that trigger the bindings.\n- **`bindings`**: Behaviors bound to the trigger. If set to multiple behaviors\n  they are invoked in sequence.\n\nAdditional conditions can be configured via _optional properties_:\n\n- **`min-prior-idle-ms`**: Minimum time that must be elapsed since the last key\n  press. Defaults to none.\n- **`max-prior-idle-ms`**: Maximum time that must be elapsed since the last key\n  press. Defaults to none.\n- **`strict-modifiers`**: If true, modifiers must _exactly_ match the\n  `trigger-keys`. Otherwise it suffices to _contain_ the `trigger-keys` (useful\n  for case-sensitive bindings). Defaults to false.\n\n### `adaptive-key` properties\n\nBesides `trigger` child nodes, `adaptive-key` instances have the following\nproperties:\n\n- **`bindings`** (required): The default behavior to invoke if no trigger\n  condition is met. Can be `\u0026none` to do nothing.\n- **`dead-keys`**: A list of key codes that are converted to dead keys. Dead\n  keys don't send a keycode when pressed the first time but are still considered\n  as trigger condition. If pressed again, dead keys send their normal keycode.\n\n## Examples\n\n### Hands-down adaptive keys\n\n```c\n/ {\n    behaviors {\n        ak_h: ak_h {\n            compatible = \"zmk,behavior-adaptive-key\";\n            #binding-cells = \u003c0\u003e;\n            bindings = \u003c\u0026kp H\u003e;\n\n            akt_ah { trigger-keys = \u003cA\u003e; max-prior-idle-ms = \u003c300\u003e; bindings = \u003c\u0026kp U\u003e; };\n            akt_uh { trigger-keys = \u003cU\u003e; max-prior-idle-ms = \u003c300\u003e; bindings = \u003c\u0026kp A\u003e; };\n            akt_eh { trigger-keys = \u003cE\u003e; max-prior-idle-ms = \u003c300\u003e; bindings = \u003c\u0026kp O\u003e; };\n        };\n\n        ak_m: ak_m {\n            compatible = \"zmk,behavior-adaptive-key\";\n            #binding-cells = \u003c0\u003e;\n            bindings = \u003c\u0026kp M\u003e;\n\n            akt_gm { trigger-keys = \u003cG\u003e; max-prior-idle-ms = \u003c300\u003e; bindings = \u003c\u0026kp L\u003e; };\n            akt_pm { trigger-keys = \u003cP\u003e; max-prior-idle-ms = \u003c300\u003e; bindings = \u003c\u0026kp L\u003e; };\n        };\n\n        // And similarly for VP-\u003eVL, PV-\u003eLV, BT-\u003eBL, TB-\u003eLB\n\n        ak_g: ak_g {\n            compatible = \"zmk,behavior-adaptive-key\";\n            #binding-cells = \u003c0\u003e;\n            bindings = \u003c\u0026kp G\u003e;\n\n            // Binding two behaviors: JG-\u003eJPG\n            akt_jg { trigger-keys = \u003cJ\u003e; max-prior-idle-ms = \u003c300\u003e; bindings = \u003c\u0026kp P \u0026kp G\u003e; };\n        };\n\n    };\n};\n```\n\n### Dead keys\n\n```c\n/ {\n    behaviors {\n        ak_e: ak_e {\n            compatible = \"zmk,behavior-adaptive-key\";\n            #binding-cells = \u003c0\u003e;\n            bindings = \u003c\u0026kp E\u003e;\n            dead-keys = \u003cGRAVE CARET APOS QUOTE\u003e;\n\n            grave { trigger-keys = \u003cGRAVE\u003e; bindings = \u003c\u0026fr_e_grave\u003e; };\n            acute { trigger-keys = \u003cAPOS\u003e; bindings = \u003c\u0026fr_e_acute\u003e; };\n            circumflex { trigger-keys = \u003cCARET\u003e; bindings = \u003c\u0026fr_e_circumflex\u003e; };\n            diaeresis { trigger-keys = \u003cQUOTE\u003e; bindings = \u003c\u0026fr_e_diaeresis\u003e; };\n        };\n    };\n};\n```\n\nNote: the behavior bindings `\u0026fr_e_grave` etc must be defined elsewhere (e.g.,\nusing the French\n[language header](https://github.com/urob/zmk-helpers/tree/main#unicode-characters-and-language-collection)\nfrom the `zmk-helpers` module).\n\nAlternatively, \"new\" dead keycodes can be \"created\" by cannibalizing unused\nkeycode. For instance:\n\n```c\n#define DEAD1 F21\n#define DEAD2 F22\n#define DEAD3 F23\n#define DEAD4 F24\n\n/ {\n    behaviors {\n        ak_e: ak_e {\n            compatible = \"zmk,behavior-adaptive-key\";\n            #binding-cells = \u003c0\u003e;\n            bindings = \u003c\u0026kp E\u003e;\n            dead-keys = \u003cDEAD1 DEAD2 DEAD3 DEAD4\u003e;\n\n            grave { trigger-keys = \u003cDEAD1\u003e; bindings = \u003c\u0026fr_e_grave\u003e; };\n            acute { trigger-keys = \u003cDEAD2\u003e; bindings = \u003c\u0026fr_e_acute\u003e; };\n            circumflex { trigger-keys = \u003cDEAD3\u003e; bindings = \u003c\u0026fr_e_circumflex\u003e; };\n            diaeresis { trigger-keys = \u003cDEAD4\u003e; bindings = \u003c\u0026fr_e_diaeresis\u003e; };\n        };\n    };\n};\n```\n\nNote: While the keycodes used in this example are typically unused, they are\nstill [defined](https://zmk.dev/docs/keymaps/list-of-keycodes#f-keys). Making up\nnew _undefined_ keycodes is unsupported as their working hinges on the execution\norder of this module, which cannot be configured by any supported means.\n\n### Shift-repeat\n\n```c\n/ {\n    behaviors {\n        shift-repeat: shift-repeat {\n            compatible = \"zmk,behavior-adaptive-key\";\n            #binding-cells = \u003c0\u003e;\n            bindings = \u003c\u0026sk LSHFT\u003e;\n\n            repeat {\n                trigger-keys = \u003cA B C D E F G H I J K L M N O P Q R S T U V W X Y Z\u003e;\n                bindings = \u003c\u0026key_repeat\u003e;\n                max-prior-idle-ms = \u003c350\u003e;\n                strict-modifiers;\n            };\n        };\n    };\n};\n```\n\nThis sets up a `shift-repeat` behavior that sends `\u0026sk LSHFT` unless when\npressed within 0.35 seconds of any alpha key, in which case it sends\n`\u0026key_repeat`. Great for your homing thumb key!\n\n## `Kconfig` settings\n\n- `CONFIG_ZMK_ADAPTIVE_KEY_MAX_TRIGGER_CONDITIONS`: Maximum number of trigger\n  conditions per `adaptive-key` behavior. Defaults to 32.\n- `CONFIG_ZMK_ADAPTIVE_KEY_MAX_BINDINGS`: Maximum number of behaviors bound to a\n  trigger (i.e., length of macro sequence). Defaults to 4.\n- `CONFIG_ZMK_ADAPTIVE_KEY_WAIT_MS`: Wait time in milliseconds between key\n  presses when binding a macro sequence. Defaults to 5ms.\n- `CONFIG_ZMK_ADAPTIVE_KEY_TAP_MS`: Hold time per key tap when binding a macro\n  sequence. Defaults to 5ms.\n\n## References\n\n- The behavior idea is inspired by the\n  [Hands Down](https://sites.google.com/alanreiser.com/handsdown/home#h.3fq4ywspvw1g)\n  keyboard layout. The original ZMK\n  [feature request](https://github.com/zmkfirmware/zmk/issues/1624) provides\n  some further discussion.\n- PR [#2042](https://github.com/zmkfirmware/zmk/pull/2042) provides an\n  alternative implementation.\n- My personal [zmk-config](https://github.com/urob/zmk-config) contains advanced\n  usage examples.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Furob%2Fzmk-adaptive-key","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Furob%2Fzmk-adaptive-key","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Furob%2Fzmk-adaptive-key/lists"}