From fd152baff08cdb643f694b9d1b1343193562325d Mon Sep 17 00:00:00 2001 From: Nicolas Munnich <98408764+Nick-Munnich@users.noreply.github.com> Date: Thu, 25 Jul 2024 21:00:22 +0200 Subject: [PATCH 01/22] feat(docs): Add troubleshooting notes on experimental bluetooth options (#2387) Co-authored-by: Cem Aksoylar --- docs/docs/config/bluetooth.md | 14 ++++++------- .../troubleshooting/connection-issues.mdx | 21 ++++++++++++++----- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/docs/docs/config/bluetooth.md b/docs/docs/config/bluetooth.md index 02d20351..83fb9ec0 100644 --- a/docs/docs/config/bluetooth.md +++ b/docs/docs/config/bluetooth.md @@ -9,10 +9,10 @@ See [Configuration Overview](index.md) for instructions on how to change these s ## Kconfig -| Option | Type | Description | Default | -| -------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | -| `CONFIG_ZMK_BLE_EXPERIMENTAL_CONN` | bool | Enables a combination of settings that are planned to be default in future versions of ZMK to improve connection stability. This includes changes to timing on BLE pairing initiation, restores use of the updated/new LLCP implementation, and disables 2M PHY support. | n | -| `CONFIG_ZMK_BLE_EXPERIMENTAL_SEC` | bool | Enables a combination of settings that are planned to be officially supported in the future. This includes enabling BT Secure Connection passkey entry, and allows overwrite of keys from previously paired hosts. | n | -| `CONFIG_ZMK_BLE_EXPERIMENTAL_FEATURES` | bool | Aggregate config that enables both `CONFIG_ZMK_BLE_EXPERIMENTAL_CONN` and `CONFIG_ZMK_BLE_EXPERIMENTAL_SEC`. | n | -| `CONFIG_ZMK_BLE_PASSKEY_ENTRY` | bool | Enable passkey entry during pairing for enhanced security. (Note: After enabling this, you will need to re-pair all previously paired hosts.) | n | -| `CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION` | bool | Low level setting for GATT subscriptions. Set to `n` to work around an annoying Windows bug with battery notifications. | y | +| Option | Type | Description | Default | +| -------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | +| `CONFIG_ZMK_BLE_EXPERIMENTAL_CONN` | bool | Enables a combination of settings that are planned to be default in future versions of ZMK to improve connection stability. Currently this only disables 2M PHY support. | n | +| `CONFIG_ZMK_BLE_EXPERIMENTAL_SEC` | bool | Enables a combination of settings that are planned to be officially supported in the future. This includes enabling BT Secure Connection passkey entry, and allows overwrite of keys from previously paired hosts. | n | +| `CONFIG_ZMK_BLE_EXPERIMENTAL_FEATURES` | bool | Aggregate config that enables both `CONFIG_ZMK_BLE_EXPERIMENTAL_CONN` and `CONFIG_ZMK_BLE_EXPERIMENTAL_SEC`. | n | +| `CONFIG_ZMK_BLE_PASSKEY_ENTRY` | bool | Enable passkey entry during pairing for enhanced security. (Note: After enabling this, you will need to re-pair all previously paired hosts.) | n | +| `CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION` | bool | Low level setting for GATT subscriptions. Set to `n` to work around an annoying Windows bug with battery notifications. | y | diff --git a/docs/docs/troubleshooting/connection-issues.mdx b/docs/docs/troubleshooting/connection-issues.mdx index 5fdd1c83..f077702c 100644 --- a/docs/docs/troubleshooting/connection-issues.mdx +++ b/docs/docs/troubleshooting/connection-issues.mdx @@ -91,6 +91,20 @@ The settings reset firmware has Bluetooth disabled to prevent the two sides from ::: +## Unable to Connect to Device + +### Additional Bluetooth Options + +Some devices and operating systems may have additional restrictions that they require be met before allowing a bluetooth peripheral to pair with them. If your keyboard is visible to your host but you are having issues trouble connecting or no input is registered, this might be the cause. Some of ZMK's [experimental bluetooth settings](../config/bluetooth.md) may suffice to resolve the issue. In particular: + +- Disabling PHY 2Mbps ([`CONFIG_BT_CTLR_PHY_2M=n`](https://docs.zephyrproject.org/3.5.0/kconfig.html#CONFIG_BT_CTLR_PHY_2M)) helps to pair and connect for certain wireless chipset firmware versions, particularly on Windows (Realtek and Intel chips) and older Intel Macs with Broadcom chipsets. +- Enabling passkey entry ([`CONFIG_ZMK_BLE_PASSKEY_ENTRY=y`](../config/bluetooth.md)) helps for certain Windows computers (work-managed ones in particular). This may also manifest in not sending keystrokes. + +### Issues With Dual Boot Setups + +Since ZMK associates pairing/bond keys with hardware addresses of hosts, you cannot pair to two different operating systems in a dual boot system at the same time. +While you can find [documented workarounds](https://wiki.archlinux.org/title/bluetooth#Dual_boot_pairing) that involve copying pairing keys across operating systems and use both OS with a single profile, they can be fairly involved and should be followed with caution. + ## Issues While Connected ### Unreliable/Weak Connection @@ -111,11 +125,6 @@ This setting can also improve the connection strength between the keyboard halve If you want to test Bluetooth output on your keyboard and are powering it through the USB connection rather than a battery, you will be able to pair with a host device but may not see keystrokes sent. In this case you need to use the [output selection behavior](../behaviors/outputs.md) to prefer sending keystrokes over Bluetooth rather than USB. This might be necessary even if you are not powering from a device capable of receiving USB inputs, such as a USB charger. -### Issues With Dual Boot Setups - -Since ZMK associates pairing/bond keys with hardware addresses of hosts, you cannot pair to two different operating systems in a dual boot system at the same time. -While you can find [documented workarounds](https://wiki.archlinux.org/title/bluetooth#Dual_boot_pairing) that involve copying pairing keys across operating systems and use both OS with a single profile, they can be fairly involved and should be followed with caution. - ### macOS Connected but Not Working If you attempt to pair a ZMK keyboard from macOS in a way that causes a bonding issue, macOS may report the keyboard as connected, but fail to actually work. If this occurs: @@ -138,3 +147,5 @@ If this doesn't help, try following the procedure above but replace step 3 with - Restart the Windows device - Open "Device Manager," turn on "Show hidden devices" from the "View" menu, then find and delete the keyboard under the "Bluetooth" item + +Some Windows devices may also require passkey entry, described under ["Unable to Connect to Device"](#unable-to-connect-to-device). From 97294aa341508e7f243f1abed670188c3596e673 Mon Sep 17 00:00:00 2001 From: Nicolas Munnich <98408764+Nick-Munnich@users.noreply.github.com> Date: Thu, 25 Jul 2024 21:00:55 +0200 Subject: [PATCH 02/22] chore: Add a recommended extension for .mdx files (#2381) --- .vscode/extensions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 0819f71e..7a1eac2a 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,6 +4,7 @@ "ms-python.python", "ms-vscode.cpptools", "plorefice.devicetree", - "twxs.cmake" + "twxs.cmake", + "unifiedjs.vscode-mdx" ] } From f92dce43e9c76bdba76359ec1f90e606cd609ee0 Mon Sep 17 00:00:00 2001 From: Nicolas Munnich <98408764+Nick-Munnich@users.noreply.github.com> Date: Thu, 25 Jul 2024 21:08:08 +0200 Subject: [PATCH 03/22] feat(docs): Add modules feature page (#2380) Co-authored-by: Cem Aksoylar --- docs/docs/config/index.md | 16 +- docs/docs/development/build-flash.mdx | 4 + docs/docs/development/new-shield.mdx | 7 + docs/docs/features/beta-testing.mdx | 100 ---------- docs/docs/features/modules.mdx | 194 +++++++++++++++++++ docs/sidebars.js | 260 ++++++++++++++------------ 6 files changed, 360 insertions(+), 221 deletions(-) delete mode 100644 docs/docs/features/beta-testing.mdx create mode 100644 docs/docs/features/modules.mdx diff --git a/docs/docs/config/index.md b/docs/docs/config/index.md index d542d435..666cf4d9 100644 --- a/docs/docs/config/index.md +++ b/docs/docs/config/index.md @@ -28,12 +28,14 @@ When using a split keyboard, you can use a single file without the `_left` or `_ ### Board Folder -ZMK will search for config files in either of: +ZMK will search for config files in: - [`zmk/app/boards/arm/`](https://github.com/zmkfirmware/zmk/tree/main/app/boards/arm) -- `zmk-config/config/boards/arm/` +- `zmk-config/boards/arm/` +- `/boards/arm/` +- `zmk-config/config/boards/arm/` (For backwards compatibility only, do not use.) -...where `` is the name of the board. These files describe the hardware of the board. +...where `` is the name of the board and `` is the root directory of any [included module](../features/modules.mdx). These files describe the hardware of the board. ZMK will search the board folder for the following config files: @@ -48,12 +50,14 @@ For more documentation on creating and configuring a new board, see [Zephyr's bo ### Shield Folder -When building with a shield, ZMK will search for config files in either of: +When building with a shield, ZMK will search for config files in: - [`zmk/app/boards/shields/`](https://github.com/zmkfirmware/zmk/tree/main/app/boards/shields) -- `zmk-config/config/boards/shields/` +- `zmk-config/boards/shields/` +- `/boards/shields/` +- `zmk-config/config/boards/shields/` (For backwards compatibility only, do not use.) -...where `` is the name of the shield. These files describe the hardware of the shield that the board is plugged into. +...where `` is the name of the shield and `` is the root directory of any [included module](../features/modules.mdx). These files describe the hardware of the shield that the board is plugged into. ZMK will search the shield folder for the following config files: diff --git a/docs/docs/development/build-flash.mdx b/docs/docs/development/build-flash.mdx index 20e9e20a..cfcb39ee 100644 --- a/docs/docs/development/build-flash.mdx +++ b/docs/docs/development/build-flash.mdx @@ -108,6 +108,10 @@ west build -b nice_nano -- -DSHIELD=kyria_left -DZMK_CONFIG="C:/Users/myUser/Doc The above command must still be invoked from the `zmk/app` directory as noted above, rather than the config directory. Otherwise, you will encounter errors such as `ERROR: source directory "." does not contain a CMakeLists.txt; is this really what you want to build?`. Alternatively you can add the `-s /path/to/zmk/app` flag to your `west` command. ::: +:::warning +If your config is also a [module](../features/modules.mdx), then you should also add the root (the folder in which the `zephyr` folder is found) of your `zmk-config` as an [external module to build with](#building-with-external-modules). +::: + In order to make your `zmk-config` folder available when building within the VSCode Remote Container, you need to create a docker volume named `zmk-config` by binding it to the full path of your config directory. If you have run the VSCode Remote Container before, it is likely that docker has created this volume automatically -- we need to delete the default volume before binding it to the correct path. Follow the following steps: diff --git a/docs/docs/development/new-shield.mdx b/docs/docs/development/new-shield.mdx index 60299abf..ae97f6af 100644 --- a/docs/docs/development/new-shield.mdx +++ b/docs/docs/development/new-shield.mdx @@ -51,6 +51,13 @@ Follow these steps to create your new repository: - Select Public or Private, depending on your preference. - Click the green "Create repository" button +The repository is a combination of the directories and files required of a ZMK config, and those required of a shield module. To create a shield module, the following components are needed: + +- The `boards/shields` directory, where the keyboard's files will go +- The `zephyr/module.yml` file, which identifies and describes the module. See the [Zephyr documentation](https://docs.zephyrproject.org/3.5.0/develop/modules.html#module-yaml-file-description) for details on customising this file. For the purposes of creating a shield module, the default found in the template can be left untouched. + +Neither of these should be moved out of their parent directory. The other files and directories such as `config` are not necessary for the purposes of a shield module, but rather intended to be used for user configuration and testing. + ## New Shield Directory :::note diff --git a/docs/docs/features/beta-testing.mdx b/docs/docs/features/beta-testing.mdx deleted file mode 100644 index 1a8d2863..00000000 --- a/docs/docs/features/beta-testing.mdx +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: Beta Testing -sidebar_label: Beta Testing ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -You may find that ZMK does not support a feature or keyboard that you are interesting in using. You may find that someone -has already taken the time to submit the feature you need as a [Pull Request](https://github.com/zmkfirmware/zmk/pulls). If you find the feature you need as a pull request, -this page is for you! - -## Developer Repositories and Branches - -For a developer to submit a pull request to ZMK, they must first clone the original ZMK repository. After they have a copy -of the source code, they may create a feature branch to work within. When they have finished, they will publish the feature -branch and create the pull request. - -### Finding the Repository Page from the Pull Request - -![PR Repository](../assets/features/beta-testing/pr-repo-branch.png) - -### Finding the Repository URL - -![Repository URL](../assets/features/beta-testing/repo-url.png) - -### Finding the Repository Branch - -![Repository URL](../assets/features/beta-testing/repo-branch.png) - -## Testing Features - -Testing features will require you to modify the `west.yml` file. You will need to add a new remote for the pull request you -would like to test, and change the selected remote and revision (or branch) for the `zmk` project. - -### Examples - - - - -```yaml -manifest: - remotes: - - name: zmkfirmware - url-base: https://github.com/zmkfirmware - projects: - - name: zmk - remote: zmkfirmware - revision: main - import: app/west.yml - self: - path: config -``` - - - - -```yaml -manifest: - remotes: - - name: zmkfirmware - url-base: https://github.com/zmkfirmware - - name: okke-formsma - url-base: https://github.com/okke-formsma - projects: - - name: zmk - remote: okke-formsma - revision: macros - import: app/west.yml - self: - path: config -``` - - - - -```yaml -manifest: - remotes: - - name: zmkfirmware - url-base: https://github.com/zmkfirmware - - name: mcrosson - url-base: https://github.com/mcrosson - projects: - - name: zmk - remote: mcrosson - revision: feat-behavior-sleep - import: app/west.yml - self: - path: config -``` - - - diff --git a/docs/docs/features/modules.mdx b/docs/docs/features/modules.mdx new file mode 100644 index 00000000..e25bc72a --- /dev/null +++ b/docs/docs/features/modules.mdx @@ -0,0 +1,194 @@ +--- +title: Modules +sidebar_label: Modules +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +ZMK makes use of [Zephyr modules](https://docs.zephyrproject.org/3.5.0/develop/modules.html) to include additional source code or configuration files into its build. You can think of them as similar to plugins or themes. The most common uses of this feature are: + +- Building firmware for a keyboard external to ZMK's tree +- Adding functionality to ZMK, such as a driver or a behavior + +A common ZMK setup thus consists of the following separate components, commonly housed in their respective Git repositories: + +- A single ZMK config maintained by the user, containing the `.conf` and `.keymap` files for one or multiple keyboards. This is also where files from ZMK or modules should be overridden/modified, if there is a need. +- Any number of ZMK modules, maintained by the module's owner. Some modules may contain multiple keyboards or functionalities. If all of your keyboards and functionalities are internal to ZMK's tree, then no modules are necessary. +- The ZMK firmware itself, maintained by its contributors. + +:::note +The shift to using modules for keyboards is a relatively recent one, and not all designs may be properly configured to be used as a module. If this is the case for your keyboard, then we would strongly suggest asking your vendor or designer to rectify this. +::: + +## Building With Modules + +### GitHub Actions + +When [using GitHub Actions to build ZMK](../user-setup.mdx), adding modules is as simple as modifying the `west.yml` found in your `zmk-config`'s `config` directory. The recommended way of doing so is: + +1. Find the URL base (the parent URL) for the module and add it as an entry to the [remotes](https://docs.zephyrproject.org/3.5.0/develop/west/manifest.html#remotes). +2. Add the module as an entry to the [projects](https://docs.zephyrproject.org/3.5.0/develop/west/manifest.html#projects). + Aside from the mandatory `name`, `remote`, and the commonly used `revision` properties, take note of the `import` property under `projects`. Some modules may have other modules as dependencies. This property allows the specifying of an additional west manifest file found in the tree of the module, which will automatically import all dependencies. + +For more information on `west.yml`, see [West Manifests](https://docs.zephyrproject.org/3.5.0/develop/west/manifest.html). + +#### Examples + + + + +```yaml +manifest: + remotes: + - name: zmkfirmware + url-base: https://github.com/zmkfirmware + projects: + - name: zmk + remote: zmkfirmware + revision: main + import: app/west.yml + self: + path: config +``` + + + + +```yaml +manifest: + remotes: + - name: zmkfirmware + url-base: https://github.com/zmkfirmware + - name: module_a_base + url-base: https://github.com/alice + projects: + - name: zmk + remote: zmkfirmware + revision: main + import: app/west.yml + - name: module_a + remote: module_a_base + revision: main + self: + path: config +``` + + + + +```yaml +manifest: + remotes: + - name: zmkfirmware + url-base: https://github.com/zmkfirmware + - name: module_a_base + url-base: https://github.com/alice + - name: module_b_base + url-base: https://github.com/bob + projects: + - name: zmk + remote: zmkfirmware + revision: main + import: app/west.yml + - name: module_a + remote: module_a_base + revision: main + - name: module_b + remote: module_b_base + revision: main + import: west.yml + self: + path: config +``` + + + + +### Building Locally + +To add a module to your build when building locally, you will need to clone/copy said module into your local file tree. You can then build using the module as described in [Building with External Modules](../development/build-flash.mdx#building-with-external-modules). + +## Beta Testing + +You may find that there are some features which you desire for which there is a [Pull Request](https://github.com/zmkfirmware/zmk/pulls), but no module. If this is the case, you can still make use of the feature. + +### Developer Repositories and Branches + +For a developer to submit a pull request to ZMK, they must first clone the original ZMK repository. After they have a copy +of the source code, they may create a feature branch to work within. When they have finished, they will publish the feature +branch and create the pull request. + +#### Finding the repository page from the Pull Request + +![PR Repository](../assets/features/beta-testing/pr-repo-branch.png) + +#### Finding the repository URL + +![Repository URL](../assets/features/beta-testing/repo-url.png) + +#### Finding the repository branch + +![Repository URL](../assets/features/beta-testing/repo-branch.png) + +## Testing Features + +### GitHub Actions + +When [using GitHub Actions to build ZMK](../user-setup.mdx), once you have obtained the correct URL, you'll need to modify the `west.yml` file similarly to [Building With Modules](#building-with-modules). Add the remote for the branch like in said section. The difference is that you will need to change the selected remote and revision (or branch) for the `zmk` project. + +#### Example + + + + +```yaml +manifest: + remotes: + - name: zmkfirmware + url-base: https://github.com/zmkfirmware + projects: + - name: zmk + remote: zmkfirmware + revision: main + import: app/west.yml + self: + path: config +``` + + + + +```yaml +manifest: + remotes: + - name: zmkfirmware + url-base: https://github.com/zmkfirmware + - name: forkedzmk + url-base: https://github.com/forkedzmk + projects: + - name: zmk + remote: forkedzmk + revision: specificpr + import: app/west.yml + self: + path: config +``` + + + + +### Building Locally + +When building from a pull request locally, you'll need to [perform the local user setup](../development/setup/index.md), but using the repository of the pull request rather than the official ZMK repository. You can then [build and flash](../development/build-flash.mdx) as usual. diff --git a/docs/sidebars.js b/docs/sidebars.js index e8c715c8..1233ccf1 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -1,120 +1,150 @@ module.exports = { - docs: { - "Getting Started": [ - "intro", - "hardware", - "faq", - "user-setup", - "customization", - { - type: "category", - label: "Troubleshooting", - link: { - type: "doc", - id: "troubleshooting/index", + docs: [ + { + type: "category", + label: "Getting Started", + link: { + type: "doc", + id: "intro", + }, + collapsed: false, + items: [ + "hardware", + "faq", + "user-setup", + "customization", + { + type: "category", + label: "Troubleshooting", + link: { + type: "doc", + id: "troubleshooting/index", + }, + collapsed: true, + items: [ + "troubleshooting/building-issues", + "troubleshooting/flashing-issues", + "troubleshooting/connection-issues", + ], }, - collapsed: true, - items: [ - "troubleshooting/building-issues", - "troubleshooting/flashing-issues", - "troubleshooting/connection-issues", - ], + ], + }, + { + Features: [ + "features/keymaps", + "features/bluetooth", + "features/combos", + "features/conditional-layers", + "features/debouncing", + "features/displays", + "features/encoders", + "features/modules", + "features/underglow", + "features/backlight", + "features/battery", + "features/soft-off", + ], + }, + { + type: "category", + label: "Behaviors", + link: { + type: "doc", + id: "behaviors/index", }, - ], - Features: [ - "features/keymaps", - "features/bluetooth", - "features/combos", - "features/conditional-layers", - "features/debouncing", - "features/displays", - "features/encoders", - "features/underglow", - "features/backlight", - "features/battery", - "features/soft-off", - "features/beta-testing", - ], - Behaviors: [ - "behaviors/index", - "behaviors/key-press", - "behaviors/layers", - "behaviors/misc", - "behaviors/hold-tap", - "behaviors/mod-tap", - "behaviors/mod-morph", - "behaviors/macros", - "behaviors/key-toggle", - "behaviors/sticky-key", - "behaviors/sticky-layer", - "behaviors/tap-dance", - "behaviors/caps-word", - "behaviors/key-repeat", - "behaviors/sensor-rotate", - "behaviors/mouse-emulation", - "behaviors/reset", - "behaviors/bluetooth", - "behaviors/outputs", - "behaviors/underglow", - "behaviors/backlight", - "behaviors/power", - "behaviors/soft-off", - ], - Codes: [ - "codes/index", - "codes/keyboard-keypad", - "codes/modifiers", - "codes/editing", - "codes/media", - "codes/applications", - "codes/input-assist", - "codes/power", - ], - Configuration: [ - "config/index", - "config/backlight", - "config/battery", - "config/behaviors", - "config/bluetooth", - "config/combos", - "config/displays", - "config/encoders", - "config/keymap", - "config/kscan", - "config/power", - "config/underglow", - "config/system", - ], - Development: [ - "development/clean-room", - "development/pre-commit", - "development/documentation", - { - type: "category", - label: "Setup", - collapsed: true, - items: [ - "development/setup/index", - "development/setup/docker", - "development/setup/native", - ], + collapsed: true, + items: [ + "behaviors/key-press", + "behaviors/layers", + "behaviors/misc", + "behaviors/hold-tap", + "behaviors/mod-tap", + "behaviors/mod-morph", + "behaviors/macros", + "behaviors/key-toggle", + "behaviors/sticky-key", + "behaviors/sticky-layer", + "behaviors/tap-dance", + "behaviors/caps-word", + "behaviors/key-repeat", + "behaviors/sensor-rotate", + "behaviors/mouse-emulation", + "behaviors/reset", + "behaviors/bluetooth", + "behaviors/outputs", + "behaviors/underglow", + "behaviors/backlight", + "behaviors/power", + "behaviors/soft-off", + ], + }, + { + Codes: [ + "codes/index", + "codes/keyboard-keypad", + "codes/modifiers", + "codes/editing", + "codes/media", + "codes/applications", + "codes/input-assist", + "codes/power", + ], + }, + { + type: "category", + label: "Configuration", + link: { + type: "doc", + id: "config/index", }, - "development/build-flash", - "development/boards-shields-keymaps", - "development/posix-board", - "development/tests", - "development/usb-logging", - "development/ide-integration", - { - type: "category", - label: "Guides", - collapsed: false, - items: [ - "development/new-shield", - "development/hardware-metadata-files", - "development/new-behavior", - ], - }, - ], - }, + collapsed: true, + items: [ + "config/backlight", + "config/battery", + "config/behaviors", + "config/bluetooth", + "config/combos", + "config/displays", + "config/encoders", + "config/keymap", + "config/kscan", + "config/power", + "config/underglow", + "config/system", + ], + }, + { + Development: [ + "development/clean-room", + "development/pre-commit", + "development/documentation", + { + type: "category", + label: "Setup", + collapsed: true, + items: [ + "development/setup/index", + "development/setup/docker", + "development/setup/native", + ], + }, + "development/build-flash", + "development/boards-shields-keymaps", + "development/posix-board", + "development/tests", + "development/usb-logging", + "development/ide-integration", + { + type: "category", + label: "Guides", + collapsed: false, + items: [ + "development/new-shield", + "development/hardware-metadata-files", + "development/new-behavior", + ], + }, + ], + }, + ], }; From 9e7fcde868d4ea0d4bd51fcae0afba0349a2bd81 Mon Sep 17 00:00:00 2001 From: Nicolas Munnich <98408764+Nick-Munnich@users.noreply.github.com> Date: Thu, 1 Aug 2024 05:53:36 +0200 Subject: [PATCH 04/22] feat(docs): Improve the layers-related documentation (#2390) Fixes #1284 --- docs/docs/behaviors/layers.md | 4 +++ docs/docs/features/keymaps.mdx | 61 ++++++++++++++++++++++++++-------- docs/docs/keymap-example.md | 36 +++++++++++++------- 3 files changed, 75 insertions(+), 26 deletions(-) diff --git a/docs/docs/behaviors/layers.md b/docs/docs/behaviors/layers.md index 511fbe08..d0e80679 100644 --- a/docs/docs/behaviors/layers.md +++ b/docs/docs/behaviors/layers.md @@ -8,6 +8,10 @@ sidebar_label: Layers Often, you may want a certain key position to alter which layers are enabled, change the default layer, etc. Some of those behaviors are still in the works; the ones that are working now are documented here. +:::note +Multiple layers can be active at the same time and activating a layer will not deactivate layers higher up in the "layer stack". See [Layers](../features/keymaps.mdx#layers) for more information. +::: + ## Defines to Refer to Layers When working with layers, you may have several different key positions with bindings that enable/disable those layers. diff --git a/docs/docs/features/keymaps.mdx b/docs/docs/features/keymaps.mdx index 95df3e86..c1608204 100644 --- a/docs/docs/features/keymaps.mdx +++ b/docs/docs/features/keymaps.mdx @@ -4,7 +4,6 @@ sidebar_label: Keymaps --- import KeymapExample from "../keymap-example.md"; -import KeymapExampleFile from "../keymap-example-file.md"; ZMK uses a declarative approach to keymaps instead of using C code for all keymap configuration. Right now, ZMK uses the devicetree syntax to declare those keymaps; future work is envisioned for @@ -38,21 +37,24 @@ For the full set of possible behaviors, see the [overview page for behaviors](.. ## Layers Like many mechanical keyboard firmwares, ZMK keymaps are composed of a collection of layers, with a -minimum of at least one layer that is the default, usually near the bottom of the "stack". Each layer +minimum of at least one layer that is the default, usually near the bottom of the "layer stack". Each layer in ZMK contains a set of bindings that bind a certain behavior to a certain key position in that layer. | ![Diagram of three layers](../assets/features/keymaps/layer-diagram.png) | | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | | _A simplified diagram showing three layers. The layout of each layer is the same (they all contain four keys), but the behavior bindings within each layer can be different._ | -In addition to the base default layer (which can be changed), certain bound behaviors may also -enable/disable additional layers "on top" of the default layer. +All layers are assigned and referred to by a natural number, with the base layer being layer `0`. It is common to [use the C preprocessor to "name" layers](../behaviors/layers.md#defines-to-refer-to-layers), making them more legible. -When a key location is pressed/released, the stack of all active layers from "top to bottom" is used, -and the event is sent to the behavior bound at that position in each layer, for it to perform whatever -actions it wants to in reaction to the event. Those behaviors can choose to "handle" the event, and stop -it from being passed to any lower layers, or may choose to "pass it along", and let the next layer -in the stack _also_ get the event. +The default layer (the base layer with index 0) is always enabled. Certain bound behaviors may enable/disable additional layers. + +When a key location is pressed/released, the _highest-valued currently active_ layer is used. The press/release event is sent to the behavior bound at that position in said layer, for it to perform whatever actions it wants to in reaction to the event. The behavior can choose to "consume" the event, or "pass it along" and let the next highest-valued active layer _also_ get the event (whose behavior may continue "passing it along"). + +Note that the _activation_ order isn't relevant for determining the priority of active layers, it is determined _only_ by the definition order. + +:::tip +If you wish to use multiple base layers (with a [toggle](../behaviors/layers.md#toggle-layer)), e.g. one for QWERTY and another for Colemak layouts, you will want these layers to have the lowest value possible. In other words, one should be layer `0`, and the other should be layer `1`. This allows other momentary layers activated on top of them to work with both. +::: ## Behavior Bindings @@ -121,18 +123,49 @@ Nested under the devicetree root, is the keymap node. The node _name_ itself is ### Layers -Each layer of your keymap will be nested under the keymap node. Here is a sample -that defines just one layer for this keymap: +Each layer of your keymap will be nested under the keymap node. Here is an example of a layer in a 6-key macropad. - +```dts + keymap { + compatible = "zmk,keymap"; + + default_layer { // Layer 0 +// ---------------------------------------------- +// | Z | M | K | +// | A | B | C | + bindings = < + &kp Z &kp M &kp K + &kp A &kp B &kp C + >; + }; + }; +``` Each layer should have: 1. A `bindings` property this will be a list of [behavior bindings](../behaviors/index.mdx), one for each key position for the keyboard. 1. (Optional) A `sensor-bindings` property that will be a list of behavior bindings for each sensor on the keyboard. (Currently, only encoders are supported as sensor hardware, but in the future devices like trackpoints would be supported the same way) +### Multiple Layers + +Layers are numbered in the order that they appear in keymap node - the first layer is `0`, the second layer is `1`, etc. + +Here is an example of a trio of layers for a simple 6-key macropad: + + + +:::note +Even if layer `1` was to be activated after `2`, layer `2` would still have priority as it is higher valued. Behaviors such as [To Layer (`&to`)](../behaviors/layers.md#to-layer) can be used to enable one layer _and disable all other non-default layers_, though. +::: + ### Complete Example -Putting this all together, a complete [`kyria.keymap`](https://github.com/zmkfirmware/zmk/blob/main/app/boards/shields/kyria/kyria.keymap) looks like: +Some examples of complete keymaps for a keyboard are: - +- [`corne.keymap`](https://github.com/zmkfirmware/zmk/blob/main/app/boards/shields/corne/corne.keymap) +- [`kyria.keymap`](https://github.com/zmkfirmware/zmk/blob/main/app/boards/shields/kyria/kyria.keymap) +- [`lily58.keymap`](https://github.com/zmkfirmware/zmk/blob/main/app/boards/shields/lily58/lily58.keymap) + +:::tip +Every keyboard comes with a "default keymap". For additional examples, the [ZMK tree's `app/boards` folder](https://github.com/zmkfirmware/zmk/blob/main/app/boards) can be browsed. +::: diff --git a/docs/docs/keymap-example.md b/docs/docs/keymap-example.md index e526d542..57f40762 100644 --- a/docs/docs/keymap-example.md +++ b/docs/docs/keymap-example.md @@ -2,20 +2,32 @@ keymap { compatible = "zmk,keymap"; - default_layer { -// -------------------------------------------------------------------------------------------------------------------------------------------------------------------- -// | ESC | Q | W | E | R | T | | Y | U | I | O | P | \ | -// | TAB | A | S | D | F | G | | H | J | K | L | ; | ' | -// | SHIFT | Z | X | C | V | B | CTRL+A | CTRL+C | | CTRL+V | CTRL+X | N | M | , | . | / | R CTRL | -// | GUI | DEL | RETURN | SPACE | ESCAPE | | RETURN | SPACE | TAB | BSPC | R ALT | + default_layer { // Layer 0 +// ---------------------------------------------- +// | Z | M | K | +// | &mo 1 | LEFT SHIFT | &mo 2 | bindings = < - &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH - &kp TAB &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SEMI &kp SQT - &kp LSHIFT &kp Z &kp X &kp C &kp V &kp B &kp LC(A) &kp LC(C) &kp LC(V) &kp LC(X) &kp N &kp M &kp COMMA &kp DOT &kp FSLH &kp RCTRL - &kp LGUI &kp DEL &kp RET &kp SPACE &kp ESC &kp RET &kp SPACE &kp TAB &kp BSPC &kp RALT + &kp Z &kp M &kp K + &mo 1 &kp LSHIFT &mo 2 + >; + }; + abc { // Layer 1 +// ---------------------------------------------- +// | A | B | C | +// | &trans | &trans | &trans | + bindings = < + &kp A &kp B &kp C + &trans &trans &trans + >; + }; + xyz { // Layer 2 +// ---------------------------------------------- +// | X | Y | Z | +// | LEFT CTRL | LEFT ALT | &trans | + bindings = < + &kp X &kp Y &kp Z + &kp LCTRL &kp LALT &trans >; - - sensor-bindings = <&inc_dec_kp C_VOL_UP C_VOL_DN &inc_dec_kp PG_UP PG_DN>; }; }; ``` From b080befebb8114cf0d539eeeb0f2d415c694800c Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Mon, 29 Jul 2024 10:40:16 -0700 Subject: [PATCH 05/22] fix(docs): Fix link for matrix transform --- docs/docs/config/kscan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index 4fb0cf16..097165be 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -342,7 +342,7 @@ Transforms should be used any time the physical layout of a keyboard's keys does Transforms can also be used for keyboards with multiple layouts. You can define multiple matrix transform nodes, one for each layout, and users can select which one they want from the `/chosen` node in their keymaps. -See the [new shield guide](../development/new-shield.mdx#optional-matrix-transform) for more documentation on how to define a matrix transform. +See the [new shield guide](../development/new-shield.mdx#matrix-transform) for more documentation on how to define a matrix transform. ### Devicetree From b9a025c76ec77417aca11b38f4dd9da60884e9a4 Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Fri, 2 Aug 2024 09:57:15 -0700 Subject: [PATCH 06/22] fix(docs): Document number of peripherals Kconfig (#2389) Fixes #2373 --- docs/docs/config/system.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/config/system.md b/docs/docs/config/system.md index 5d63ca52..fef9f53a 100644 --- a/docs/docs/config/system.md +++ b/docs/docs/config/system.md @@ -125,6 +125,7 @@ Following split keyboard settings are defined in [zmk/app/src/split/Kconfig](htt | `CONFIG_ZMK_SPLIT_ROLE_CENTRAL` | bool | `y` for central device, `n` for peripheral | | | `CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS` | bool | Enable split keyboard support for passing indicator state to peripherals | n | | `CONFIG_ZMK_SPLIT_BLE` | bool | Use BLE to communicate between split keyboard halves | y | +| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS` | int | Number of peripherals that will connect to the central | 1 | | `CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING` | bool | Enable fetching split peripheral battery levels to the central side | n | | `CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_PROXY` | bool | Enable central reporting of split battery levels to hosts | n | | `CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_QUEUE_SIZE` | int | Max number of battery level events to queue when received from peripherals | `CONFIG_ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS` | From 47a6715aa6d380b97c505e40a581e7efb3bed042 Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Fri, 26 Jul 2024 14:27:25 -0700 Subject: [PATCH 07/22] fix(docs): Fix toggle layer docs --- docs/docs/behaviors/layers.md | 43 +++-------------------------------- 1 file changed, 3 insertions(+), 40 deletions(-) diff --git a/docs/docs/behaviors/layers.md b/docs/docs/behaviors/layers.md index d0e80679..7c95246d 100644 --- a/docs/docs/behaviors/layers.md +++ b/docs/docs/behaviors/layers.md @@ -37,7 +37,7 @@ again. ### Behavior Binding - Reference: `&mo` -- Parameter: The layer number to enable/disable, e.g. `1` +- Parameter: The layer number to enable while held, e.g. `1` Example: @@ -52,7 +52,7 @@ The "layer-tap" behavior enables a layer when a key is held, and outputs a [keyp ### Behavior Binding - Reference: `<` -- Parameter: The layer number to enable when held, e.g. `1` +- Parameter: The layer number to enable while held, e.g. `1` - Parameter: The keycode to send when tapped, e.g. `A` Example: @@ -103,7 +103,7 @@ Example: ## Toggle Layer -The "toggle layer" behavior enables a layer until the layer is manually disabled. +The "toggle layer" behavior enables a layer if it is currently disabled, or disables it if enabled. ### Behavior Binding @@ -116,43 +116,6 @@ Example: &tog LOWER ``` -"Toggle layer" for a : - -```dts -#define DEFAULT 0 -#define NAVI 1 - -#define NONE 0 - -/ { - keymap { - compatible = "zmk,keymap"; - - default_layer { - bindings = < - &tog NAVI &kp KP_DIVIDE &kp KP_MULTIPLY &kp KP_MINUS - &kp NUMBER_7 &kp NUMBER_8 &kp NUMBER_9 &kp KP_PLUS - &kp NUMBER_4 &kp NUMBER_5 &kp NUMBER_6 &kp KP_PLUS - &kp NUMBER_1 &kp NUMBER_2 &kp NUMBER_3 &kp RETURN - &kp NUMBER_0 &kp NUMBER_0 &kp DOT &kp RETURN - >; - }; - - nav_layer { - bindings = < - &tog NAVI &kp KP_DIVIDE &kp KP_MULTIPLY &kp KP_MINUS - &kp HOME &kp UP &kp PAGE_UP &kp KP_PLUS - &kp LEFT &none &kp RIGHT &kp KP_PLUS - &kp END &kp DOWN &kp PAGE_DOWN &kp RETURN - &kp INSERT &kp INSERT &kp DEL &kp RETURN - >; - }; - }; -}; -``` - -It is possible to use "toggle layer" to have keys that raise and lower the layers as well. - ## Conditional Layers The "conditional layers" feature enables a particular layer when all layers in a specified set are active. From 78ed721c36115efeba877b7a419139bade905f65 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Fri, 2 Aug 2024 17:26:56 -0600 Subject: [PATCH 08/22] fix(pm): Properly configure a wakeup as input. * Other drivers properly disconnect/de-config pins now, so we need to be sure the wakeup trigger connects the wake pin as input. --- app/src/gpio_key_wakeup_trigger.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/gpio_key_wakeup_trigger.c b/app/src/gpio_key_wakeup_trigger.c index 308c4973..d22523a4 100644 --- a/app/src/gpio_key_wakeup_trigger.c +++ b/app/src/gpio_key_wakeup_trigger.c @@ -36,7 +36,12 @@ static int zmk_gpio_key_wakeup_trigger_init(const struct device *dev) { static int gpio_key_wakeup_trigger_pm_resume(const struct device *dev) { const struct gpio_key_wakeup_trigger_config *config = dev->config; - int ret = gpio_pin_interrupt_configure_dt(&config->trigger, GPIO_INT_LEVEL_ACTIVE); + int ret = gpio_pin_configure_dt(&config->trigger, GPIO_INPUT); + if (ret < 0) { + LOG_ERR("Failed to configure wakeup trigger key GPIO pin as input (%d)", ret); + return ret; + } + ret = gpio_pin_interrupt_configure_dt(&config->trigger, GPIO_INT_LEVEL_ACTIVE); if (ret < 0) { LOG_ERR("Failed to configure wakeup trigger key GPIO pin interrupt (%d)", ret); return ret; From cd25c12ce99d1ee708d76854a79980c7fb48a331 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Sat, 3 Aug 2024 14:00:31 -0600 Subject: [PATCH 09/22] fix: Proper device PM support for composite kscan. * Clean up composite kscan to allow multiple instances properly. * Implement PM hook and properly suspend/resume the child devices. Fixes: #2388 --- app/module/drivers/kscan/kscan_composite.c | 107 ++++++++++++++++----- 1 file changed, 82 insertions(+), 25 deletions(-) diff --git a/app/module/drivers/kscan/kscan_composite.c b/app/module/drivers/kscan/kscan_composite.c index 2a364324..a064903a 100644 --- a/app/module/drivers/kscan/kscan_composite.c +++ b/app/module/drivers/kscan/kscan_composite.c @@ -7,6 +7,7 @@ #define DT_DRV_COMPAT zmk_kscan_composite #include +#include #include #include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -26,10 +27,10 @@ struct kscan_composite_child_config { .row_offset = DT_PROP(inst, row_offset), \ .column_offset = DT_PROP(inst, column_offset)}, -const struct kscan_composite_child_config kscan_composite_children[] = { - DT_FOREACH_CHILD(MATRIX_NODE_ID, CHILD_CONFIG)}; - -struct kscan_composite_config {}; +struct kscan_composite_config { + const struct kscan_composite_child_config *children; + size_t children_len; +}; struct kscan_composite_data { kscan_callback_t callback; @@ -38,51 +39,80 @@ struct kscan_composite_data { }; static int kscan_composite_enable_callback(const struct device *dev) { - for (int i = 0; i < ARRAY_SIZE(kscan_composite_children); i++) { - const struct kscan_composite_child_config *cfg = &kscan_composite_children[i]; + const struct kscan_composite_config *cfg = dev->config; - kscan_enable_callback(cfg->child); + for (int i = 0; i < cfg->children_len; i++) { + const struct kscan_composite_child_config *child_cfg = &cfg->children[i]; + +#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) + if (!pm_device_runtime_is_enabled(dev) && pm_device_runtime_is_enabled(child_cfg->child)) { + pm_device_runtime_get(child_cfg->child); + } +#elif IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_action_run(child_cfg->child, PM_DEVICE_ACTION_RESUME); +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + + kscan_enable_callback(child_cfg->child); } return 0; } static int kscan_composite_disable_callback(const struct device *dev) { - for (int i = 0; i < ARRAY_SIZE(kscan_composite_children); i++) { - const struct kscan_composite_child_config *cfg = &kscan_composite_children[i]; + const struct kscan_composite_config *cfg = dev->config; + for (int i = 0; i < cfg->children_len; i++) { + const struct kscan_composite_child_config *child_cfg = &cfg->children[i]; - kscan_disable_callback(cfg->child); + kscan_disable_callback(child_cfg->child); + +#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) + if (!pm_device_runtime_is_enabled(dev) && pm_device_runtime_is_enabled(child_cfg->child)) { + pm_device_runtime_put(child_cfg->child); + } +#elif IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_action_run(child_cfg->child, PM_DEVICE_ACTION_SUSPEND); +#endif // IS_ENABLED(CONFIG_PM_DEVICE) } return 0; } +#define KSCAN_COMP_INST_DEV(n) DEVICE_DT_GET(DT_DRV_INST(n)), + +static const struct device *all_instances[] = {DT_INST_FOREACH_STATUS_OKAY(KSCAN_COMP_INST_DEV)}; + static void kscan_composite_child_callback(const struct device *child_dev, uint32_t row, uint32_t column, bool pressed) { // TODO: Ideally we can get this passed into our callback! - const struct device *dev = DEVICE_DT_GET(DT_DRV_INST(0)); - struct kscan_composite_data *data = dev->data; + for (int i = 0; i < ARRAY_SIZE(all_instances); i++) { - for (int i = 0; i < ARRAY_SIZE(kscan_composite_children); i++) { - const struct kscan_composite_child_config *cfg = &kscan_composite_children[i]; + const struct device *dev = all_instances[i]; + const struct kscan_composite_config *cfg = dev->config; + struct kscan_composite_data *data = dev->data; - if (cfg->child != child_dev) { - continue; + for (int c = 0; c < cfg->children_len; c++) { + const struct kscan_composite_child_config *child_cfg = &cfg->children[c]; + + if (child_cfg->child != child_dev) { + continue; + } + + data->callback(dev, row + child_cfg->row_offset, column + child_cfg->column_offset, + pressed); } - - data->callback(dev, row + cfg->row_offset, column + cfg->column_offset, pressed); } } static int kscan_composite_configure(const struct device *dev, kscan_callback_t callback) { + const struct kscan_composite_config *cfg = dev->config; struct kscan_composite_data *data = dev->data; if (!callback) { return -EINVAL; } - for (int i = 0; i < ARRAY_SIZE(kscan_composite_children); i++) { - const struct kscan_composite_child_config *cfg = &kscan_composite_children[i]; + for (int i = 0; i < cfg->children_len; i++) { + const struct kscan_composite_child_config *child_cfg = &cfg->children[i]; - kscan_config(cfg->child, &kscan_composite_child_callback); + kscan_config(child_cfg->child, &kscan_composite_child_callback); } data->callback = callback; @@ -95,6 +125,10 @@ static int kscan_composite_init(const struct device *dev) { data->dev = dev; +#if IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_init_suspended(dev); +#endif + return 0; } @@ -104,9 +138,32 @@ static const struct kscan_driver_api mock_driver_api = { .disable_callback = kscan_composite_disable_callback, }; -static const struct kscan_composite_config kscan_composite_config = {}; +#if IS_ENABLED(CONFIG_PM_DEVICE) -static struct kscan_composite_data kscan_composite_data; +static int kscan_composite_pm_action(const struct device *dev, enum pm_device_action action) { + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + return kscan_composite_disable_callback(dev); + case PM_DEVICE_ACTION_RESUME: + return kscan_composite_enable_callback(dev); + default: + return -ENOTSUP; + } +} -DEVICE_DT_INST_DEFINE(0, kscan_composite_init, NULL, &kscan_composite_data, &kscan_composite_config, - POST_KERNEL, CONFIG_ZMK_KSCAN_COMPOSITE_INIT_PRIORITY, &mock_driver_api); +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + +#define KSCAN_COMP_DEV(n) \ + static const struct kscan_composite_child_config kscan_composite_children_##n[] = { \ + DT_INST_FOREACH_CHILD(n, CHILD_CONFIG)}; \ + static const struct kscan_composite_config kscan_composite_config_##n = { \ + .children = kscan_composite_children_##n, \ + .children_len = ARRAY_SIZE(kscan_composite_children_##n), \ + }; \ + static struct kscan_composite_data kscan_composite_data_##n; \ + PM_DEVICE_DT_INST_DEFINE(n, kscan_composite_pm_action); \ + DEVICE_DT_INST_DEFINE(n, kscan_composite_init, PM_DEVICE_DT_INST_GET(n), \ + &kscan_composite_data_##n, &kscan_composite_config_##n, POST_KERNEL, \ + CONFIG_ZMK_KSCAN_COMPOSITE_INIT_PRIORITY, &mock_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(KSCAN_COMP_DEV) From d75e38859356ab2cd7de034700a5d4f32a9800ad Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Sun, 4 Aug 2024 22:43:01 -0700 Subject: [PATCH 10/22] feat(docs): Add split keyboards feature page Co-authored-by: Nicolas Munnich <98408764+Nick-Munnich@users.noreply.github.com> --- docs/docs/features/split-keyboards.md | 92 +++++++++++++++++++++++++++ docs/sidebars.js | 1 + 2 files changed, 93 insertions(+) create mode 100644 docs/docs/features/split-keyboards.md diff --git a/docs/docs/features/split-keyboards.md b/docs/docs/features/split-keyboards.md new file mode 100644 index 00000000..96af3472 --- /dev/null +++ b/docs/docs/features/split-keyboards.md @@ -0,0 +1,92 @@ +--- +title: Split Keyboards +sidebar_label: Split Keyboards +--- + +ZMK supports setups where a keyboard is split into two or more physical parts (also called "sides" or "halves" when split in two), each with their own controller running ZMK. The parts communicate with each other to work as a single keyboard device. + +:::note[Split communication protocols] +Currently ZMK only supports split keyboards that communicate with each other wirelessly over BLE. +As such, only controllers that support BLE can be used with ZMK split keyboards. + +Supporting split communication over wired protocols is planned, allowing for ZMK split keyboards using non-wireless controllers. +::: + +## Central and Peripheral Roles + +In split keyboards running ZMK, one part is assigned the "central" role which receives key position and sensor events from the other parts that are called "peripherals." +The central runs the necessary keymap logic to convert received events into HID events such as keycodes and then communicates with the connected host devices, e.g. over USB or bluetooth. + +The internal keyboard state (like active layers) is handled exclusively by the central. +Peripherals _cannot_ communicate with host devices on their own, since they can only communicate with the central. +They will not present as keyboard devices when connected over USB and will not advertise as pairable BLE keyboards. + +By convention, for a keyboard split into two "halves" the left half is set as the central and the right as a peripheral. + +### Configuration + +The [new shield guide](../development/new-shield.mdx) details how to define a split keyboard shield with two parts, enabling the split feature and setting up the necessary roles for each part. + +Also see the reference section on [split keyboards configuration](../config/system.md#split-keyboards) where the relevant symbols include `CONFIG_ZMK_SPLIT` that enables the feature, `CONFIG_ZMK_SPLIT_ROLE_CENTRAL` which sets the central role and `CONFIG_ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS` that sets the number of peripherals. + +### Latency Considerations + +Since peripherals communicate through centrals, the key and sensor events originating from them will naturally have a larger latency, especially with a wireless split communication protocol. +For the currently used BLE-based transport, split communication increases the average latency by 3.75ms with a worst case increase of 7.5ms. + +## Building and Flashing Firmware + +ZMK split keyboards require building and flashing different firmware files for each split part. +For instance when [using the GitHub workflow](../user-setup.mdx) to build two part split keyboards, two firmware files that typically contain `_left` and `_right` in the file names will be produced. +These files need to be flashed to the respective controllers of the two halves. + +:::tip[Updating your keymap] +Since the keymap processing is primarily done on the central side, for keymap changes it will typically be enough to flash the controller of the central half. +However if you make changes to [config files](../config/index.md) that should apply to all parts, you need to flash to all parts. +Any changes in ZMK related to split keyboard features might also necessitate doing this. +::: + +## Pairing for Wireless Split Keyboards + +Split keyboards with BLE-based split communications (i.e. all officially supported split keyboards) have an internal pairing procedure between the central and each peripheral. +When the central has an open slot for a peripheral, it will advertise for connections (which will not be visible to non-ZMK devices). +Then, any peripheral that has not yet bonded to a peripheral will pair to it. +Similar to how [bluetooth profiles](bluetooth.md) are managed between the keyboard and host devices, the bonding information will be stored with the corresponding hardware addresses of the other keyboard part, on both the central and peripheral. + +In practice, this means that your split keyboard parts will automatically pair and work the first time they are all on at the same time. +However, if this process somehow went wrong or you used controllers in a different split keyboard configuration before, you will need to explicitly clear the stored bond information so that the parts can pair properly. +For this, please follow [the specified procedure](../troubleshooting/connection-issues.mdx#split-keyboard-halves-unable-to-pair) in the troubleshooting section. + +:::warning +If the central keyboard part is either advertising for a pairing or waiting for disconnected peripherals, it will consume more power and drain batteries faster. +::: + +## Behaviors with Locality + +Most ZMK [behaviors](../behaviors/index.mdx) are processed exclusively on the central of the split keyboard as it handles the keymap state and any communication with the host devices. +However, certain behaviors have "global" or "source" localities, where they can affect the peripherals when invoked. + +### Global Locality Behaviors + +These are behaviors that affect all keyboard parts, such as changing lighting effects. +Currently these are the following behaviors: + +- [RGB underglow behaviors](../behaviors/underglow.md) +- [Backlight behaviors](../behaviors/backlight.md) +- [Power management behaviors](../behaviors/power.md) +- [Soft off behavior](../behaviors/soft-off.md) + +### Source Locality Behaviors + +These behaviors only affect the keyboard part that they are invoked from, given that they were invoked from a plain behavior binding (i.e. not nested inside another behavior) on a keymap layer. These behaviors include: + +- [Reset behaviors](../behaviors/reset.md) + +:::note[Peripheral invocation] +Peripherals must be paired and connected to the central in order to be able to activate these behaviors, even if it is possible to trigger the behavior using only keys on a particular peripheral. +This is because the key bindings are processed on the central side which would then instruct the peripheral side to run the behavior's effect. +::: + +:::note[Combos] +[Combos](combos.md) always invoke behaviors with source locality on the central. +::: diff --git a/docs/sidebars.js b/docs/sidebars.js index 1233ccf1..1c718e51 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -33,6 +33,7 @@ module.exports = { Features: [ "features/keymaps", "features/bluetooth", + "features/split-keyboards", "features/combos", "features/conditional-layers", "features/debouncing", From 2501f1f548128ad1a5ead0807006f315254faa39 Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Sun, 4 Aug 2024 22:48:17 -0700 Subject: [PATCH 11/22] feat(docs): Add references to new split keyboards page --- docs/docs/behaviors/backlight.md | 2 +- docs/docs/behaviors/power.md | 2 +- docs/docs/behaviors/reset.md | 6 +----- docs/docs/behaviors/underglow.md | 2 +- docs/docs/config/index.md | 2 +- docs/docs/config/system.md | 2 +- docs/docs/customization.md | 2 +- docs/docs/development/new-behavior.mdx | 2 +- docs/docs/development/new-shield.mdx | 2 +- docs/docs/features/battery.md | 2 +- docs/docs/features/bluetooth.md | 2 +- docs/docs/features/combos.md | 4 ++-- docs/docs/troubleshooting/connection-issues.mdx | 2 +- docs/docs/user-setup.mdx | 2 ++ 14 files changed, 16 insertions(+), 18 deletions(-) diff --git a/docs/docs/behaviors/backlight.md b/docs/docs/behaviors/backlight.md index 12ed01f1..040bb7b7 100644 --- a/docs/docs/behaviors/backlight.md +++ b/docs/docs/behaviors/backlight.md @@ -58,4 +58,4 @@ However the settings will only be saved after [`CONFIG_ZMK_SETTINGS_SAVE_DEBOUNC ## Split Keyboards -Backlight behaviors are global: This means that when triggered, they affect both the central and peripheral side of split keyboards. +Backlight behaviors are [global](../features/split-keyboards.md#global-locality-behaviors): This means that when triggered, they affect both the central and peripheral side of split keyboards. diff --git a/docs/docs/behaviors/power.md b/docs/docs/behaviors/power.md index dce7b155..53110f95 100644 --- a/docs/docs/behaviors/power.md +++ b/docs/docs/behaviors/power.md @@ -70,4 +70,4 @@ However it will only be saved after [`CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE`](../con ## Split Keyboards -Power management behaviors are global: This means that when triggered, they affects both the central and peripheral side of split keyboards. +Power management behaviors are [global](../features/split-keyboards.md#global-locality-behaviors): This means that when triggered, they affects both the central and peripheral side of split keyboards. diff --git a/docs/docs/behaviors/reset.md b/docs/docs/behaviors/reset.md index 60e69431..1e2110b9 100644 --- a/docs/docs/behaviors/reset.md +++ b/docs/docs/behaviors/reset.md @@ -44,8 +44,4 @@ Example: ## Split Keyboards -Both basic and bootloader reset behaviors are source-specific: This means that it affects the side of the keyboard that contains the behavior binding for split keyboards. For example if you press a key with the `&sys_reset` binding on the left half of the keyboard, the left half will be reset. If you want to be able to reset both sides you can put the bindings on both sides of the keyboard and activate it on the side you would like to reset. - -:::note[Peripheral invocation] -The peripheral side of the keyboard has to be paired and connected to the central side in order to be able to activate these behaviors, even if it is possible to trigger the behavior using only keys on that side. This is because the key bindings are processed on the central side which would then instruct the peripheral side to reset. -::: +Both basic and bootloader reset behaviors are [source-specific](../features/split-keyboards.md##source-locality-behaviors): This means that it affects the side of the keyboard that contains the behavior binding for split keyboards. For example if you press a key with the `&sys_reset` binding on the left half of the keyboard, the left half will be reset. If you want to be able to reset both sides you can put the bindings on both sides of the keyboard and activate it on the side you would like to reset. diff --git a/docs/docs/behaviors/underglow.md b/docs/docs/behaviors/underglow.md index 490789a7..bd549395 100644 --- a/docs/docs/behaviors/underglow.md +++ b/docs/docs/behaviors/underglow.md @@ -77,4 +77,4 @@ However the settings will only be saved after [`CONFIG_ZMK_SETTINGS_SAVE_DEBOUNC ## Split Keyboards -RGB underglow behaviors are global: This means that when triggered, they affect both the central and peripheral side of split keyboards. +RGB underglow behaviors are [global](../features/split-keyboards.md#global-locality-behaviors): This means that when triggered, they affect both the central and peripheral side of split keyboards. diff --git a/docs/docs/config/index.md b/docs/docs/config/index.md index 666cf4d9..a7c89b7b 100644 --- a/docs/docs/config/index.md +++ b/docs/docs/config/index.md @@ -24,7 +24,7 @@ When building with a `zmk-config` folder, ZMK will search the `zmk-config/config These files hold your personal settings for the keyboard. All files are optional. If present, they override any configuration set in the board or shield folders. Otherwise, the default configuration and/or keymap is used. -When using a split keyboard, you can use a single file without the `_left` or `_right` suffix to configure both sides. For example, `corne.conf` and `corne.keymap` will apply to both `corne_left` and `corne_right`. If a shared config file exists, any left or right files will be ignored. +When using a [split keyboard](../features/split-keyboards.md), you can use a single file without the `_left` or `_right` suffix to configure both sides. For example, `corne.conf` and `corne.keymap` will apply to both `corne_left` and `corne_right`. If a shared config file exists, any left or right files will be ignored. ### Board Folder diff --git a/docs/docs/config/system.md b/docs/docs/config/system.md index fef9f53a..1a5306bd 100644 --- a/docs/docs/config/system.md +++ b/docs/docs/config/system.md @@ -117,7 +117,7 @@ Note that `CONFIG_BT_MAX_CONN` and `CONFIG_BT_MAX_PAIRED` should be set to the s ### Split keyboards -Following split keyboard settings are defined in [zmk/app/src/split/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/split/Kconfig) (generic) and [zmk/app/src/split/bluetooth/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/split/bluetooth/Kconfig) (bluetooth). +Following [split keyboard](../features/split-keyboards.md) settings are defined in [zmk/app/src/split/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/split/Kconfig) (generic) and [zmk/app/src/split/bluetooth/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/split/bluetooth/Kconfig) (bluetooth). | Config | Type | Description | Default | | ------------------------------------------------------- | ---- | -------------------------------------------------------------------------- | ------------------------------------------ | diff --git a/docs/docs/customization.md b/docs/docs/customization.md index 87d78a22..5b02003d 100644 --- a/docs/docs/customization.md +++ b/docs/docs/customization.md @@ -49,7 +49,7 @@ It is also possible to build firmware locally on your computer by following the For normal keyboards, follow the same flashing instructions as before to flash your updated firmware. -For split keyboards, only the central (left) side will need to be reflashed if you are just updating your keymap. +For [split keyboards](features/split-keyboards.md#building-and-flashing-firmware), only the central (left) side will need to be reflashed if you are just updating your keymap. More troubleshooting information for split keyboards can be found [here](troubleshooting/connection-issues.mdx#split-keyboard-halves-unable-to-pair). ## Building Additional Keyboards diff --git a/docs/docs/development/new-behavior.mdx b/docs/docs/development/new-behavior.mdx index dca19288..113ddf0a 100644 --- a/docs/docs/development/new-behavior.mdx +++ b/docs/docs/development/new-behavior.mdx @@ -346,7 +346,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) endif() ``` -For behaviors that do not require central locality, the following options for updating `app/CmakeLists.txt` also exist: +For behaviors that do not require [central locality](../features/split-keyboards.md#behaviors-with-locality), the following options for updating `app/CMakeLists.txt` also exist: - Behavior applies to unibody, or central or peripheral half of keyboard: place `target_sources(app PRIVATE .c)` line _before_ `if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)` - Behavior applies to _only_ central half of split keyboard: place `target_sources(app PRIVATE .c)` after `if (CONFIG_ZMK_SPLIT AND CONFIG_ZMK_SPLIT_ROLE_CENTRAL)` diff --git a/docs/docs/development/new-shield.mdx b/docs/docs/development/new-shield.mdx index ae97f6af..5234e13e 100644 --- a/docs/docs/development/new-shield.mdx +++ b/docs/docs/development/new-shield.mdx @@ -29,7 +29,7 @@ The high level steps are: It may be helpful to review the upstream [shields documentation](https://docs.zephyrproject.org/3.5.0/hardware/porting/shields.html#shields) to get a proper understanding of the underlying system before continuing. :::note -ZMK support for split keyboards requires a few more files than single boards to ensure proper connectivity between the central and peripheral units. Check the following guides thoroughly to ensure that all the files are in place. +ZMK support for [split keyboards](../features/split-keyboards.md) requires a few more files than single boards to ensure proper connectivity between the central and peripheral units. Check the following guides thoroughly to ensure that all the files are in place. ::: ## New Zephyr Module Repository diff --git a/docs/docs/features/battery.md b/docs/docs/features/battery.md index 29142eed..14dd6661 100644 --- a/docs/docs/features/battery.md +++ b/docs/docs/features/battery.md @@ -5,7 +5,7 @@ sidebar_label: Battery Level If your keyboard has a battery sensor, ZMK will report its battery level to the connected bluetooth host and show it on the keyboard's display, if it has one. -For split keyboards, only the battery level of the central (usually left) side is reported over bluetooth by default. ZMK can be [configured to report the battery levels for peripherals](../config/battery.md#peripheral-battery-monitoring), but not many host systems will display this information without additional configuration or the use of third party utilities. +For [split keyboards](split-keyboards.md), only the battery level of the central (usually left) side is reported over bluetooth by default. ZMK can be [configured to report the battery levels for peripherals](../config/battery.md#peripheral-battery-monitoring), but not many host systems will display this information without additional configuration or the use of third party utilities. :::note diff --git a/docs/docs/features/bluetooth.md b/docs/docs/features/bluetooth.md index d148acd8..79af22b4 100644 --- a/docs/docs/features/bluetooth.md +++ b/docs/docs/features/bluetooth.md @@ -3,7 +3,7 @@ title: Bluetooth sidebar_label: Bluetooth --- -ZMK's bluetooth functionality allows users to connect their keyboards to hosts using Bluetooth Low Energy (BLE) technology. It also is used for split keyboards to connect the two halves wirelessly. +ZMK's bluetooth functionality allows users to connect their keyboards to hosts using Bluetooth Low Energy (BLE) technology. It also is used for [split keyboards](split-keyboards.md) to connect the two halves wirelessly. :::note diff --git a/docs/docs/features/combos.md b/docs/docs/features/combos.md index 63c57c38..ad24d61f 100644 --- a/docs/docs/features/combos.md +++ b/docs/docs/features/combos.md @@ -45,7 +45,7 @@ Key positions are numbered like the keys in your keymap, starting at 0. So, if t - You are not limited to `&kp` bindings. You can use all ZMK behaviors there, like `&mo`, `&bt`, `&mt`, `<` etc. :::note[Source-specific behaviors on split keyboards] -Invoking a source-specific behavior such as one of the [reset behaviors](behaviors/reset.md) using a combo will always trigger it on the central side of the keyboard, regardless of the side that the keys corresponding to `key-positions` are on. +Invoking a [source-specific behavior](split-keyboards.md#source-locality-behaviors) such as one of the [reset behaviors](behaviors/reset.md) using a combo will always trigger it on the central side of the keyboard, regardless of the side that the keys corresponding to `key-positions` are on. ::: -See [combo configuration](/docs/config/combos) for advanced configuration options. +See [combo configuration](../config/combos.md) for advanced configuration options. diff --git a/docs/docs/troubleshooting/connection-issues.mdx b/docs/docs/troubleshooting/connection-issues.mdx index f077702c..fe80326b 100644 --- a/docs/docs/troubleshooting/connection-issues.mdx +++ b/docs/docs/troubleshooting/connection-issues.mdx @@ -24,7 +24,7 @@ export const Uf2Tabs = (props) => ( ## Split Keyboard Halves Unable to Pair -Split keyboard halves will automatically pair with one another, but there are some cases where this breaks, and the pairing needs to be reset, for example: +[Split keyboard](../features/split-keyboards.md) halves will automatically pair with one another, but there are some cases where this breaks, and the pairing needs to be reset, for example: - Switching which halves are the central/peripheral. - Replacing the controller for one of the halves. diff --git a/docs/docs/user-setup.mdx b/docs/docs/user-setup.mdx index f6cb5eb2..a79a2956 100644 --- a/docs/docs/user-setup.mdx +++ b/docs/docs/user-setup.mdx @@ -209,6 +209,7 @@ connect to it wirelessly. For split keyboards, only the central half (typically the left side) will send keyboard outputs over USB or advertise to other devices over bluetooth. Peripheral half will only send keystrokes to the central once they are paired and connected. For this reason it is recommended to test the left half of a split keyboard first. +Please refer to [split keyboards documentation](features/split-keyboards.md) for more information. ::: @@ -221,6 +222,7 @@ ZMK supports multiple BLE “profiles”, which allows you to connect to and swi ### Connecting Split Keyboard Halves For split keyboards, after flashing each half individually you can connect them together by resetting them at the same time. Within a few seconds of resetting, both halves should automatically connect to each other. +Please refer to [the pairing section in the split keyboards documentation](features/split-keyboards.md#pairing-for-wireless-split-keyboards) for more information. :::note From 2a0708d1fc58dd59616e55e120020308db7406f1 Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Tue, 6 Aug 2024 17:21:53 -0700 Subject: [PATCH 12/22] feat(docs): Add locality warning for nested behaviors --- docs/docs/features/split-keyboards.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/docs/features/split-keyboards.md b/docs/docs/features/split-keyboards.md index 96af3472..fd8ce16c 100644 --- a/docs/docs/features/split-keyboards.md +++ b/docs/docs/features/split-keyboards.md @@ -68,8 +68,7 @@ However, certain behaviors have "global" or "source" localities, where they can ### Global Locality Behaviors -These are behaviors that affect all keyboard parts, such as changing lighting effects. -Currently these are the following behaviors: +These are behaviors that affect all keyboard parts, such as changing lighting effects: - [RGB underglow behaviors](../behaviors/underglow.md) - [Backlight behaviors](../behaviors/backlight.md) @@ -78,10 +77,15 @@ Currently these are the following behaviors: ### Source Locality Behaviors -These behaviors only affect the keyboard part that they are invoked from, given that they were invoked from a plain behavior binding (i.e. not nested inside another behavior) on a keymap layer. These behaviors include: +These behaviors only affect the keyboard part that they are invoked from: - [Reset behaviors](../behaviors/reset.md) +:::warning[Nesting behaviors with locality] +Currently there is [an issue](https://github.com/zmkfirmware/zmk/issues/1494) preventing both global and source locality behaviors from working as expected if they are invoked from another behavior, such as a hold-tap, tap dance or a mod-morph. +For this reason it is recommended that these behaviors are placed directly on a keymap layer. +::: + :::note[Peripheral invocation] Peripherals must be paired and connected to the central in order to be able to activate these behaviors, even if it is possible to trigger the behavior using only keys on a particular peripheral. This is because the key bindings are processed on the central side which would then instruct the peripheral side to run the behavior's effect. From 2eff266f5b55677ea5a50ed8c79ff25c54674bc0 Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Wed, 7 Aug 2024 23:47:27 -0700 Subject: [PATCH 13/22] feat(docs): Add battery life remark for split keyboards --- docs/docs/features/split-keyboards.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/docs/features/split-keyboards.md b/docs/docs/features/split-keyboards.md index fd8ce16c..aae61090 100644 --- a/docs/docs/features/split-keyboards.md +++ b/docs/docs/features/split-keyboards.md @@ -23,6 +23,11 @@ They will not present as keyboard devices when connected over USB and will not a By convention, for a keyboard split into two "halves" the left half is set as the central and the right as a peripheral. +:::info[Battery life impact] +For BLE-based split keyboards, the central uses significantly more power than the peripherals because its radio needs to periodically wake up to check for incoming transmissions. +You can refer to the [power profiler](/power-profiler) to see battery life estimates for different roles. +::: + ### Configuration The [new shield guide](../development/new-shield.mdx) details how to define a split keyboard shield with two parts, enabling the split feature and setting up the necessary roles for each part. From 3fec690def0af2e012fce3eeaee0292698d9d261 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Mon, 12 Aug 2024 11:57:56 -0600 Subject: [PATCH 14/22] feat: Add config settings for sticky keys/hold-tap * Add new Kconfig symbols to config max held sticky-keys/hold-taps and for max captured events during hold-tap resolution. --- app/CMakeLists.txt | 4 +-- app/Kconfig.behaviors | 38 +++++++++++++++++++++++++ app/src/behaviors/behavior_hold_tap.c | 4 +-- app/src/behaviors/behavior_sticky_key.c | 2 +- docs/docs/config/behaviors.md | 13 +++++++++ 5 files changed, 56 insertions(+), 5 deletions(-) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index ab2e1502..5e19713a 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -47,8 +47,8 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources_ifdef(CONFIG_ZMK_MOUSE app PRIVATE src/mouse.c) target_sources(app PRIVATE src/behaviors/behavior_key_press.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_KEY_TOGGLE app PRIVATE src/behaviors/behavior_key_toggle.c) - target_sources(app PRIVATE src/behaviors/behavior_hold_tap.c) - target_sources(app PRIVATE src/behaviors/behavior_sticky_key.c) + target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_HOLD_TAP app PRIVATE src/behaviors/behavior_hold_tap.c) + target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_STICKY_KEY app PRIVATE src/behaviors/behavior_sticky_key.c) target_sources(app PRIVATE src/behaviors/behavior_caps_word.c) target_sources(app PRIVATE src/behaviors/behavior_key_repeat.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_MACRO app PRIVATE src/behaviors/behavior_macro.c) diff --git a/app/Kconfig.behaviors b/app/Kconfig.behaviors index d3f4537e..adada062 100644 --- a/app/Kconfig.behaviors +++ b/app/Kconfig.behaviors @@ -40,6 +40,29 @@ endchoice endif + +config ZMK_BEHAVIOR_HOLD_TAP + bool + default y + depends on DT_HAS_ZMK_BEHAVIOR_HOLD_TAP_ENABLED + +if ZMK_BEHAVIOR_HOLD_TAP + +config ZMK_BEHAVIOR_HOLD_TAP_MAX_HELD + int "Hold Tap Max Held" + default 10 + help + Max number of simultaneously held hold-taps + + +config ZMK_BEHAVIOR_HOLD_TAP_MAX_CAPTURED_EVENTS + int "Hold Tap Max Captured Events" + default 40 + help + Max number of captured system events while waiting to resolve hold taps + +endif + config ZMK_BEHAVIOR_KEY_TOGGLE bool default y @@ -51,6 +74,21 @@ config ZMK_BEHAVIOR_MOUSE_KEY_PRESS depends on DT_HAS_ZMK_BEHAVIOR_MOUSE_KEY_PRESS_ENABLED imply ZMK_MOUSE +config ZMK_BEHAVIOR_STICKY_KEY + bool + default y + depends on DT_HAS_ZMK_BEHAVIOR_STICKY_KEY_ENABLED + +if ZMK_BEHAVIOR_STICKY_KEY + +config ZMK_BEHAVIOR_STICKY_KEY_MAX_HELD + int "Sticky Key Max Held" + default 10 + help + Max number of simultaneously held sticky keys + +endif + config ZMK_BEHAVIOR_SOFT_OFF bool default y diff --git a/app/src/behaviors/behavior_hold_tap.c b/app/src/behaviors/behavior_hold_tap.c index 1c050c44..c45ee803 100644 --- a/app/src/behaviors/behavior_hold_tap.c +++ b/app/src/behaviors/behavior_hold_tap.c @@ -24,8 +24,8 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) -#define ZMK_BHV_HOLD_TAP_MAX_HELD 10 -#define ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS 40 +#define ZMK_BHV_HOLD_TAP_MAX_HELD CONFIG_ZMK_BEHAVIOR_HOLD_TAP_MAX_HELD +#define ZMK_BHV_HOLD_TAP_MAX_CAPTURED_EVENTS CONFIG_ZMK_BEHAVIOR_HOLD_TAP_MAX_CAPTURED_EVENTS // increase if you have keyboard with more keys. #define ZMK_BHV_HOLD_TAP_POSITION_NOT_USED 9999 diff --git a/app/src/behaviors/behavior_sticky_key.c b/app/src/behaviors/behavior_sticky_key.c index d1299c78..6016fc2d 100644 --- a/app/src/behaviors/behavior_sticky_key.c +++ b/app/src/behaviors/behavior_sticky_key.c @@ -26,7 +26,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define KEY_PRESS DEVICE_DT_NAME(DT_INST(0, zmk_behavior_key_press)) -#define ZMK_BHV_STICKY_KEY_MAX_HELD 10 +#define ZMK_BHV_STICKY_KEY_MAX_HELD CONFIG_ZMK_BEHAVIOR_STICKY_KEY_MAX_HELD #define ZMK_BHV_STICKY_KEY_POSITION_FREE UINT32_MAX diff --git a/docs/docs/config/behaviors.md b/docs/docs/config/behaviors.md index 28abdf27..11ad0002 100644 --- a/docs/docs/config/behaviors.md +++ b/docs/docs/config/behaviors.md @@ -51,6 +51,13 @@ Creates a custom behavior that triggers one behavior when a key is held or a dif See the [hold-tap behavior](../behaviors/hold-tap.mdx) documentation for more details and examples. +### Kconfig + +| Config | Type | Description | Default | +| -------------------------------------------------- | ---- | --------------------------------------------------------------------------------------------- | ------- | +| `CONFIG_ZMK_BEHAVIOR_HOLD_TAP_MAX_HELD` | int | Maximum number of simultaneous held hold-taps. | 10 | +| `CONFIG_ZMK_BEHAVIOR_HOLD_TAP_MAX_CAPTURED_EVENTS` | int | Maximum number of system events to capture while deferring a hold or tap decision resolution. | 40 | + ### Devicetree Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-hold-tap.yaml) @@ -227,6 +234,12 @@ Creates a custom behavior that triggers a behavior and keeps it pressed it until See the [sticky key behavior](../behaviors/sticky-key.md) and [sticky layer behavior](../behaviors/sticky-layer.md) documentation for more details and examples. +### Kconfig + +| Config | Type | Description | Default | +| ----------------------------------------- | ---- | ------------------------------------------------ | ------- | +| `CONFIG_ZMK_BEHAVIOR_STICKY_KEY_MAX_HELD` | int | Maximum number of simultaneous held sticky keys. | 10 | + ### Devicetree Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-sticky-key.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-sticky-key.yaml) From 6d50ba555383f86b6cde68dabd48abd5f3877967 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Mon, 12 Aug 2024 19:11:16 -0400 Subject: [PATCH 15/22] chore: Docs review tweaks. Co-authored-by: Cem Aksoylar --- app/Kconfig.behaviors | 1 - docs/docs/config/behaviors.md | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/Kconfig.behaviors b/app/Kconfig.behaviors index adada062..96609b93 100644 --- a/app/Kconfig.behaviors +++ b/app/Kconfig.behaviors @@ -54,7 +54,6 @@ config ZMK_BEHAVIOR_HOLD_TAP_MAX_HELD help Max number of simultaneously held hold-taps - config ZMK_BEHAVIOR_HOLD_TAP_MAX_CAPTURED_EVENTS int "Hold Tap Max Captured Events" default 40 diff --git a/docs/docs/config/behaviors.md b/docs/docs/config/behaviors.md index 11ad0002..a422e599 100644 --- a/docs/docs/config/behaviors.md +++ b/docs/docs/config/behaviors.md @@ -53,10 +53,10 @@ See the [hold-tap behavior](../behaviors/hold-tap.mdx) documentation for more de ### Kconfig -| Config | Type | Description | Default | -| -------------------------------------------------- | ---- | --------------------------------------------------------------------------------------------- | ------- | -| `CONFIG_ZMK_BEHAVIOR_HOLD_TAP_MAX_HELD` | int | Maximum number of simultaneous held hold-taps. | 10 | -| `CONFIG_ZMK_BEHAVIOR_HOLD_TAP_MAX_CAPTURED_EVENTS` | int | Maximum number of system events to capture while deferring a hold or tap decision resolution. | 40 | +| Config | Type | Description | Default | +| -------------------------------------------------- | ---- | -------------------------------------------------------------------------------------------- | ------- | +| `CONFIG_ZMK_BEHAVIOR_HOLD_TAP_MAX_HELD` | int | Maximum number of simultaneous held hold-taps | 10 | +| `CONFIG_ZMK_BEHAVIOR_HOLD_TAP_MAX_CAPTURED_EVENTS` | int | Maximum number of system events to capture while deferring a hold or tap decision resolution | 40 | ### Devicetree @@ -236,9 +236,9 @@ See the [sticky key behavior](../behaviors/sticky-key.md) and [sticky layer beha ### Kconfig -| Config | Type | Description | Default | -| ----------------------------------------- | ---- | ------------------------------------------------ | ------- | -| `CONFIG_ZMK_BEHAVIOR_STICKY_KEY_MAX_HELD` | int | Maximum number of simultaneous held sticky keys. | 10 | +| Config | Type | Description | Default | +| ----------------------------------------- | ---- | ----------------------------------------------- | ------- | +| `CONFIG_ZMK_BEHAVIOR_STICKY_KEY_MAX_HELD` | int | Maximum number of simultaneous held sticky keys | 10 | ### Devicetree From fb3af5327e7e42b826c9fffc2cd26c60c7ec8c9e Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Tue, 13 Aug 2024 08:24:13 -0600 Subject: [PATCH 16/22] fix: Fix hardware metadata validation after image changes. * Newer Docker image requires --break-system-packages for pip. --- .github/workflows/hardware-metadata-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hardware-metadata-validation.yml b/.github/workflows/hardware-metadata-validation.yml index 716cd976..3e3faa0d 100644 --- a/.github/workflows/hardware-metadata-validation.yml +++ b/.github/workflows/hardware-metadata-validation.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install dependencies - run: pip install -r app/scripts/requirements.txt + run: pip install --break-system-packages -r app/scripts/requirements.txt - name: West init run: west init -l app - name: Update modules (west update) From 049c070b3a42463f5e9410f713e1eb9132c8e793 Mon Sep 17 00:00:00 2001 From: Nicolas Munnich <98408764+Nick-Munnich@users.noreply.github.com> Date: Tue, 13 Aug 2024 19:47:45 +0200 Subject: [PATCH 17/22] feat(docs): Minor improvements to the docs (#2411) --- docs/docs/config/index.md | 7 ++----- docs/docs/troubleshooting/connection-issues.mdx | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/docs/docs/config/index.md b/docs/docs/config/index.md index a7c89b7b..409b1f04 100644 --- a/docs/docs/config/index.md +++ b/docs/docs/config/index.md @@ -37,11 +37,9 @@ ZMK will search for config files in: ...where `` is the name of the board and `` is the root directory of any [included module](../features/modules.mdx). These files describe the hardware of the board. -ZMK will search the board folder for the following config files: +ZMK will search the board folder for the following config files _in addition_ to [Zephyr board-defining files](https://docs.zephyrproject.org/3.5.0/hardware/porting/board_porting.html#create-your-board-directory): -- `_defconfig` (Kconfig) - `.conf` (Kconfig) -- `.dts` (Devicetree) - `.keymap` (Devicetree, keyboards with onboard controllers only) Shared config files (excluding any `_left` or `_right` suffix) are not currently supported in board folders. @@ -59,10 +57,9 @@ When building with a shield, ZMK will search for config files in: ...where `` is the name of the shield and `` is the root directory of any [included module](../features/modules.mdx). These files describe the hardware of the shield that the board is plugged into. -ZMK will search the shield folder for the following config files: +ZMK will search the shield folder for the following config files _in addition_ to [Zephyr shield-defining files](https://docs.zephyrproject.org/3.5.0/hardware/porting/shields.html#shield-porting-and-configuration): - `.conf` (Kconfig) -- `.overlay` (Devicetree) - `.keymap` (Devicetree) Shared config files (excluding any `_left` or `_right` suffix) are not currently supported in shield folders. diff --git a/docs/docs/troubleshooting/connection-issues.mdx b/docs/docs/troubleshooting/connection-issues.mdx index fe80326b..19d9f441 100644 --- a/docs/docs/troubleshooting/connection-issues.mdx +++ b/docs/docs/troubleshooting/connection-issues.mdx @@ -22,6 +22,10 @@ export const Uf2Tabs = (props) => ( ); +:::tip +[USB logging](../development/usb-logging.mdx) can be very helpful for diagnosing issues with ZMK. However, when connected to USB your ZMK device will output to USB by default. To troubleshoot wireless connection issues using logging, you will need to [change the preferred output endpoint](../behaviors/outputs.md). +::: + ## Split Keyboard Halves Unable to Pair [Split keyboard](../features/split-keyboards.md) halves will automatically pair with one another, but there are some cases where this breaks, and the pairing needs to be reset, for example: @@ -105,6 +109,18 @@ Some devices and operating systems may have additional restrictions that they re Since ZMK associates pairing/bond keys with hardware addresses of hosts, you cannot pair to two different operating systems in a dual boot system at the same time. While you can find [documented workarounds](https://wiki.archlinux.org/title/bluetooth#Dual_boot_pairing) that involve copying pairing keys across operating systems and use both OS with a single profile, they can be fairly involved and should be followed with caution. +### Mitigating a Faulty Oscillator + +Rarely, due to a manufacturing error, a device may have a faulty oscillator. This prevents it from functioning correctly wirelessly and can be difficult to diagnose. Some microcontrollers such as the `nRF52840` allow for the usage of an internal oscillator instead of an external one. This results in increased power draw but can be a solution if your device does indeed have a faulty oscillator. + +For the `nRF52840`, the flag to set to use the internal oscillator is: + +``` +CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=y +``` + +Other microcontrollers may have similar configuration options [found in the Zephyr documentation](https://docs.zephyrproject.org/3.5.0/search.html?q=CONFIG_CLOCK_CONTROL&check_keywords=yes&area=default). Do note that not all microcontrollers allow for the use of an internal oscillator, though. + ## Issues While Connected ### Unreliable/Weak Connection From 00e481809464a013dce95f9b0f691566f6931d2d Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Tue, 13 Aug 2024 10:46:36 -0700 Subject: [PATCH 18/22] fix(docs): Fix broken anchor in reset behaviors --- docs/docs/behaviors/reset.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/behaviors/reset.md b/docs/docs/behaviors/reset.md index 1e2110b9..0ccde851 100644 --- a/docs/docs/behaviors/reset.md +++ b/docs/docs/behaviors/reset.md @@ -44,4 +44,4 @@ Example: ## Split Keyboards -Both basic and bootloader reset behaviors are [source-specific](../features/split-keyboards.md##source-locality-behaviors): This means that it affects the side of the keyboard that contains the behavior binding for split keyboards. For example if you press a key with the `&sys_reset` binding on the left half of the keyboard, the left half will be reset. If you want to be able to reset both sides you can put the bindings on both sides of the keyboard and activate it on the side you would like to reset. +Both basic and bootloader reset behaviors are [source-specific](../features/split-keyboards.md#source-locality-behaviors): This means that it affects the side of the keyboard that contains the behavior binding for split keyboards. For example if you press a key with the `&sys_reset` binding on the left half of the keyboard, the left half will be reset. If you want to be able to reset both sides you can put the bindings on both sides of the keyboard and activate it on the side you would like to reset. From 3de8a56fcb7c98e2bf98e00742594da791580fab Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Mon, 12 Aug 2024 18:14:53 -0700 Subject: [PATCH 19/22] feat(docs): Add a note on column numbers for DTS errors --- docs/docs/troubleshooting/building-issues.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docs/troubleshooting/building-issues.md b/docs/docs/troubleshooting/building-issues.md index e37d3934..a8d1fc99 100644 --- a/docs/docs/troubleshooting/building-issues.md +++ b/docs/docs/troubleshooting/building-issues.md @@ -29,6 +29,8 @@ A `devicetree error` followed by a reference to the line number on `.k devicetree error: /__w/zmk-config/zmk-config/config/cradio.keymap:109 (column 4): parse error: expected ';' or ',' ``` +Note that the provided number for the column corresponds to the numbering after the preprocessor expansion and it will likely not match the column numbers in your keymap. + A `devicetree error` followed by an `empty_file.c` reference with `lacks #binding-cells` string indicates possible problems with improper parameters for specific bindings: ``` From 8b909acdcf988bdbf43289d56e81cc46339e869d Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Tue, 13 Aug 2024 10:58:13 -0700 Subject: [PATCH 20/22] refactor(docs): Add subheaders for devicetree error types --- docs/docs/troubleshooting/building-issues.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/docs/troubleshooting/building-issues.md b/docs/docs/troubleshooting/building-issues.md index a8d1fc99..5ea52611 100644 --- a/docs/docs/troubleshooting/building-issues.md +++ b/docs/docs/troubleshooting/building-issues.md @@ -23,6 +23,8 @@ If you get an error stating `Keymap node not found, check a keymap is available ### Devicetree Errors +#### "parse error" + A `devicetree error` followed by a reference to the line number on `.keymap` refers to an issue at the exact line position in that file. For example, below error message indicates a missing `;` at line 109 of the `cradio.keymap` file: ``` @@ -31,6 +33,8 @@ devicetree error: /__w/zmk-config/zmk-config/config/cradio.keymap:109 (column 4) Note that the provided number for the column corresponds to the numbering after the preprocessor expansion and it will likely not match the column numbers in your keymap. +#### "lacks #binding-cells" + A `devicetree error` followed by an `empty_file.c` reference with `lacks #binding-cells` string indicates possible problems with improper parameters for specific bindings: ``` @@ -39,6 +43,8 @@ devicetree error: Date: Fri, 26 Jul 2024 14:47:27 -0700 Subject: [PATCH 21/22] fix(docs): Fix mod-morph terms from keycodes to bindings --- docs/docs/behaviors/mod-morph.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/docs/behaviors/mod-morph.md b/docs/docs/behaviors/mod-morph.md index 8949ec2c..f77d7511 100644 --- a/docs/docs/behaviors/mod-morph.md +++ b/docs/docs/behaviors/mod-morph.md @@ -5,18 +5,17 @@ sidebar_label: Mod-Morph ## Summary -The Mod-Morph behavior sends a different keypress, depending on whether a specified modifier is being held during the keypress. +The mod-morph behavior invokes a different behavior depending on whether any of the specified modifiers are being held during the key press. -- If you tap the key by itself, the first keycode is sent. -- If you tap the key while holding the specified modifier, the second keycode is sent. +- If you tap the key by itself, the first behavior binding is activated. +- If you tap the key while holding (any of) the specified modifier(s), the second behavior binding is activated. ## Mod-Morph -The Mod-Morph behavior acts as one of two keycodes, depending on if the required modifier is being held during the keypress. - ### Configuration -An example of how to implement the mod-morph "Grave Escape": +Below is an example of how to implement the mod-morph "Grave Escape". When assigned to a key, pressing the key on its own will send an +Escape keycode but pressing it while a shift or GUI modifier is held sends the grave `` ` `` keycode instead: ```dts / { @@ -31,7 +30,7 @@ An example of how to implement the mod-morph "Grave Escape": }; ``` -Note that this specific mod-morph exists in ZMK by default using code `&gresc`. +Note that this specific mod-morph exists in ZMK by default using the binding `&gresc`. ### Behavior Binding @@ -67,7 +66,7 @@ mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>; ### Advanced Configuration -`keep-mods` +#### `keep-mods` When a modifier specified in `mods` is being held, it won't be sent along with the morphed keycode unless it is also specified in `keep-mods`. By default `keep-mods` equals `0`, which means no modifier specified in `mods` will be sent along with the morphed keycode. From ba0dee088f014911e0486288068b8a10284d73f9 Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Mon, 29 Jul 2024 23:45:08 -0700 Subject: [PATCH 22/22] feat(docs): Add section on nesting mod-morphs Co-authored-by: Nicolas Munnich <98408764+Nick-Munnich@users.noreply.github.com> --- docs/docs/behaviors/mod-morph.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/docs/behaviors/mod-morph.md b/docs/docs/behaviors/mod-morph.md index f77d7511..879db998 100644 --- a/docs/docs/behaviors/mod-morph.md +++ b/docs/docs/behaviors/mod-morph.md @@ -86,6 +86,34 @@ For example, the following configuration morphs `LEFT_SHIFT` + `BACKSPACE` into }; ``` +#### Trigger conditions with multiple modifiers + +Any modifier used in the `mods` property will activate a mod-morph; it isn't possible to require that multiple modifiers are held _together_ in order to activate it. +However, you can nest multiple mod-morph behaviors to achieve more complex decision logic, where you use one (or two) mod-morph behaviors in the `bindings` fields of another mod-morph. + +As an example, consider the following two mod-morphs: + +```dts +/ { + behaviors { + morph_BC: morph_BC { + compatible = "zmk,behavior-mod-morph"; + #binding-cells = <0>; + bindings = <&kp B>, <&kp C>; + mods = <(MOD_LCTL|MOD_RCTL)>; + }; + morph_ABC: morph_ABC { + compatible = "zmk,behavior-mod-morph"; + #binding-cells = <0>; + bindings = <&kp A>, <&morph_BC>; + mods = <(MOD_LSFT|MOD_RSFT)>; + }; + }; +}; +``` + +When you assign `&morph_ABC` to a key position and press it, it will output `A` by default. If you press it while a shift modifier is held it will output `B`, and if you are also holding a control modifier it will output `C` instead. + :::note[Karabiner-Elements (macOS) interfering with mod-morphs] If the first modified key press sends the modifier along with the morphed keycode and [Karabiner-Elements](https://karabiner-elements.pqrs.org/) is running, disable the "Modify Events" toggle from Karabiner's "Devices" settings page for the keyboard running ZMK.