diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c2d1992d..9e09dc21 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,6 +12,12 @@ on: schedule: - cron: "22 4 * * *" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name == 'schedule' }} + cancel-in-progress: true + +permissions: {} + jobs: build: if: ${{ always() }} @@ -25,6 +31,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + persist-credentials: false - name: Cache west modules uses: actions/cache@v4 env: @@ -131,7 +139,7 @@ jobs: throw new Error('Failed to build one or more configurations'); } compile-matrix: - if: ${{ always() }} + if: ${{ !cancelled() }} runs-on: ubuntu-latest needs: [core-coverage, board-changes, nightly] outputs: @@ -179,6 +187,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + persist-credentials: false - name: Use Node.js uses: actions/setup-node@v4 with: @@ -284,7 +294,7 @@ jobs: }); }))).flat(); nightly: - if: ${{ github.event_name == 'schedule' }} + if: ${{ github.event_name == 'schedule' && github.repository_owner == 'zmkfirmware' }} runs-on: ubuntu-latest needs: get-grouped-hardware outputs: @@ -335,6 +345,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + persist-credentials: false - name: Use Node.js uses: actions/setup-node@v4 with: @@ -413,7 +425,11 @@ jobs: board-changes: ${{ steps.board-changes.outputs.result }} core-changes: ${{ steps.core-changes.outputs.result }} steps: - - uses: tj-actions/changed-files@v42 + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: tj-actions/changed-files@v44 id: changed-files with: json: true diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 0b681ea9..ab2e1502 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -11,6 +11,10 @@ project(zmk) zephyr_linker_sources(SECTIONS include/linker/zmk-behaviors.ld) zephyr_linker_sources(RODATA include/linker/zmk-events.ld) +if(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS) + zephyr_linker_sources(DATA_SECTIONS include/linker/zmk-behavior-local-id-map.ld) +endif() + zephyr_syscall_header(${APPLICATION_SOURCE_DIR}/include/drivers/behavior.h) zephyr_syscall_header(${APPLICATION_SOURCE_DIR}/include/drivers/ext_power.h) @@ -20,9 +24,9 @@ target_include_directories(app PRIVATE include) target_sources(app PRIVATE src/stdlib.c) target_sources(app PRIVATE src/activity.c) target_sources(app PRIVATE src/behavior.c) -target_sources(app PRIVATE src/kscan.c) target_sources_ifdef(CONFIG_ZMK_KSCAN_SIDEBAND_BEHAVIORS app PRIVATE src/kscan_sideband_behaviors.c) target_sources(app PRIVATE src/matrix_transform.c) +target_sources(app PRIVATE src/physical_layouts.c) target_sources(app PRIVATE src/sensors.c) target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/wpm.c) target_sources(app PRIVATE src/event_manager.c) diff --git a/app/Kconfig b/app/Kconfig index 5aedd9d9..a45f2dc2 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -113,6 +113,12 @@ config ZMK_HID_INDICATORS Enable HID indicators, used for detecting state of Caps/Scroll/Num Lock, Kata, and Compose. +config ZMK_HID_SEPARATE_MOD_RELEASE_REPORT + bool "Release Modifiers Separately" + help + Send a separate release event for the modifiers, to make sure the release + of the modifier doesn't get recognized before the actual key's release event. + menu "Output Types" config ZMK_USB @@ -490,7 +496,11 @@ if USB_DEVICE_STACK config ZMK_USB_INIT_PRIORITY int "USB Init Priority" - default 50 + default 94 + +config ZMK_USB_HID_INIT_PRIORITY + int "USB HID Init Priority" + default 95 #USB endif diff --git a/app/Kconfig.behaviors b/app/Kconfig.behaviors index c9754bf7..d3f4537e 100644 --- a/app/Kconfig.behaviors +++ b/app/Kconfig.behaviors @@ -1,6 +1,45 @@ # Copyright (c) 2023 The ZMK Contributors # SPDX-License-Identifier: MIT +config ZMK_BEHAVIOR_METADATA + bool "Metadata" + help + Enabling this option adds APIs for documenting and fetching + metadata describing a behaviors name, and supported parameters. + +config ZMK_BEHAVIOR_LOCAL_IDS + bool "Local IDs" + +if ZMK_BEHAVIOR_LOCAL_IDS + +config ZMK_BEHAVIOR_LOCAL_IDS_IN_BINDINGS + bool "Track in behavior bindings" + +choice ZMK_BEHAVIOR_LOCAL_ID_TYPE + prompt "Local ID Type" + +config ZMK_BEHAVIOR_LOCAL_ID_TYPE_SETTINGS_TABLE + bool "Settings Table" + depends on SETTINGS + select ZMK_BEHAVIOR_LOCAL_IDS_IN_BINDINGS + help + Use persistent entries in the settings subsystem to identify + behaviors by local ID, which uses the device name to generate + a new settings entry tying a presistant local ID to that name. + This guarantees stable, colllision-free local IDs at the expense + of settings storage used. + +config ZMK_BEHAVIOR_LOCAL_ID_TYPE_CRC16 + bool "CRC16 Hash" + help + Use the CRC16-ANSI hash of behavior device names to generate + stable behavior local IDs. This saves on settings storage at + the expense of (highly unlikely) risk of collisions. + +endchoice + +endif + config ZMK_BEHAVIOR_KEY_TOGGLE bool default y @@ -35,4 +74,4 @@ config ZMK_BEHAVIOR_SENSOR_ROTATE_VAR config ZMK_BEHAVIOR_MACRO bool default y - depends on DT_HAS_ZMK_BEHAVIOR_MACRO_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_ONE_PARAM_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_TWO_PARAM_ENABLED \ No newline at end of file + depends on DT_HAS_ZMK_BEHAVIOR_MACRO_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_ONE_PARAM_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_TWO_PARAM_ENABLED diff --git a/app/boards/01space_rp2040_042lcd.overlay b/app/boards/01space_rp2040_042lcd.overlay index d89e53f4..b5d2cdb2 100644 --- a/app/boards/01space_rp2040_042lcd.overlay +++ b/app/boards/01space_rp2040_042lcd.overlay @@ -4,6 +4,4 @@ * SPDX-License-Identifier: MIT */ -#include "usb_console.dtsi" - &xiao_serial { status = "disabled"; }; diff --git a/app/boards/adafruit_kb2040.overlay b/app/boards/adafruit_kb2040.overlay index b14e0d04..72b3adca 100644 --- a/app/boards/adafruit_kb2040.overlay +++ b/app/boards/adafruit_kb2040.overlay @@ -4,6 +4,4 @@ * SPDX-License-Identifier: MIT */ -#include "usb_console.dtsi" - &pro_micro_serial { status = "disabled"; }; diff --git a/app/boards/adafruit_qt_py_rp2040.overlay b/app/boards/adafruit_qt_py_rp2040.overlay index d89e53f4..b5d2cdb2 100644 --- a/app/boards/adafruit_qt_py_rp2040.overlay +++ b/app/boards/adafruit_qt_py_rp2040.overlay @@ -4,6 +4,4 @@ * SPDX-License-Identifier: MIT */ -#include "usb_console.dtsi" - &xiao_serial { status = "disabled"; }; diff --git a/app/boards/arm/adv360pro/adv360pro.dtsi b/app/boards/arm/adv360pro/adv360pro.dtsi index c64d0d3f..ea68624b 100644 --- a/app/boards/arm/adv360pro/adv360pro.dtsi +++ b/app/boards/arm/adv360pro/adv360pro.dtsi @@ -21,7 +21,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,kscan = &kscan0; zmk,backlight = &backlight; zmk,battery = &vbatt; @@ -90,11 +89,8 @@ status = "okay"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; &flash0 { diff --git a/app/boards/arm/adv360pro/pre_dt_board.cmake b/app/boards/arm/adv360pro/pre_dt_board.cmake new file mode 100644 index 00000000..05b0efe5 --- /dev/null +++ b/app/boards/arm/adv360pro/pre_dt_board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller +# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html + +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") \ No newline at end of file diff --git a/app/boards/arm/bdn9/bdn9_rev2.dts b/app/boards/arm/bdn9/bdn9_rev2.dts index 6e15408a..2189530d 100644 --- a/app/boards/arm/bdn9/bdn9_rev2.dts +++ b/app/boards/arm/bdn9/bdn9_rev2.dts @@ -16,7 +16,6 @@ chosen { zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,kscan = &kscan; zmk,underglow = &led_strip; }; @@ -106,13 +105,10 @@ apb1-prescaler = <1>; }; -&usb { +zephyr_udc0: &usb { status = "okay"; pinctrl-0 = <&usb_dm_pa11 &usb_dp_pa12>; pinctrl-names = "default"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; &rtc { diff --git a/app/boards/arm/bluemicro840/bluemicro840_v1.dts b/app/boards/arm/bluemicro840/bluemicro840_v1.dts index aabdf310..84d3ebae 100644 --- a/app/boards/arm/bluemicro840/bluemicro840_v1.dts +++ b/app/boards/arm/bluemicro840/bluemicro840_v1.dts @@ -17,7 +17,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,battery = &vbatt; }; @@ -82,11 +81,8 @@ pinctrl-names = "default", "sleep"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; diff --git a/app/boards/arm/bluemicro840/pre_dt_board.cmake b/app/boards/arm/bluemicro840/pre_dt_board.cmake new file mode 100644 index 00000000..05b0efe5 --- /dev/null +++ b/app/boards/arm/bluemicro840/pre_dt_board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller +# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html + +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") \ No newline at end of file diff --git a/app/boards/arm/bt60/bt60.dtsi b/app/boards/arm/bt60/bt60.dtsi index 655d2576..83ff3f04 100644 --- a/app/boards/arm/bt60/bt60.dtsi +++ b/app/boards/arm/bt60/bt60.dtsi @@ -16,7 +16,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,battery = &vbatt; zmk,kscan = &kscan0; zmk,matrix-transform = &default_transform; @@ -70,11 +69,8 @@ status = "okay"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; diff --git a/app/boards/arm/bt60/pre_dt_board.cmake b/app/boards/arm/bt60/pre_dt_board.cmake new file mode 100644 index 00000000..05b0efe5 --- /dev/null +++ b/app/boards/arm/bt60/pre_dt_board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller +# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html + +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") \ No newline at end of file diff --git a/app/boards/arm/ckp/ckp.dtsi b/app/boards/arm/ckp/ckp.dtsi index 4142622a..b127cabc 100644 --- a/app/boards/arm/ckp/ckp.dtsi +++ b/app/boards/arm/ckp/ckp.dtsi @@ -142,7 +142,7 @@ status = "okay"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; }; diff --git a/app/boards/arm/ckp/pre_dt_board.cmake b/app/boards/arm/ckp/pre_dt_board.cmake new file mode 100644 index 00000000..05b0efe5 --- /dev/null +++ b/app/boards/arm/ckp/pre_dt_board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller +# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html + +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") \ No newline at end of file diff --git a/app/boards/arm/corneish_zen/corneish_zen.dtsi b/app/boards/arm/corneish_zen/corneish_zen.dtsi index 881fadb0..dbd6f93e 100644 --- a/app/boards/arm/corneish_zen/corneish_zen.dtsi +++ b/app/boards/arm/corneish_zen/corneish_zen.dtsi @@ -20,7 +20,6 @@ zephyr,flash = &flash0; zmk,kscan = &kscan0; zmk,display = &epd; - zephyr,console = &cdc_acm_uart; zmk,matrix-transform = &default_transform; }; @@ -76,11 +75,8 @@ status = "okay"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; &flash0 { diff --git a/app/boards/arm/corneish_zen/pre_dt_board.cmake b/app/boards/arm/corneish_zen/pre_dt_board.cmake new file mode 100644 index 00000000..05b0efe5 --- /dev/null +++ b/app/boards/arm/corneish_zen/pre_dt_board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller +# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html + +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") \ No newline at end of file diff --git a/app/boards/arm/dz60rgb/dz60rgb_rev1.dts b/app/boards/arm/dz60rgb/dz60rgb_rev1.dts index 4e1d4b66..b8fac4e2 100644 --- a/app/boards/arm/dz60rgb/dz60rgb_rev1.dts +++ b/app/boards/arm/dz60rgb/dz60rgb_rev1.dts @@ -16,7 +16,6 @@ chosen { zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,kscan = &kscan0; zmk,matrix-transform = &default_transform; }; @@ -65,11 +64,8 @@ RC(4,0) RC(4,1) RC(4,2) RC(4,5) RC( }; -&usb { +zephyr_udc0: &usb { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; &flash0 { diff --git a/app/boards/arm/ferris/ferris_rev02.dts b/app/boards/arm/ferris/ferris_rev02.dts index 4fecd280..a0e28f03 100644 --- a/app/boards/arm/ferris/ferris_rev02.dts +++ b/app/boards/arm/ferris/ferris_rev02.dts @@ -17,7 +17,6 @@ chosen { zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,kscan = &kscan; zmk,matrix-transform = &transform; /* TODO: Enable once we support the IC for underglow @@ -110,14 +109,11 @@ }; }; -&usb { +zephyr_udc0: &usb { status = "okay"; pinctrl-0 = <&usb_dm_pa11 &usb_dp_pa12>; pinctrl-names = "default"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; &clk_hsi { diff --git a/app/boards/arm/glove80/glove80.dtsi b/app/boards/arm/glove80/glove80.dtsi index 4803488b..d51a73ac 100644 --- a/app/boards/arm/glove80/glove80.dtsi +++ b/app/boards/arm/glove80/glove80.dtsi @@ -15,7 +15,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; }; default_transform: keymap_transform_0 { @@ -59,11 +58,8 @@ status = "okay"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; &flash0 { diff --git a/app/boards/arm/glove80/pre_dt_board.cmake b/app/boards/arm/glove80/pre_dt_board.cmake new file mode 100644 index 00000000..05b0efe5 --- /dev/null +++ b/app/boards/arm/glove80/pre_dt_board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller +# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html + +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") \ No newline at end of file diff --git a/app/boards/arm/kbdfans_tofu65/kbdfans_tofu65_v2.dts b/app/boards/arm/kbdfans_tofu65/kbdfans_tofu65_v2.dts index 18c92671..60ba1da0 100644 --- a/app/boards/arm/kbdfans_tofu65/kbdfans_tofu65_v2.dts +++ b/app/boards/arm/kbdfans_tofu65/kbdfans_tofu65_v2.dts @@ -13,8 +13,6 @@ chosen { zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; - zephyr,shell-uart = &cdc_acm_uart; zephyr,code-partition = &code_partition; zmk,kscan = &kscan0; zmk,matrix-transform = &default_transform; @@ -108,11 +106,8 @@ RC(4,0) RC(4,1) RC(4,2) RC(4,6) RC(4,8) RC(4,9) }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; diff --git a/app/boards/arm/mikoto/mikoto_520.dts b/app/boards/arm/mikoto/mikoto_520.dts index a6ca5081..3ea48cd9 100644 --- a/app/boards/arm/mikoto/mikoto_520.dts +++ b/app/boards/arm/mikoto/mikoto_520.dts @@ -17,7 +17,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,battery = &vbatt; }; @@ -81,11 +80,8 @@ pinctrl-names = "default", "sleep"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; diff --git a/app/boards/arm/mikoto/pre_dt_board.cmake b/app/boards/arm/mikoto/pre_dt_board.cmake new file mode 100644 index 00000000..05b0efe5 --- /dev/null +++ b/app/boards/arm/mikoto/pre_dt_board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller +# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html + +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") \ No newline at end of file diff --git a/app/boards/arm/nice60/nice60.dts b/app/boards/arm/nice60/nice60.dts index d1b9f992..4eefbb9d 100644 --- a/app/boards/arm/nice60/nice60.dts +++ b/app/boards/arm/nice60/nice60.dts @@ -20,7 +20,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,battery = &vbatt; zmk,kscan = &kscan0; zmk,matrix-transform = &default_transform; @@ -129,11 +128,8 @@ RC(4,0) RC(4,1) RC(4,2) RC(4,5) R }; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; &flash0 { diff --git a/app/boards/arm/nice60/pre_dt_board.cmake b/app/boards/arm/nice60/pre_dt_board.cmake new file mode 100644 index 00000000..05b0efe5 --- /dev/null +++ b/app/boards/arm/nice60/pre_dt_board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller +# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html + +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") \ No newline at end of file diff --git a/app/boards/arm/nice_nano/nice_nano.dtsi b/app/boards/arm/nice_nano/nice_nano.dtsi index 41770dd3..839845c8 100644 --- a/app/boards/arm/nice_nano/nice_nano.dtsi +++ b/app/boards/arm/nice_nano/nice_nano.dtsi @@ -16,7 +16,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; }; leds { @@ -65,11 +64,8 @@ pinctrl-names = "default", "sleep"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; diff --git a/app/boards/arm/nice_nano/pre_dt_board.cmake b/app/boards/arm/nice_nano/pre_dt_board.cmake new file mode 100644 index 00000000..05b0efe5 --- /dev/null +++ b/app/boards/arm/nice_nano/pre_dt_board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller +# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html + +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") \ No newline at end of file diff --git a/app/boards/arm/nrf52840_m2/nrf52840_m2.dts b/app/boards/arm/nrf52840_m2/nrf52840_m2.dts index 85e9ce21..39569f0b 100644 --- a/app/boards/arm/nrf52840_m2/nrf52840_m2.dts +++ b/app/boards/arm/nrf52840_m2/nrf52840_m2.dts @@ -15,7 +15,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,battery = &vbatt; }; @@ -57,12 +56,9 @@ status = "okay"; }; -&usbd { +zephyr_udc0: &usbd { compatible = "nordic,nrf-usbd"; status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; diff --git a/app/boards/arm/nrf52840_m2/pre_dt_board.cmake b/app/boards/arm/nrf52840_m2/pre_dt_board.cmake new file mode 100644 index 00000000..05b0efe5 --- /dev/null +++ b/app/boards/arm/nrf52840_m2/pre_dt_board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller +# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html + +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") \ No newline at end of file diff --git a/app/boards/arm/nrfmicro/nrfmicro_11.dts b/app/boards/arm/nrfmicro/nrfmicro_11.dts index 04368ab8..b80ed4c6 100644 --- a/app/boards/arm/nrfmicro/nrfmicro_11.dts +++ b/app/boards/arm/nrfmicro/nrfmicro_11.dts @@ -17,7 +17,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; }; leds { @@ -69,11 +68,8 @@ pinctrl-names = "default", "sleep"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; diff --git a/app/boards/arm/nrfmicro/nrfmicro_11_flipped.dts b/app/boards/arm/nrfmicro/nrfmicro_11_flipped.dts index 600935aa..7b89b62f 100644 --- a/app/boards/arm/nrfmicro/nrfmicro_11_flipped.dts +++ b/app/boards/arm/nrfmicro/nrfmicro_11_flipped.dts @@ -17,7 +17,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; }; leds { @@ -69,11 +68,8 @@ pinctrl-names = "default", "sleep"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; diff --git a/app/boards/arm/nrfmicro/nrfmicro_13.dts b/app/boards/arm/nrfmicro/nrfmicro_13.dts index 716e5b18..0cb22e63 100644 --- a/app/boards/arm/nrfmicro/nrfmicro_13.dts +++ b/app/boards/arm/nrfmicro/nrfmicro_13.dts @@ -17,7 +17,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,battery = &vbatt; }; @@ -81,11 +80,8 @@ pinctrl-names = "default", "sleep"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; diff --git a/app/boards/arm/nrfmicro/nrfmicro_13_52833.dts b/app/boards/arm/nrfmicro/nrfmicro_13_52833.dts index f57c413d..866276bb 100644 --- a/app/boards/arm/nrfmicro/nrfmicro_13_52833.dts +++ b/app/boards/arm/nrfmicro/nrfmicro_13_52833.dts @@ -17,7 +17,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,battery = &vbatt; }; @@ -81,11 +80,8 @@ pinctrl-names = "default", "sleep"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; diff --git a/app/boards/arm/nrfmicro/pre_dt_board.cmake b/app/boards/arm/nrfmicro/pre_dt_board.cmake new file mode 100644 index 00000000..05b0efe5 --- /dev/null +++ b/app/boards/arm/nrfmicro/pre_dt_board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller +# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html + +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") \ No newline at end of file diff --git a/app/boards/arm/pillbug/pillbug.dts b/app/boards/arm/pillbug/pillbug.dts index c30d306e..cf4f62fc 100644 --- a/app/boards/arm/pillbug/pillbug.dts +++ b/app/boards/arm/pillbug/pillbug.dts @@ -18,7 +18,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,battery = &vbatt; }; @@ -83,11 +82,8 @@ pinctrl-names = "default", "sleep"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; diff --git a/app/boards/arm/pillbug/pre_dt_board.cmake b/app/boards/arm/pillbug/pre_dt_board.cmake new file mode 100644 index 00000000..05b0efe5 --- /dev/null +++ b/app/boards/arm/pillbug/pre_dt_board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller +# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html + +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") \ No newline at end of file diff --git a/app/boards/arm/planck/planck_rev6.dts b/app/boards/arm/planck/planck_rev6.dts index 5b8e16b2..85b75140 100644 --- a/app/boards/arm/planck/planck_rev6.dts +++ b/app/boards/arm/planck/planck_rev6.dts @@ -16,7 +16,6 @@ chosen { zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,kscan = &kscan0; zmk,matrix-transform = &layout_grid_transform; }; @@ -96,13 +95,10 @@ layout_2x2u_transform: }; }; -&usb { +zephyr_udc0: &usb { pinctrl-0 = <&usb_dm_pa11 &usb_dp_pa12>; pinctrl-names = "default"; status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; &clk_hse { diff --git a/app/boards/arm/preonic/preonic_rev3.dts b/app/boards/arm/preonic/preonic_rev3.dts index d14355da..79f88c33 100644 --- a/app/boards/arm/preonic/preonic_rev3.dts +++ b/app/boards/arm/preonic/preonic_rev3.dts @@ -17,7 +17,6 @@ chosen { zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,kscan = &kscan0; zmk,matrix-transform = &layout_grid_transform; }; @@ -90,13 +89,10 @@ }; }; -&usb { +zephyr_udc0: &usb { pinctrl-0 = <&usb_dm_pa11 &usb_dp_pa12>; pinctrl-names = "default"; status = "okay"; -cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; &clk_hse { diff --git a/app/boards/arm/proton_c/proton_c.dts b/app/boards/arm/proton_c/proton_c.dts index 3aad62c8..05872b25 100644 --- a/app/boards/arm/proton_c/proton_c.dts +++ b/app/boards/arm/proton_c/proton_c.dts @@ -16,7 +16,6 @@ chosen { zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart0; }; aliases { @@ -66,13 +65,10 @@ apb2-prescaler = <1>; }; -&usb { +zephyr_udc0: &usb { pinctrl-0 = <&usb_dm_pa11 &usb_dp_pa12>; pinctrl-names = "default"; status = "okay"; - cdc_acm_uart0: cdc_acm_uart0 { - compatible = "zephyr,cdc-acm-uart"; - }; }; &rtc { diff --git a/app/boards/arm/puchi_ble/pre_dt_board.cmake b/app/boards/arm/puchi_ble/pre_dt_board.cmake new file mode 100644 index 00000000..05b0efe5 --- /dev/null +++ b/app/boards/arm/puchi_ble/pre_dt_board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller +# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html + +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") \ No newline at end of file diff --git a/app/boards/arm/puchi_ble/puchi_ble_v1.dts b/app/boards/arm/puchi_ble/puchi_ble_v1.dts index 05aba8d3..9f3194e0 100644 --- a/app/boards/arm/puchi_ble/puchi_ble_v1.dts +++ b/app/boards/arm/puchi_ble/puchi_ble_v1.dts @@ -17,7 +17,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,battery = &vbatt; }; @@ -72,11 +71,8 @@ pinctrl-names = "default", "sleep"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; diff --git a/app/boards/arm/s40nc/pre_dt_board.cmake b/app/boards/arm/s40nc/pre_dt_board.cmake new file mode 100644 index 00000000..05b0efe5 --- /dev/null +++ b/app/boards/arm/s40nc/pre_dt_board.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +# Suppresses duplicate unit-address warning at build time for power, clock, acl and flash-controller +# https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html + +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") \ No newline at end of file diff --git a/app/boards/arm/s40nc/s40nc.dts b/app/boards/arm/s40nc/s40nc.dts index a2eb89ea..4c37030d 100644 --- a/app/boards/arm/s40nc/s40nc.dts +++ b/app/boards/arm/s40nc/s40nc.dts @@ -16,7 +16,6 @@ zephyr,code-partition = &code_partition; zephyr,sram = &sram0; zephyr,flash = &flash0; - zephyr,console = &cdc_acm_uart; zmk,battery = &vbatt; zmk,kscan = &kscan0; zmk,matrix-transform = &default_transform; @@ -93,11 +92,8 @@ status = "okay"; }; -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; &flash0 { diff --git a/app/boards/boardsource_blok.overlay b/app/boards/boardsource_blok.overlay index b14e0d04..72b3adca 100644 --- a/app/boards/boardsource_blok.overlay +++ b/app/boards/boardsource_blok.overlay @@ -4,6 +4,4 @@ * SPDX-License-Identifier: MIT */ -#include "usb_console.dtsi" - &pro_micro_serial { status = "disabled"; }; diff --git a/app/boards/rpi_pico.overlay b/app/boards/rpi_pico.overlay deleted file mode 100644 index efc8e080..00000000 --- a/app/boards/rpi_pico.overlay +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (c) 2023 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#include "usb_console.dtsi" - diff --git a/app/boards/seeeduino_xiao.overlay b/app/boards/seeeduino_xiao.overlay deleted file mode 100644 index 285ee4de..00000000 --- a/app/boards/seeeduino_xiao.overlay +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2022 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -/ { - chosen { - zephyr,console = &cdc_acm_uart; - }; -}; - -&usb0 { - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; -}; - diff --git a/app/boards/seeeduino_xiao_ble.overlay b/app/boards/seeeduino_xiao_ble.overlay index e0934691..f6a60858 100644 --- a/app/boards/seeeduino_xiao_ble.overlay +++ b/app/boards/seeeduino_xiao_ble.overlay @@ -7,7 +7,6 @@ / { chosen { - zephyr,console = &cdc_acm_uart; zmk,battery = &vbatt; }; @@ -24,12 +23,6 @@ status = "okay"; }; -&usbd { - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; -}; - &qspi { status = "okay"; pinctrl-0 = <&qspi_default>; diff --git a/app/boards/seeeduino_xiao_rp2040.conf b/app/boards/seeeduino_xiao_rp2040.conf index 21c1893d..714e715c 100644 --- a/app/boards/seeeduino_xiao_rp2040.conf +++ b/app/boards/seeeduino_xiao_rp2040.conf @@ -2,3 +2,10 @@ CONFIG_CONSOLE=n CONFIG_SERIAL=n CONFIG_UART_CONSOLE=n CONFIG_ZMK_USB=y + +CONFIG_MPU_ALLOW_FLASH_WRITE=y +CONFIG_NVS=y +CONFIG_SETTINGS_NVS=y +CONFIG_FLASH=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH_MAP=y diff --git a/app/boards/seeeduino_xiao_rp2040.overlay b/app/boards/seeeduino_xiao_rp2040.overlay index d89e53f4..e6ba8136 100644 --- a/app/boards/seeeduino_xiao_rp2040.overlay +++ b/app/boards/seeeduino_xiao_rp2040.overlay @@ -4,6 +4,19 @@ * SPDX-License-Identifier: MIT */ -#include "usb_console.dtsi" - &xiao_serial { status = "disabled"; }; + +&code_partition { + reg = <0x100 (DT_SIZE_M(2) - 0x100 - DT_SIZE_K(512))>; +}; + +&flash0 { + reg = <0x10000000 DT_SIZE_M(2)>; + + partitions { + storage_partition: partition@180000 { + reg = <0x180000 DT_SIZE_K(512)>; + read-only; + }; + }; +}; diff --git a/app/boards/shields/zmk_uno/zmk_uno.dtsi b/app/boards/shields/zmk_uno/zmk_uno.dtsi index ba1d3b5d..9ea625a4 100644 --- a/app/boards/shields/zmk_uno/zmk_uno.dtsi +++ b/app/boards/shields/zmk_uno/zmk_uno.dtsi @@ -40,10 +40,8 @@ nice_view_spi: &arduino_spi { / { chosen { - zmk,kscan = &kscan_matrix; zmk,backlight = &backlight; zmk,underglow = &led_strip; - zmk,matrix-transform = &matrix_transform; }; // Commented out until we add more powerful power domain support @@ -109,7 +107,6 @@ nice_view_spi: &arduino_spi { kscan_direct: kscan_direct { compatible = "zmk,kscan-gpio-direct"; wakeup-source; - status = "disabled"; input-gpios = <&arduino_header 10 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> diff --git a/app/boards/shields/zmk_uno/zmk_uno.keymap b/app/boards/shields/zmk_uno/zmk_uno.keymap index a7f6a267..186cdb60 100644 --- a/app/boards/shields/zmk_uno/zmk_uno.keymap +++ b/app/boards/shields/zmk_uno/zmk_uno.keymap @@ -13,13 +13,9 @@ // Uncomment the following lines if using the "Direct Wire" jumper to switch the matrix to a direct wire. -// &kscan_direct { status = "okay"; }; -// &kscan_matrix { status = "disabled"; }; - // / { // chosen { -// zmk,matrix-transform = &direct_matrix_transform; -// zmk,kscan = &kscan_direct; +// zmk,physical-layout = &direct_physical_layout; // }; // }; diff --git a/app/boards/shields/zmk_uno/zmk_uno.overlay b/app/boards/shields/zmk_uno/zmk_uno.overlay index 3d105abf..2a8eb266 100644 --- a/app/boards/shields/zmk_uno/zmk_uno.overlay +++ b/app/boards/shields/zmk_uno/zmk_uno.overlay @@ -7,13 +7,15 @@ #include "zmk_uno.dtsi" #include +#include #include #include / { chosen { - zmk,matrix-transform = &matrix_transform; + zmk,physical-layout = &matrix_physical_layout; }; + sensors: sensors { compatible = "zmk,keymap-sensors"; sensors = <&encoder>; @@ -38,6 +40,8 @@ endpoint_sideband_behaviors { compatible = "zmk,kscan-sideband-behaviors"; + + auto-enable; kscan = <&kscan_sp3t_toggle>; first_toggle_sideband: first_toggle_sideband { @@ -56,4 +60,35 @@ }; }; + matrix_physical_layout: matrix_physical_layout { + compatible = "zmk,physical-layout"; + display-name = "Matrix Layout"; + + kscan = <&kscan_matrix>; + transform = <&matrix_transform>; + + keys + = <&key_physical_attrs 100 100 0 0 0 0 0> + , <&key_physical_attrs 100 100 100 0 0 0 0> + , <&key_physical_attrs 100 100 0 100 0 0 0> + , <&key_physical_attrs 100 100 100 100 0 0 0> + ; + }; + + direct_physical_layout: direct_physical_layout { + compatible = "zmk,physical-layout"; + + display-name = "Direct Wire Layout"; + + kscan = <&kscan_direct>; + transform = <&direct_matrix_transform>; + + keys + = <&key_physical_attrs 100 100 0 0 0 0 0> + , <&key_physical_attrs 100 100 100 0 0 0 0> + , <&key_physical_attrs 100 100 0 100 0 0 0> + , <&key_physical_attrs 100 100 100 100 0 0 0> + ; + }; + }; diff --git a/app/boards/shields/zmk_uno/zmk_uno_split.dtsi b/app/boards/shields/zmk_uno/zmk_uno_split.dtsi index dac6fc3e..9afbf79a 100644 --- a/app/boards/shields/zmk_uno/zmk_uno_split.dtsi +++ b/app/boards/shields/zmk_uno/zmk_uno_split.dtsi @@ -6,13 +6,15 @@ #include "zmk_uno.dtsi" + #include + left_encoder: &encoder { status = "disabled"; }; / { chosen { - zmk,matrix-transform = &split_matrix_transform; + zmk,physical-layout = &matrix_physical_layout; }; split_matrix_transform: split_matrix_transform { @@ -31,18 +33,57 @@ split_direct_matrix_transform: split_direct_matrix_transform { compatible = "zmk,matrix-transform"; - rows = <3>; + rows = <2>; columns = <4>; map = < RC(0,0) RC(0,1) RC(0,2) RC(0,3) - RC(2,0) RC(2,1) - RC(2,2) RC(2,3) + RC(1,0) RC(1,1) + RC(1,2) RC(1,3) >; }; + matrix_physical_layout: matrix_physical_layout { + compatible = "zmk,physical-layout"; + display-name = "Matrix Layout"; + + kscan = <&kscan_matrix>; + transform = <&split_matrix_transform>; + + keys + = <&key_physical_attrs 100 100 0 0 0 0 0> + , <&key_physical_attrs 100 100 100 0 0 0 0> + , <&key_physical_attrs 100 100 0 100 0 0 0> + , <&key_physical_attrs 100 100 100 100 0 0 0> + , <&key_physical_attrs 100 100 0 200 0 0 0> + , <&key_physical_attrs 100 100 100 200 0 0 0> + , <&key_physical_attrs 100 100 0 300 0 0 0> + , <&key_physical_attrs 100 100 100 300 0 0 0> + ; + }; + + direct_physical_layout: direct_physical_layout { + compatible = "zmk,physical-layout"; + + display-name = "Direct Wire Layout"; + + kscan = <&kscan_direct>; + transform = <&split_direct_matrix_transform>; + + keys + = <&key_physical_attrs 100 100 0 0 0 0 0> + , <&key_physical_attrs 100 100 100 0 0 0 0> + , <&key_physical_attrs 100 100 0 100 0 0 0> + , <&key_physical_attrs 100 100 100 100 0 0 0> + , <&key_physical_attrs 100 100 0 200 0 0 0> + , <&key_physical_attrs 100 100 100 200 0 0 0> + , <&key_physical_attrs 100 100 0 300 0 0 0> + , <&key_physical_attrs 100 100 100 300 0 0 0> + ; + }; + right_encoder: right_encoder { steps = <80>; status = "disabled"; diff --git a/app/boards/shields/zmk_uno/zmk_uno_split.keymap b/app/boards/shields/zmk_uno/zmk_uno_split.keymap index 0e50a283..d2daa6ea 100644 --- a/app/boards/shields/zmk_uno/zmk_uno_split.keymap +++ b/app/boards/shields/zmk_uno/zmk_uno_split.keymap @@ -14,14 +14,9 @@ // Uncomment the following lines if using the "Direct Wire" jumper to switch the matrix to a direct wire. - -// &kscan_direct { status = "okay"; }; -// &kscan_matrix { status = "disabled"; }; - // / { // chosen { -// zmk,matrix-transform = &split_direct_matrix_transform; -// zmk,kscan = &kscan_direct; +// zmk,physical-layout = &direct_physical_layout; // }; // }; diff --git a/app/boards/shields/zmk_uno/zmk_uno_split_right.overlay b/app/boards/shields/zmk_uno/zmk_uno_split_right.overlay index 9c2e7d7f..acfad5a2 100644 --- a/app/boards/shields/zmk_uno/zmk_uno_split_right.overlay +++ b/app/boards/shields/zmk_uno/zmk_uno_split_right.overlay @@ -11,7 +11,7 @@ }; &split_direct_matrix_transform { - row-offset = <2>; + row-offset = <1>; }; &right_encoder { diff --git a/app/boards/sparkfun_pro_micro_rp2040.overlay b/app/boards/sparkfun_pro_micro_rp2040.overlay index b14e0d04..72b3adca 100644 --- a/app/boards/sparkfun_pro_micro_rp2040.overlay +++ b/app/boards/sparkfun_pro_micro_rp2040.overlay @@ -4,6 +4,4 @@ * SPDX-License-Identifier: MIT */ -#include "usb_console.dtsi" - &pro_micro_serial { status = "disabled"; }; diff --git a/app/boards/usb_console.dtsi b/app/boards/usb_console.dtsi deleted file mode 100644 index adf3bd19..00000000 --- a/app/boards/usb_console.dtsi +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2023 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - - -/ { - chosen { - zephyr,console = &cdc_acm_uart; - }; -}; - -&usbd { - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; -}; - diff --git a/app/core-coverage.yml b/app/core-coverage.yml index 4a60aad9..1350044c 100644 --- a/app/core-coverage.yml +++ b/app/core-coverage.yml @@ -22,6 +22,13 @@ include: shield: kyria_left cmake-args: "-DCONFIG_ZMK_DISPLAY=y" nickname: "display" + - board: nice_nano_v2 + shield: kyria_left + cmake-args: "-DCONFIG_ZMK_MOUSE=y" + nickname: "mouse" + - board: sparkfun_pro_micro_rp2040 + shield: reviung41 + cmake-args: "-DSNIPPET='zmk-usb-logging'" - board: nice_nano_v2 shield: kyria_right cmake-args: "-DCONFIG_ZMK_DISPLAY=y" diff --git a/app/dts/behaviors/backlight.dtsi b/app/dts/behaviors/backlight.dtsi index 54c83ff4..dd045eff 100644 --- a/app/dts/behaviors/backlight.dtsi +++ b/app/dts/behaviors/backlight.dtsi @@ -10,6 +10,7 @@ /omit-if-no-ref/ bl: bcklight { compatible = "zmk,behavior-backlight"; #binding-cells = <2>; + display-name = "Backlight"; }; }; }; diff --git a/app/dts/behaviors/bluetooth.dtsi b/app/dts/behaviors/bluetooth.dtsi index 40557b7a..bece156f 100644 --- a/app/dts/behaviors/bluetooth.dtsi +++ b/app/dts/behaviors/bluetooth.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ bt: bluetooth { compatible = "zmk,behavior-bluetooth"; #binding-cells = <2>; + display-name = "Bluetooth"; }; }; }; diff --git a/app/dts/behaviors/caps_word.dtsi b/app/dts/behaviors/caps_word.dtsi index 795fbc08..05431bd8 100644 --- a/app/dts/behaviors/caps_word.dtsi +++ b/app/dts/behaviors/caps_word.dtsi @@ -12,6 +12,7 @@ compatible = "zmk,behavior-caps-word"; #binding-cells = <0>; continue-list = ; + display-name = "Caps Word"; }; }; }; diff --git a/app/dts/behaviors/ext_power.dtsi b/app/dts/behaviors/ext_power.dtsi index 2ae1daf8..08113f94 100644 --- a/app/dts/behaviors/ext_power.dtsi +++ b/app/dts/behaviors/ext_power.dtsi @@ -10,6 +10,7 @@ ext_power: extpower { compatible = "zmk,behavior-ext-power"; #binding-cells = <1>; + display-name = "External Power"; }; }; }; diff --git a/app/dts/behaviors/gresc.dtsi b/app/dts/behaviors/gresc.dtsi index 59a73291..2643a383 100644 --- a/app/dts/behaviors/gresc.dtsi +++ b/app/dts/behaviors/gresc.dtsi @@ -13,6 +13,7 @@ #binding-cells = <0>; bindings = <&kp ESC>, <&kp GRAVE>; mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>; + display-name = "Grave/Escape"; }; }; }; diff --git a/app/dts/behaviors/key_press.dtsi b/app/dts/behaviors/key_press.dtsi index ddaf7eed..2435699b 100644 --- a/app/dts/behaviors/key_press.dtsi +++ b/app/dts/behaviors/key_press.dtsi @@ -10,6 +10,7 @@ /omit-if-no-ref/ cp: kp: key_press { compatible = "zmk,behavior-key-press"; #binding-cells = <1>; + display-name = "Key Press"; }; }; }; diff --git a/app/dts/behaviors/key_repeat.dtsi b/app/dts/behaviors/key_repeat.dtsi index 88910f62..cd5d3771 100644 --- a/app/dts/behaviors/key_repeat.dtsi +++ b/app/dts/behaviors/key_repeat.dtsi @@ -12,6 +12,7 @@ compatible = "zmk,behavior-key-repeat"; #binding-cells = <0>; usage-pages = ; + display-name = "Key Repeat"; }; }; }; diff --git a/app/dts/behaviors/key_toggle.dtsi b/app/dts/behaviors/key_toggle.dtsi index a3e3f36f..a7b66aab 100644 --- a/app/dts/behaviors/key_toggle.dtsi +++ b/app/dts/behaviors/key_toggle.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ kt: key_toggle { compatible = "zmk,behavior-key-toggle"; #binding-cells = <1>; + display-name = "Key Toggle"; }; }; }; diff --git a/app/dts/behaviors/layer_tap.dtsi b/app/dts/behaviors/layer_tap.dtsi index dc953e93..2858bf17 100644 --- a/app/dts/behaviors/layer_tap.dtsi +++ b/app/dts/behaviors/layer_tap.dtsi @@ -12,6 +12,7 @@ flavor = "tap-preferred"; tapping-term-ms = <200>; bindings = <&mo>, <&kp>; + display-name = "Layer-Tap"; }; }; }; diff --git a/app/dts/behaviors/mod_tap.dtsi b/app/dts/behaviors/mod_tap.dtsi index 38bb34fe..0b46f77e 100644 --- a/app/dts/behaviors/mod_tap.dtsi +++ b/app/dts/behaviors/mod_tap.dtsi @@ -12,6 +12,7 @@ flavor = "hold-preferred"; tapping-term-ms = <200>; bindings = <&kp>, <&kp>; + display-name = "Mod-Tap"; }; }; }; diff --git a/app/dts/behaviors/momentary_layer.dtsi b/app/dts/behaviors/momentary_layer.dtsi index 6d85165d..cae08d5f 100644 --- a/app/dts/behaviors/momentary_layer.dtsi +++ b/app/dts/behaviors/momentary_layer.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ mo: momentary_layer { compatible = "zmk,behavior-momentary-layer"; #binding-cells = <1>; + display-name = "Momentary Layer"; }; }; }; diff --git a/app/dts/behaviors/none.dtsi b/app/dts/behaviors/none.dtsi index 13d056f0..a9a820c3 100644 --- a/app/dts/behaviors/none.dtsi +++ b/app/dts/behaviors/none.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ none: none { compatible = "zmk,behavior-none"; #binding-cells = <0>; + display-name = "None"; }; }; }; diff --git a/app/dts/behaviors/outputs.dtsi b/app/dts/behaviors/outputs.dtsi index f7737196..3047852a 100644 --- a/app/dts/behaviors/outputs.dtsi +++ b/app/dts/behaviors/outputs.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ out: outputs { compatible = "zmk,behavior-outputs"; #binding-cells = <1>; + display-name = "Output Selection"; }; }; }; diff --git a/app/dts/behaviors/reset.dtsi b/app/dts/behaviors/reset.dtsi index e407b107..2aa41d7d 100644 --- a/app/dts/behaviors/reset.dtsi +++ b/app/dts/behaviors/reset.dtsi @@ -12,6 +12,7 @@ sys_reset: sysreset { compatible = "zmk,behavior-reset"; #binding-cells = <0>; + display-name = "Reset"; }; // Behavior can be invoked on peripherals, so name must be <= 8 characters. @@ -19,6 +20,7 @@ compatible = "zmk,behavior-reset"; type = ; #binding-cells = <0>; + display-name = "Bootloader"; }; }; }; diff --git a/app/dts/behaviors/rgb_underglow.dtsi b/app/dts/behaviors/rgb_underglow.dtsi index 969518a6..07640058 100644 --- a/app/dts/behaviors/rgb_underglow.dtsi +++ b/app/dts/behaviors/rgb_underglow.dtsi @@ -10,6 +10,7 @@ rgb_ug: rgb_ug { compatible = "zmk,behavior-rgb-underglow"; #binding-cells = <2>; + display-name = "Underglow"; }; }; }; diff --git a/app/dts/behaviors/sticky_key.dtsi b/app/dts/behaviors/sticky_key.dtsi index c8973d4d..382a7254 100644 --- a/app/dts/behaviors/sticky_key.dtsi +++ b/app/dts/behaviors/sticky_key.dtsi @@ -12,6 +12,7 @@ release-after-ms = <1000>; bindings = <&kp>; ignore-modifiers; + display-name = "Sticky Key"; }; /omit-if-no-ref/ sl: sticky_layer { compatible = "zmk,behavior-sticky-key"; @@ -19,6 +20,7 @@ release-after-ms = <1000>; bindings = <&mo>; quick-release; + display-name = "Sticky Layer"; }; }; diff --git a/app/dts/behaviors/to_layer.dtsi b/app/dts/behaviors/to_layer.dtsi index 904f023d..3c740209 100644 --- a/app/dts/behaviors/to_layer.dtsi +++ b/app/dts/behaviors/to_layer.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ to: to_layer { compatible = "zmk,behavior-to-layer"; #binding-cells = <1>; + display-name = "To Layer"; }; }; }; diff --git a/app/dts/behaviors/toggle_layer.dtsi b/app/dts/behaviors/toggle_layer.dtsi index 05f2988e..ea9b25b7 100644 --- a/app/dts/behaviors/toggle_layer.dtsi +++ b/app/dts/behaviors/toggle_layer.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ tog: toggle_layer { compatible = "zmk,behavior-toggle-layer"; #binding-cells = <1>; + display-name = "Toggle Layer"; }; }; }; diff --git a/app/dts/behaviors/transparent.dtsi b/app/dts/behaviors/transparent.dtsi index 3586f02a..03ec36a6 100644 --- a/app/dts/behaviors/transparent.dtsi +++ b/app/dts/behaviors/transparent.dtsi @@ -9,6 +9,7 @@ /omit-if-no-ref/ trans: transparent { compatible = "zmk,behavior-transparent"; #binding-cells = <0>; + display-name = "Transparent"; }; }; }; diff --git a/app/dts/bindings/behaviors/behavior-metadata.yaml b/app/dts/bindings/behaviors/behavior-metadata.yaml new file mode 100644 index 00000000..3a758ba3 --- /dev/null +++ b/app/dts/bindings/behaviors/behavior-metadata.yaml @@ -0,0 +1,6 @@ +# Copyright (c) 2024 The ZMK Contributors +# SPDX-License-Identifier: MIT + +properties: + display-name: + type: string diff --git a/app/dts/bindings/behaviors/one_param.yaml b/app/dts/bindings/behaviors/one_param.yaml index 9a503e8a..fa4c2dc0 100644 --- a/app/dts/bindings/behaviors/one_param.yaml +++ b/app/dts/bindings/behaviors/one_param.yaml @@ -1,6 +1,8 @@ # Copyright (c) 2020 The ZMK Contributors # SPDX-License-Identifier: MIT +include: behavior-metadata.yaml + properties: label: type: string diff --git a/app/dts/bindings/behaviors/two_param.yaml b/app/dts/bindings/behaviors/two_param.yaml index 4f342301..af9618e1 100644 --- a/app/dts/bindings/behaviors/two_param.yaml +++ b/app/dts/bindings/behaviors/two_param.yaml @@ -1,6 +1,8 @@ # Copyright (c) 2020 The ZMK Contributors # SPDX-License-Identifier: MIT +include: behavior-metadata.yaml + properties: label: type: string diff --git a/app/dts/bindings/behaviors/zero_param.yaml b/app/dts/bindings/behaviors/zero_param.yaml index 79d0dcae..deed5a12 100644 --- a/app/dts/bindings/behaviors/zero_param.yaml +++ b/app/dts/bindings/behaviors/zero_param.yaml @@ -1,6 +1,8 @@ # Copyright (c) 2020 The ZMK Contributors # SPDX-License-Identifier: MIT +include: behavior-metadata.yaml + properties: label: type: string diff --git a/app/dts/bindings/kscan/zmk,kscan-sideband-behaviors.yaml b/app/dts/bindings/kscan/zmk,kscan-sideband-behaviors.yaml index f3ed180d..e38beeb4 100644 --- a/app/dts/bindings/kscan/zmk,kscan-sideband-behaviors.yaml +++ b/app/dts/bindings/kscan/zmk,kscan-sideband-behaviors.yaml @@ -11,6 +11,9 @@ compatible: "zmk,kscan-sideband-behaviors" include: kscan.yaml properties: + auto-enable: + type: boolean + kscan: type: phandle required: true diff --git a/app/dts/bindings/zmk,key-physical-attrs.yaml b/app/dts/bindings/zmk,key-physical-attrs.yaml new file mode 100644 index 00000000..9ea070f8 --- /dev/null +++ b/app/dts/bindings/zmk,key-physical-attrs.yaml @@ -0,0 +1,24 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# +# SPDX-License-Identifier: MIT + +description: | + The physical attributes of a key, including size, location, and rotation + +compatible: "zmk,key-physical-attrs" + +properties: + "#key-cells": + type: int + required: true + const: 7 + +key-cells: + - width + - height + - x + - y + - r + - rx + - ry diff --git a/app/dts/bindings/zmk,physical-layout-position-map.yaml b/app/dts/bindings/zmk,physical-layout-position-map.yaml new file mode 100644 index 00000000..8647404b --- /dev/null +++ b/app/dts/bindings/zmk,physical-layout-position-map.yaml @@ -0,0 +1,23 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# +# SPDX-License-Identifier: MIT + +description: | + Describes how to correlate equivalent keys between layouts that don't have the exact same X,Y location. + +compatible: "zmk,physical-layout-position-map" + +properties: + complete: + type: boolean + description: If the mapping complete describes the key mapping, and no position based mapping should be used. + +child-binding: + properties: + physical-layout: + type: phandle + description: The physical layout that corresponds to this mapping entry. + positions: + type: array + description: Array of key positions that match the same array entry in the other sibling nodes. diff --git a/app/dts/bindings/zmk,physical-layout.yaml b/app/dts/bindings/zmk,physical-layout.yaml new file mode 100644 index 00000000..3f9b8c24 --- /dev/null +++ b/app/dts/bindings/zmk,physical-layout.yaml @@ -0,0 +1,26 @@ +# +# Copyright (c) 2024 The ZMK Contributors +# +# SPDX-License-Identifier: MIT + +description: | + Describe the physical layout of a keyboard, including deps like the transform and kscan + that are needed for that layout to work. + +compatible: "zmk,physical-layout" + +properties: + display-name: + type: string + required: true + description: The name of this layout to display in the UI + transform: + type: phandle + required: true + description: The matrix transform to use along with this layout. + kscan: + type: phandle + description: The kscan to use along with this layout. The `zmk,kscan` chosen will be used as a fallback if this property is omitted. + keys: + type: phandle-array + description: Array of key physical attributes. diff --git a/app/dts/physical_layouts.dtsi b/app/dts/physical_layouts.dtsi new file mode 100644 index 00000000..1c8703ec --- /dev/null +++ b/app/dts/physical_layouts.dtsi @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/ { + key_physical_attrs: key_physical_attrs { + compatible = "zmk,key-physical-attrs"; + + #key-cells = <7>; + }; +}; \ No newline at end of file diff --git a/app/include/drivers/behavior.h b/app/include/drivers/behavior.h index 3936da5e..7c99f04e 100644 --- a/app/include/drivers/behavior.h +++ b/app/include/drivers/behavior.h @@ -23,6 +23,39 @@ * (Internal use only.) */ +struct behavior_parameter_value_metadata { + char *display_name; + + union { + uint32_t value; + struct { + int32_t min; + int32_t max; + } range; + }; + + enum { + BEHAVIOR_PARAMETER_VALUE_TYPE_NIL = 0, + BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE = 1, + BEHAVIOR_PARAMETER_VALUE_TYPE_RANGE = 2, + BEHAVIOR_PARAMETER_VALUE_TYPE_HID_USAGE = 3, + BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_ID = 4, + } type; +}; + +struct behavior_parameter_metadata_set { + size_t param1_values_len; + const struct behavior_parameter_value_metadata *param1_values; + + size_t param2_values_len; + const struct behavior_parameter_value_metadata *param2_values; +}; + +struct behavior_parameter_metadata { + size_t sets_len; + const struct behavior_parameter_metadata_set *sets; +}; + enum behavior_sensor_binding_process_mode { BEHAVIOR_SENSOR_BINDING_PROCESS_MODE_TRIGGER, BEHAVIOR_SENSOR_BINDING_PROCESS_MODE_DISCARD, @@ -37,6 +70,10 @@ typedef int (*behavior_sensor_keymap_binding_accept_data_callback_t)( struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event, const struct zmk_sensor_config *sensor_config, size_t channel_data_size, const struct zmk_sensor_channel_data channel_data[channel_data_size]); +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +typedef int (*behavior_get_parameter_metadata_t)( + const struct device *behavior, struct behavior_parameter_metadata *param_metadata); +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) enum behavior_locality { BEHAVIOR_LOCALITY_CENTRAL, @@ -51,23 +88,71 @@ __subsystem struct behavior_driver_api { behavior_keymap_binding_callback_t binding_released; behavior_sensor_keymap_binding_accept_data_callback_t sensor_binding_accept_data; behavior_sensor_keymap_binding_process_callback_t sensor_binding_process; +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + behavior_get_parameter_metadata_t get_parameter_metadata; + const struct behavior_parameter_metadata *parameter_metadata; +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; /** * @endcond */ +struct zmk_behavior_metadata { +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + const char *display_name; +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +}; + struct zmk_behavior_ref { const struct device *device; + const struct zmk_behavior_metadata metadata; }; +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS) + +struct zmk_behavior_local_id_map { + const struct device *device; + zmk_behavior_local_id_t local_id; +}; + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS) + +#define ZMK_BEHAVIOR_REF_DT_NAME(node_id) _CONCAT(zmk_behavior_, DEVICE_DT_NAME_GET(node_id)) + +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +#define ZMK_BEHAVIOR_METADATA_INITIALIZER(node_id) \ + { .display_name = DT_PROP_OR(node_id, display_name, DEVICE_DT_NAME(node_id)), } + +#else + +#define ZMK_BEHAVIOR_METADATA_INITIALIZER(node_id) \ + {} + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +#define ZMK_BEHAVIOR_REF_INITIALIZER(node_id, _dev) \ + { .device = _dev, .metadata = ZMK_BEHAVIOR_METADATA_INITIALIZER(node_id), } + +#define ZMK_BEHAVIOR_LOCAL_ID_MAP_INITIALIZER(node_id, _dev) \ + { .device = _dev, } + +#define ZMK_BEHAVIOR_REF_DEFINE(name, node_id, _dev) \ + static const STRUCT_SECTION_ITERABLE(zmk_behavior_ref, name) = \ + ZMK_BEHAVIOR_REF_INITIALIZER(node_id, _dev); \ + COND_CODE_1(IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS), \ + (static const STRUCT_SECTION_ITERABLE(zmk_behavior_local_id_map, \ + _CONCAT(_zmk_behavior_local_id_map, name)) = \ + ZMK_BEHAVIOR_LOCAL_ID_MAP_INITIALIZER(node_id, _dev)), \ + ()); + +#define ZMK_BEHAVIOR_REF_DT_DEFINE(node_id) \ + ZMK_BEHAVIOR_REF_DEFINE(ZMK_BEHAVIOR_REF_DT_NAME(node_id), node_id, DEVICE_DT_GET(node_id)) + /** * Registers @p node_id as a behavior. */ -#define BEHAVIOR_DEFINE(node_id) \ - static const STRUCT_SECTION_ITERABLE(zmk_behavior_ref, \ - _CONCAT(zmk_behavior_, DEVICE_DT_NAME_GET(node_id))) = { \ - .device = DEVICE_DT_GET(node_id), \ - } +#define BEHAVIOR_DEFINE(node_id) ZMK_BEHAVIOR_REF_DT_DEFINE(node_id) /** * @brief Like DEVICE_DT_DEFINE(), but also registers the device as a behavior. @@ -89,6 +174,52 @@ struct zmk_behavior_ref { DEVICE_DT_INST_DEFINE(inst, __VA_ARGS__); \ BEHAVIOR_DEFINE(DT_DRV_INST(inst)) +/** + * @brief Validate a given behavior binding is valid, including parameter validation + * if the metadata feature is enablued. + * + * @param binding The behavior binding to validate. + * + * @retval 0 if the passed in binding is valid. + * @retval -ENODEV if the binding references a non-existant behavior. + * @retval -EINVAL if parameters are not valid for the behavior metadata. + */ +int zmk_behavior_validate_binding(const struct zmk_behavior_binding *binding); + +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +int zmk_behavior_get_empty_param_metadata(const struct device *dev, + struct behavior_parameter_metadata *metadata); + +/** + * @brief Validate a given behavior parameters match the behavior metadata. + * + * @param metadata The behavior metadata to validate against + * @param param1 The first parameter value + * @param param2 The second parameter value + * + * @retval 0 if the passed in parameters are valid. + * @retval -ENODEV if metadata is NULL. + * @retval -EINVAL if parameters are not valid for the metadata. + */ +int zmk_behavior_check_params_match_metadata(const struct behavior_parameter_metadata *metadata, + uint32_t param1, uint32_t param2); +/** + * @brief Validate a given behavior parameter matches the behavior metadata parameter values. + * + * @param values The values to validate against + * @param values_len How many values to check + * @param param The value to check. + * + * @retval 0 if the passed in parameter is valid. + * @retval -ENODEV if values is NULL. + * @retval -EINVAL if parameter is not valid for the value metadata. + */ +int zmk_behavior_validate_param_values(const struct behavior_parameter_value_metadata *values, + size_t values_len, uint32_t param); + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + /** * Syscall wrapper for zmk_behavior_get_binding(). * @@ -120,6 +251,40 @@ static inline int z_impl_behavior_keymap_binding_convert_central_state_dependent return api->binding_convert_central_state_dependent_params(binding, event); } +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +/** + * @brief Determine where the behavior should be run + * @param behavior Pointer to the device structure for the driver instance. + * + * @retval Zero if successful. + * @retval Negative errno code if failure. + */ +__syscall int behavior_get_parameter_metadata(const struct device *behavior, + struct behavior_parameter_metadata *param_metadata); + +static inline int +z_impl_behavior_get_parameter_metadata(const struct device *behavior, + struct behavior_parameter_metadata *param_metadata) { + if (behavior == NULL || param_metadata == NULL) { + return -EINVAL; + } + + const struct behavior_driver_api *api = (const struct behavior_driver_api *)behavior->api; + + if (api->get_parameter_metadata) { + return api->get_parameter_metadata(behavior, param_metadata); + } else if (api->parameter_metadata) { + *param_metadata = *api->parameter_metadata; + } else { + return -ENODEV; + } + + return 0; +} + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + /** * @brief Determine where the behavior should be run * @param behavior Pointer to the device structure for the driver instance. diff --git a/app/include/linker/zmk-behavior-local-id-map.ld b/app/include/linker/zmk-behavior-local-id-map.ld new file mode 100644 index 00000000..c91e64c4 --- /dev/null +++ b/app/include/linker/zmk-behavior-local-id-map.ld @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +ITERABLE_SECTION_RAM(zmk_behavior_local_id_map, 4) diff --git a/app/include/zmk/behavior.h b/app/include/zmk/behavior.h index ab95fd8e..d45bbfff 100644 --- a/app/include/zmk/behavior.h +++ b/app/include/zmk/behavior.h @@ -11,8 +11,13 @@ #define ZMK_BEHAVIOR_OPAQUE 0 #define ZMK_BEHAVIOR_TRANSPARENT 1 +typedef uint16_t zmk_behavior_local_id_t; + struct zmk_behavior_binding { - char *behavior_dev; +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS_IN_BINDINGS) + zmk_behavior_local_id_t local_id; +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS_IN_BINDINGS) + const char *behavior_dev; uint32_t param1; uint32_t param2; }; @@ -36,3 +41,23 @@ struct zmk_behavior_binding_event { * unrelated node which shares the same name as a behavior. */ const struct device *zmk_behavior_get_binding(const char *name); + +/** + * @brief Get a local ID for a behavior from its @p name field. + * + * @param name Behavior name to search for. + * + * @retval The local ID value that can be used to reference the behavior later, across reboots. + * @retval UINT16_MAX if the behavior is not found or its initialization function failed. + */ +zmk_behavior_local_id_t zmk_behavior_get_local_id(const char *name); + +/** + * @brief Get a behavior name for a behavior from its @p local_id . + * + * @param local_id Behavior local ID used to search for the behavior + * + * @retval The name of the behavior that is associated with that local ID. + * @retval NULL if the behavior is not found or its initialization function failed. + */ +const char *zmk_behavior_find_behavior_name_from_local_id(zmk_behavior_local_id_t local_id); diff --git a/app/include/zmk/ble.h b/app/include/zmk/ble.h index 773323c1..cc55a6ce 100644 --- a/app/include/zmk/ble.h +++ b/app/include/zmk/ble.h @@ -29,7 +29,10 @@ int zmk_ble_prof_disconnect(uint8_t index); int zmk_ble_active_profile_index(void); int zmk_ble_profile_index(const bt_addr_le_t *addr); + bt_addr_le_t *zmk_ble_active_profile_addr(void); +struct bt_conn *zmk_ble_active_profile_conn(void); + bool zmk_ble_active_profile_is_open(void); bool zmk_ble_active_profile_is_connected(void); char *zmk_ble_active_profile_name(void); diff --git a/app/include/zmk/event_manager.h b/app/include/zmk/event_manager.h index e4420715..0eb63ad7 100644 --- a/app/include/zmk/event_manager.h +++ b/app/include/zmk/event_manager.h @@ -64,6 +64,7 @@ struct zmk_event_subscription { #define ZMK_LISTENER(mod, cb) const struct zmk_listener zmk_listener_##mod = {.callback = cb}; #define ZMK_SUBSCRIPTION(mod, ev_type) \ + extern const struct zmk_listener zmk_listener_##mod; \ const Z_DECL_ALIGN(struct zmk_event_subscription) \ _CONCAT(_CONCAT(zmk_event_sub_, mod), ev_type) __used \ __attribute__((__section__(".event_subscription"))) = { \ diff --git a/app/include/zmk/kscan.h b/app/include/zmk/kscan.h deleted file mode 100644 index eebe41e7..00000000 --- a/app/include/zmk/kscan.h +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) 2020 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#pragma once - -#include - -int zmk_kscan_init(const struct device *dev); diff --git a/app/include/zmk/matrix.h b/app/include/zmk/matrix.h index 5f8cd7d7..e38f5a49 100644 --- a/app/include/zmk/matrix.h +++ b/app/include/zmk/matrix.h @@ -9,15 +9,25 @@ #include #define ZMK_MATRIX_NODE_ID DT_CHOSEN(zmk_kscan) +#define ZMK_MATRIX_HAS_TRANSFORM DT_HAS_CHOSEN(zmk_matrix_transform) -#if DT_HAS_CHOSEN(zmk_matrix_transform) +#if DT_HAS_COMPAT_STATUS_OKAY(zmk_physical_layout) + +#if ZMK_MATRIX_HAS_TRANSFORM +#error "To use physical layouts, remove the chosen `zmk,matrix-transform` value." +#endif + +#define ZMK_PHYSICAL_LAYOUT_BYTE_ARRAY(node_id) \ + uint8_t _CONCAT(prop_, node_id)[DT_PROP_LEN(DT_PHANDLE(node_id, transform), map)]; + +#define ZMK_KEYMAP_LEN \ + sizeof(union {DT_FOREACH_STATUS_OKAY(zmk_physical_layout, ZMK_PHYSICAL_LAYOUT_BYTE_ARRAY)}) + +#elif ZMK_MATRIX_HAS_TRANSFORM #define ZMK_KEYMAP_TRANSFORM_NODE DT_CHOSEN(zmk_matrix_transform) #define ZMK_KEYMAP_LEN DT_PROP_LEN(ZMK_KEYMAP_TRANSFORM_NODE, map) -#define ZMK_MATRIX_ROWS DT_PROP(ZMK_KEYMAP_TRANSFORM_NODE, rows) -#define ZMK_MATRIX_COLS DT_PROP(ZMK_KEYMAP_TRANSFORM_NODE, columns) - #else /* DT_HAS_CHOSEN(zmk_matrix_transform) */ #if DT_NODE_HAS_PROP(ZMK_MATRIX_NODE_ID, row_gpios) diff --git a/app/include/zmk/matrix_transform.h b/app/include/zmk/matrix_transform.h index ffd3e3f1..42a98151 100644 --- a/app/include/zmk/matrix_transform.h +++ b/app/include/zmk/matrix_transform.h @@ -6,4 +6,16 @@ #pragma once -int32_t zmk_matrix_transform_row_column_to_position(uint32_t row, uint32_t column); \ No newline at end of file +#include + +typedef const struct zmk_matrix_transform *zmk_matrix_transform_t; + +#define ZMK_MATRIX_TRANSFORM_DEFAULT_EXTERN() \ + extern const struct zmk_matrix_transform zmk_matrix_transform_default +#define ZMK_MATRIX_TRANSFORM_EXTERN(node_id) \ + extern const struct zmk_matrix_transform _CONCAT(zmk_matrix_transform_, node_id) + +#define ZMK_MATRIX_TRANSFORM_T_FOR_NODE(node_id) &_CONCAT(zmk_matrix_transform_, node_id) + +int32_t zmk_matrix_transform_row_column_to_position(zmk_matrix_transform_t mt, uint32_t row, + uint32_t column); diff --git a/app/include/zmk/physical_layouts.h b/app/include/zmk/physical_layouts.h new file mode 100644 index 00000000..8d8188e3 --- /dev/null +++ b/app/include/zmk/physical_layouts.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +struct zmk_key_physical_attrs { + int16_t width; + int16_t height; + int16_t x; + int16_t y; + int16_t rx; + int16_t ry; + int16_t r; +}; + +struct zmk_physical_layout { + const char *display_name; + + zmk_matrix_transform_t matrix_transform; + const struct device *kscan; + + const struct zmk_key_physical_attrs *keys; + size_t keys_len; +}; + +#define ZMK_PHYS_LAYOUTS_FOREACH(_ref) STRUCT_SECTION_FOREACH(zmk_physical_layout, _ref) + +size_t zmk_physical_layouts_get_list(struct zmk_physical_layout const *const **phys_layouts); + +int zmk_physical_layouts_select(uint8_t index); +int zmk_physical_layouts_get_selected(void); + +int zmk_physical_layouts_check_unsaved_selection(void); +int zmk_physical_layouts_save_selected(void); +int zmk_physical_layouts_revert_selected(void); + +int zmk_physical_layouts_get_position_map(uint8_t source, uint8_t dest, uint32_t *map); diff --git a/app/module/drivers/kscan/kscan_gpio_charlieplex.c b/app/module/drivers/kscan/kscan_gpio_charlieplex.c index 3ecbcd6a..f48a6a2f 100644 --- a/app/module/drivers/kscan/kscan_gpio_charlieplex.c +++ b/app/module/drivers/kscan/kscan_gpio_charlieplex.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -167,6 +168,21 @@ static int kscan_charlieplex_set_all_outputs(const struct device *dev, const int return 0; } +static int kscan_charlieplex_disconnect_all(const struct device *dev) { + const struct kscan_charlieplex_config *config = dev->config; + + for (int i = 0; i < config->cells.len; i++) { + const struct gpio_dt_spec *gpio = &config->cells.gpios[i]; + int err = gpio_pin_configure_dt(gpio, GPIO_DISCONNECTED); + if (err) { + LOG_ERR("Unable to configure pin %u on %s for input", gpio->pin, gpio->port->name); + return err; + } + } + + return 0; +} + static int kscan_charlieplex_interrupt_configure(const struct device *dev, const gpio_flags_t flags) { const struct kscan_charlieplex_config *config = dev->config; @@ -359,11 +375,7 @@ static int kscan_charlieplex_init_interrupt(const struct device *dev) { return err; } -static int kscan_charlieplex_init(const struct device *dev) { - struct kscan_charlieplex_data *data = dev->data; - - data->dev = dev; - +static void kscan_charlieplex_setup_pins(const struct device *dev) { kscan_charlieplex_init_inputs(dev); kscan_charlieplex_set_all_outputs(dev, 0); @@ -371,7 +383,46 @@ static int kscan_charlieplex_init(const struct device *dev) { if (config->use_interrupt) { kscan_charlieplex_init_interrupt(dev); } +} + +#if IS_ENABLED(CONFIG_PM_DEVICE) + +static int kscan_charlieplex_pm_action(const struct device *dev, enum pm_device_action action) { + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + kscan_charlieplex_interrupt_configure(dev, GPIO_INT_DISABLE); + kscan_charlieplex_disconnect_all(dev); + + return kscan_charlieplex_disable(dev); + case PM_DEVICE_ACTION_RESUME: + kscan_charlieplex_setup_pins(dev); + + return kscan_charlieplex_enable(dev); + default: + return -ENOTSUP; + } +} + +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + +static int kscan_charlieplex_init(const struct device *dev) { + struct kscan_charlieplex_data *data = dev->data; + + data->dev = dev; + k_work_init_delayable(&data->work, kscan_charlieplex_work_handler); + +#if IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_init_suspended(dev); + +#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) + pm_device_runtime_enable(dev); +#endif + +#else + kscan_charlieplex_setup_pins(dev); +#endif + return 0; } @@ -406,8 +457,10 @@ static const struct kscan_driver_api kscan_charlieplex_api = { COND_THIS_INTERRUPT(n, (.use_interrupt = INST_INTR_DEFINED(n), )) \ COND_THIS_INTERRUPT(n, (.interrupt = KSCAN_INTR_CFG_INIT(n), ))}; \ \ - DEVICE_DT_INST_DEFINE(n, &kscan_charlieplex_init, NULL, &kscan_charlieplex_data_##n, \ - &kscan_charlieplex_config_##n, POST_KERNEL, CONFIG_KSCAN_INIT_PRIORITY, \ - &kscan_charlieplex_api); + PM_DEVICE_DT_INST_DEFINE(n, kscan_charlieplex_pm_action); \ + \ + DEVICE_DT_INST_DEFINE(n, &kscan_charlieplex_init, PM_DEVICE_DT_INST_GET(n), \ + &kscan_charlieplex_data_##n, &kscan_charlieplex_config_##n, POST_KERNEL, \ + CONFIG_KSCAN_INIT_PRIORITY, &kscan_charlieplex_api); DT_INST_FOREACH_STATUS_OKAY(KSCAN_CHARLIEPLEX_INIT); diff --git a/app/module/drivers/kscan/kscan_gpio_direct.c b/app/module/drivers/kscan/kscan_gpio_direct.c index fa24e69e..245e78b5 100644 --- a/app/module/drivers/kscan/kscan_gpio_direct.c +++ b/app/module/drivers/kscan/kscan_gpio_direct.c @@ -294,6 +294,24 @@ static int kscan_direct_init_input_inst(const struct device *dev, const struct g return 0; } +#if IS_ENABLED(CONFIG_PM_DEVICE) + +static int kscan_direct_disconnect_inputs(const struct device *dev) { + const struct kscan_direct_data *data = dev->data; + + for (int i = 0; i < data->inputs.len; i++) { + const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec; + int err = gpio_pin_configure_dt(gpio, GPIO_DISCONNECTED); + if (err) { + return err; + } + } + + return 0; +} + +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + static int kscan_direct_init_inputs(const struct device *dev) { const struct kscan_direct_data *data = dev->data; const struct kscan_direct_config *config = dev->config; @@ -317,9 +335,20 @@ static int kscan_direct_init(const struct device *dev) { // Sort inputs by port so we can read each port just once per scan. kscan_gpio_list_sort_by_port(&data->inputs); + k_work_init_delayable(&data->work, kscan_direct_work_handler); + +#if IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_init_suspended(dev); + +#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) + pm_device_runtime_enable(dev); +#endif + +#else + kscan_direct_init_inputs(dev); - k_work_init_delayable(&data->work, kscan_direct_work_handler); +#endif return 0; } @@ -329,8 +358,10 @@ static int kscan_direct_init(const struct device *dev) { static int kscan_direct_pm_action(const struct device *dev, enum pm_device_action action) { switch (action) { case PM_DEVICE_ACTION_SUSPEND: + kscan_direct_disconnect_inputs(dev); return kscan_direct_disable(dev); case PM_DEVICE_ACTION_RESUME: + kscan_direct_init_inputs(dev); return kscan_direct_enable(dev); default: return -ENOTSUP; diff --git a/app/module/drivers/kscan/kscan_gpio_matrix.c b/app/module/drivers/kscan/kscan_gpio_matrix.c index 8a3c39f2..e0c76395 100644 --- a/app/module/drivers/kscan/kscan_gpio_matrix.c +++ b/app/module/drivers/kscan/kscan_gpio_matrix.c @@ -405,6 +405,44 @@ static int kscan_matrix_init_outputs(const struct device *dev) { return 0; } +#if IS_ENABLED(CONFIG_PM_DEVICE) + +static int kscan_matrix_disconnect_inputs(const struct device *dev) { + const struct kscan_matrix_data *data = dev->data; + + for (int i = 0; i < data->inputs.len; i++) { + const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec; + int err = gpio_pin_configure_dt(gpio, GPIO_DISCONNECTED); + if (err) { + return err; + } + } + + return 0; +} + +static int kscan_matrix_disconnect_outputs(const struct device *dev) { + const struct kscan_matrix_config *config = dev->config; + + for (int i = 0; i < config->outputs.len; i++) { + const struct gpio_dt_spec *gpio = &config->outputs.gpios[i].spec; + int err = gpio_pin_configure_dt(gpio, GPIO_DISCONNECTED); + if (err) { + return err; + } + } + + return 0; +} + +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + +static void kscan_matrix_setup_pins(const struct device *dev) { + kscan_matrix_init_inputs(dev); + kscan_matrix_init_outputs(dev); + kscan_matrix_set_all_outputs(dev, 0); +} + static int kscan_matrix_init(const struct device *dev) { struct kscan_matrix_data *data = dev->data; @@ -413,12 +451,19 @@ static int kscan_matrix_init(const struct device *dev) { // Sort inputs by port so we can read each port just once per scan. kscan_gpio_list_sort_by_port(&data->inputs); - kscan_matrix_init_inputs(dev); - kscan_matrix_init_outputs(dev); - kscan_matrix_set_all_outputs(dev, 0); - k_work_init_delayable(&data->work, kscan_matrix_work_handler); +#if IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_init_suspended(dev); + +#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) + pm_device_runtime_enable(dev); +#endif + +#else + kscan_matrix_setup_pins(dev); +#endif + return 0; } @@ -427,8 +472,12 @@ static int kscan_matrix_init(const struct device *dev) { static int kscan_matrix_pm_action(const struct device *dev, enum pm_device_action action) { switch (action) { case PM_DEVICE_ACTION_SUSPEND: + kscan_matrix_disconnect_inputs(dev); + kscan_matrix_disconnect_outputs(dev); + return kscan_matrix_disable(dev); case PM_DEVICE_ACTION_RESUME: + kscan_matrix_setup_pins(dev); return kscan_matrix_enable(dev); default: return -ENOTSUP; diff --git a/app/snippets/zmk-usb-logging/snippet.yml b/app/snippets/zmk-usb-logging/snippet.yml new file mode 100644 index 00000000..8f218085 --- /dev/null +++ b/app/snippets/zmk-usb-logging/snippet.yml @@ -0,0 +1,4 @@ +name: zmk-usb-logging +append: + EXTRA_CONF_FILE: zmk-usb-logging.conf + EXTRA_DTC_OVERLAY_FILE: zmk-usb-logging.overlay diff --git a/app/snippets/zmk-usb-logging/zmk-usb-logging.conf b/app/snippets/zmk-usb-logging/zmk-usb-logging.conf new file mode 100644 index 00000000..57893df5 --- /dev/null +++ b/app/snippets/zmk-usb-logging/zmk-usb-logging.conf @@ -0,0 +1,2 @@ +CONFIG_ZMK_USB_LOGGING=y + diff --git a/app/snippets/zmk-usb-logging/zmk-usb-logging.overlay b/app/snippets/zmk-usb-logging/zmk-usb-logging.overlay new file mode 100644 index 00000000..5ceda583 --- /dev/null +++ b/app/snippets/zmk-usb-logging/zmk-usb-logging.overlay @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/ { + chosen { + zephyr,console = &snippet_zmk_usb_logging_uart; + zephyr,shell-uart = &snippet_zmk_usb_logging_uart; + }; +}; + +&zephyr_udc0 { + snippet_zmk_usb_logging_uart: snippet_zmk_usb_logging_uart { + compatible = "zephyr,cdc-acm-uart"; + }; +}; diff --git a/app/src/backlight.c b/app/src/backlight.c index f050978f..22b73066 100644 --- a/app/src/backlight.c +++ b/app/src/backlight.c @@ -58,7 +58,7 @@ static int zmk_backlight_update(void) { #if IS_ENABLED(CONFIG_SETTINGS) static int backlight_settings_load_cb(const char *name, size_t len, settings_read_cb read_cb, - void *cb_arg, void *param) { + void *cb_arg) { const char *next; if (settings_name_steq(name, "state", &next) && !next) { if (len != sizeof(state)) { @@ -66,11 +66,18 @@ static int backlight_settings_load_cb(const char *name, size_t len, settings_rea } int rc = read_cb(cb_arg, &state, sizeof(state)); + if (rc >= 0) { + rc = zmk_backlight_update(); + } + return MIN(rc, 0); } return -ENOENT; } +SETTINGS_STATIC_HANDLER_DEFINE(backlight, "backlight", NULL, backlight_settings_load_cb, NULL, + NULL); + static void backlight_save_work_handler(struct k_work *work) { settings_save_one("backlight/state", &state, sizeof(state)); } @@ -85,11 +92,6 @@ static int zmk_backlight_init(void) { } #if IS_ENABLED(CONFIG_SETTINGS) - settings_subsys_init(); - int rc = settings_load_subtree_direct("backlight", backlight_settings_load_cb, NULL); - if (rc != 0) { - LOG_ERR("Failed to load backlight settings: %d", rc); - } k_work_init_delayable(&backlight_save_work, backlight_save_work_handler); #endif #if IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB) diff --git a/app/src/behavior.c b/app/src/behavior.c index fa2005ff..e69cdf88 100644 --- a/app/src/behavior.c +++ b/app/src/behavior.c @@ -6,11 +6,21 @@ #include #include +#include #include #include +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS) && \ + IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_ID_TYPE_SETTINGS_TABLE) + +#include + +#endif + #include #include +#include +#include #include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -39,6 +49,269 @@ const struct device *z_impl_behavior_get_binding(const char *name) { return NULL; } +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +int zmk_behavior_get_empty_param_metadata(const struct device *dev, + struct behavior_parameter_metadata *metadata) { + metadata->sets_len = 0; + return 0; +} + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +static int validate_hid_usage(uint16_t usage_page, uint16_t usage_id) { + LOG_DBG("Validate usage %d in page %d", usage_id, usage_page); + switch (usage_page) { + case HID_USAGE_KEY: + if (usage_id == 0 || (usage_id > ZMK_HID_KEYBOARD_NKRO_MAX_USAGE && + usage_id < LEFT_CONTROL && usage_id > RIGHT_GUI)) { + return -EINVAL; + } + break; + case HID_USAGE_CONSUMER: + if (usage_id > + COND_CODE_1(IS_ENABLED(CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_BASIC), (0xFF), (0xFFF))) { + return -EINVAL; + } + break; + default: + LOG_WRN("Unsupported HID usage page %d", usage_page); + return -EINVAL; + } + + return 0; +} + +#define PARAM_MATCHES 0 + +static int check_param_matches_value(const struct behavior_parameter_value_metadata *value_meta, + uint32_t param) { + switch (value_meta->type) { + case BEHAVIOR_PARAMETER_VALUE_TYPE_NIL: + if (param == 0) { + return PARAM_MATCHES; + } + break; + case BEHAVIOR_PARAMETER_VALUE_TYPE_HID_USAGE: + if (validate_hid_usage(ZMK_HID_USAGE_PAGE(param), ZMK_HID_USAGE_ID(param)) >= 0) { + return PARAM_MATCHES; + } + + break; + case BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_ID: + if (param >= 0 && param < ZMK_KEYMAP_LEN) { + return PARAM_MATCHES; + } + break; + /* TODO: Restore with HSV -> RGB refactor + case BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HSV: + // TODO: No real way to validate? Maybe max brightness? + break; + */ + case BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE: + if (param == value_meta->value) { + return PARAM_MATCHES; + } + break; + case BEHAVIOR_PARAMETER_VALUE_TYPE_RANGE: + if (param >= value_meta->range.min && param <= value_meta->range.max) { + return PARAM_MATCHES; + } + break; + default: + LOG_WRN("Unknown type %d", value_meta->type); + break; + } + + return -ENOTSUP; +} + +int zmk_behavior_validate_param_values(const struct behavior_parameter_value_metadata *values, + size_t values_len, uint32_t param) { + if (values_len == 0) { + return -ENODEV; + } + + for (int v = 0; v < values_len; v++) { + int ret = check_param_matches_value(&values[v], param); + if (ret >= 0) { + return ret; + } + } + + return -EINVAL; +} + +int zmk_behavior_check_params_match_metadata(const struct behavior_parameter_metadata *metadata, + uint32_t param1, uint32_t param2) { + if (!metadata || metadata->sets_len == 0) { + if (!metadata) { + LOG_ERR("No metadata to check against"); + } + + return (param1 == 0 && param2 == 0) ? 0 : -ENODEV; + } + + for (int s = 0; s < metadata->sets_len; s++) { + const struct behavior_parameter_metadata_set *set = &metadata->sets[s]; + int param1_ret = + zmk_behavior_validate_param_values(set->param1_values, set->param1_values_len, param1); + int param2_ret = + zmk_behavior_validate_param_values(set->param2_values, set->param2_values_len, param2); + + if ((param1_ret >= 0 || (param1_ret == -ENODEV && param1 == 0)) && + (param2_ret >= 0 || (param2_ret == -ENODEV && param2 == 0))) { + return 0; + } + } + + return -EINVAL; +} + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +int zmk_behavior_validate_binding(const struct zmk_behavior_binding *binding) { +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + const struct device *behavior = zmk_behavior_get_binding(binding->behavior_dev); + + if (!behavior) { + return -ENODEV; + } + + struct behavior_parameter_metadata metadata; + int ret = behavior_get_parameter_metadata(behavior, &metadata); + + if (ret < 0) { + LOG_WRN("Failed getting metadata for %s: %d", binding->behavior_dev, ret); + return ret; + } + + return zmk_behavior_check_params_match_metadata(&metadata, binding->param1, binding->param2); +#else + return 0; +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +} + +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS) + +zmk_behavior_local_id_t zmk_behavior_get_local_id(const char *name) { + if (!name) { + return UINT16_MAX; + } + + STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) { + if (z_device_is_ready(item->device) && strcmp(item->device->name, name) == 0) { + return item->local_id; + } + } + + return UINT16_MAX; +} + +const char *zmk_behavior_find_behavior_name_from_local_id(zmk_behavior_local_id_t local_id) { + STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) { + if (z_device_is_ready(item->device) && item->local_id == local_id) { + return item->device->name; + } + } + + return NULL; +} + +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_ID_TYPE_CRC16) + +static int behavior_local_id_init(void) { + STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) { + item->local_id = crc16_ansi(item->device->name, strlen(item->device->name)); + } + + return 0; +} + +SYS_INIT(behavior_local_id_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); + +#elif IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_ID_TYPE_SETTINGS_TABLE) + +static zmk_behavior_local_id_t largest_local_id = 0; + +static int behavior_handle_set(const char *name, size_t len, settings_read_cb read_cb, + void *cb_arg) { + const char *next; + + if (settings_name_steq(name, "local_id", &next) && next) { + char *endptr; + zmk_behavior_local_id_t local_id = strtoul(next, &endptr, 10); + if (*endptr != '\0') { + LOG_WRN("Invalid behavior local ID: %s with endptr %s", next, endptr); + return -EINVAL; + } + + if (len >= 64) { + LOG_ERR("Too large binding setting size (got %d expected less than %d)", len, 64); + return -EINVAL; + } + + char name[len + 1]; + + int err = read_cb(cb_arg, name, len); + if (err <= 0) { + LOG_ERR("Failed to handle keymap binding from settings (err %d)", err); + return err; + } + + name[len] = '\0'; + STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) { + if (strcmp(name, item->device->name) == 0) { + item->local_id = local_id; + largest_local_id = MAX(largest_local_id, local_id); + return 0; + } + } + + return -EINVAL; + } + + return 0; +} + +static int behavior_handle_commit(void) { + STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) { + if (item->local_id != 0) { + continue; + } + + if (!item->device || !item->device->name || !device_is_ready(item->device)) { + LOG_WRN("Skipping ID for device that doesn't exist or isn't ready"); + continue; + } + + item->local_id = ++largest_local_id; + char setting_name[32]; + sprintf(setting_name, "behavior/local_id/%d", item->local_id); + + // If the `device->name` is readonly in flash, settings save can fail to copy/read it while + // persisting to flash, so copy the device name into memory first before saving. + char device_name[32]; + snprintf(device_name, ARRAY_SIZE(device_name), "%s", item->device->name); + + settings_save_one(setting_name, device_name, strlen(device_name)); + } + + return 0; +} + +SETTINGS_STATIC_HANDLER_DEFINE(behavior, "behavior", NULL, behavior_handle_set, + behavior_handle_commit, NULL); + +#else + +#error "A behavior local ID mechanism must be selected" + +#endif + +#endif + #if IS_ENABLED(CONFIG_LOG) static int check_behavior_names(void) { // Behavior names must be unique, but we don't have a good way to enforce this diff --git a/app/src/behaviors/behavior_backlight.c b/app/src/behaviors/behavior_backlight.c index 3f836b73..d67ce2e7 100644 --- a/app/src/behaviors/behavior_backlight.c +++ b/app/src/behaviors/behavior_backlight.c @@ -18,6 +18,82 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static const struct behavior_parameter_value_metadata no_arg_values[] = { + { + .display_name = "Toggle On/Off", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = BL_TOG_CMD, + }, + { + .display_name = "Turn On", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = BL_ON_CMD, + }, + { + .display_name = "Turn OFF", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = BL_OFF_CMD, + }, + { + .display_name = "Increase Brightness", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = BL_INC_CMD, + }, + { + .display_name = "Decrease Brightness", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = BL_DEC_CMD, + }, + { + .display_name = "Cycle Brightness", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = BL_CYCLE_CMD, + }, +}; + +static const struct behavior_parameter_value_metadata one_arg_p1_values[] = { + { + .display_name = "Set Brightness", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = BL_SET_CMD, + }, +}; + +static const struct behavior_parameter_value_metadata one_arg_p2_values[] = { + { + .display_name = "Brightness", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_RANGE, + .range = + { + .min = 0, + .max = 255, + }, + }, +}; + +static const struct behavior_parameter_metadata_set no_args_set = { + .param1_values = no_arg_values, + .param1_values_len = ARRAY_SIZE(no_arg_values), +}; + +static const struct behavior_parameter_metadata_set one_args_set = { + .param1_values = one_arg_p1_values, + .param1_values_len = ARRAY_SIZE(one_arg_p1_values), + .param2_values = one_arg_p2_values, + .param2_values_len = ARRAY_SIZE(one_arg_p2_values), +}; + +static const struct behavior_parameter_metadata_set sets[] = {no_args_set, one_args_set}; + +static const struct behavior_parameter_metadata metadata = { + .sets_len = ARRAY_SIZE(sets), + .sets = sets, +}; + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + static int behavior_backlight_init(const struct device *dev) { return 0; } static int @@ -89,6 +165,9 @@ static const struct behavior_driver_api behavior_backlight_driver_api = { .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released, .locality = BEHAVIOR_LOCALITY_GLOBAL, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .parameter_metadata = &metadata, +#endif }; BEHAVIOR_DT_INST_DEFINE(0, behavior_backlight_init, NULL, NULL, NULL, POST_KERNEL, diff --git a/app/src/behaviors/behavior_bt.c b/app/src/behaviors/behavior_bt.c index 03bb7d8c..f439e49b 100644 --- a/app/src/behaviors/behavior_bt.c +++ b/app/src/behaviors/behavior_bt.c @@ -20,6 +20,74 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static const struct behavior_parameter_value_metadata no_arg_values[] = { + { + .display_name = "Next Profile", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = BT_NXT_CMD, + }, + { + .display_name = "Previous Profile", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = BT_PRV_CMD, + }, + { + .display_name = "Clear All Profiles", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = BT_CLR_ALL_CMD, + }, + { + .display_name = "Clear Selected Profile", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = BT_CLR_CMD, + }, +}; + +static const struct behavior_parameter_metadata_set no_args_set = { + .param1_values = no_arg_values, + .param1_values_len = ARRAY_SIZE(no_arg_values), +}; + +static const struct behavior_parameter_value_metadata prof_index_param1_values[] = { + { + .display_name = "Select Profile", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = BT_SEL_CMD, + }, + { + .display_name = "Disconnect Profile", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = BT_DISC_CMD, + }, +}; + +static const struct behavior_parameter_value_metadata prof_index_param2_values[] = { + { + .display_name = "Profile", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_RANGE, + .range = {.min = 0, .max = ZMK_BLE_PROFILE_COUNT}, + }, +}; + +static const struct behavior_parameter_metadata_set profile_index_metadata_set = { + .param1_values = prof_index_param1_values, + .param1_values_len = ARRAY_SIZE(prof_index_param1_values), + .param2_values = prof_index_param2_values, + .param2_values_len = ARRAY_SIZE(prof_index_param2_values), +}; + +static const struct behavior_parameter_metadata_set metadata_sets[] = {no_args_set, + profile_index_metadata_set}; + +static const struct behavior_parameter_metadata metadata = { + .sets_len = ARRAY_SIZE(metadata_sets), + .sets = metadata_sets, +}; + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { switch (binding->param1) { @@ -54,6 +122,9 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding, static const struct behavior_driver_api behavior_bt_driver_api = { .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .parameter_metadata = &metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; BEHAVIOR_DT_INST_DEFINE(0, behavior_bt_init, NULL, NULL, NULL, POST_KERNEL, diff --git a/app/src/behaviors/behavior_caps_word.c b/app/src/behaviors/behavior_caps_word.c index d9b3f24e..bf74a4b3 100644 --- a/app/src/behaviors/behavior_caps_word.c +++ b/app/src/behaviors/behavior_caps_word.c @@ -75,6 +75,9 @@ static int on_caps_word_binding_released(struct zmk_behavior_binding *binding, static const struct behavior_driver_api behavior_caps_word_driver_api = { .binding_pressed = on_caps_word_binding_pressed, .binding_released = on_caps_word_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; static int caps_word_keycode_state_changed_listener(const zmk_event_t *eh); diff --git a/app/src/behaviors/behavior_hold_tap.c b/app/src/behaviors/behavior_hold_tap.c index 57263d1c..1c050c44 100644 --- a/app/src/behaviors/behavior_hold_tap.c +++ b/app/src/behaviors/behavior_hold_tap.c @@ -68,6 +68,12 @@ struct behavior_hold_tap_config { int32_t hold_trigger_key_positions[]; }; +struct behavior_hold_tap_data { +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + struct behavior_parameter_metadata_set set; +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +}; + // this data is specific for each hold-tap struct active_hold_tap { int32_t position; @@ -652,9 +658,52 @@ static int on_hold_tap_binding_released(struct zmk_behavior_binding *binding, return ZMK_BEHAVIOR_OPAQUE; } +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +static int hold_tap_parameter_metadata(const struct device *hold_tap, + struct behavior_parameter_metadata *param_metadata) { + const struct behavior_hold_tap_config *cfg = hold_tap->config; + struct behavior_hold_tap_data *data = hold_tap->data; + int err; + struct behavior_parameter_metadata child_meta; + + err = behavior_get_parameter_metadata(zmk_behavior_get_binding(cfg->hold_behavior_dev), + &child_meta); + if (err < 0) { + LOG_WRN("Failed to get the hold behavior parameter: %d", err); + return err; + } + + if (child_meta.sets_len > 0) { + data->set.param1_values = child_meta.sets[0].param1_values; + data->set.param1_values_len = child_meta.sets[0].param1_values_len; + } + + err = behavior_get_parameter_metadata(zmk_behavior_get_binding(cfg->tap_behavior_dev), + &child_meta); + if (err < 0) { + LOG_WRN("Failed to get the tap behavior parameter: %d", err); + return err; + } + + if (child_meta.sets_len > 0) { + data->set.param2_values = child_meta.sets[0].param1_values; + data->set.param2_values_len = child_meta.sets[0].param1_values_len; + } + + param_metadata->sets = &data->set; + param_metadata->sets_len = 1; + + return 0; +} + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + static const struct behavior_driver_api behavior_hold_tap_driver_api = { .binding_pressed = on_hold_tap_binding_pressed, .binding_released = on_hold_tap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = hold_tap_parameter_metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; static int position_state_changed_listener(const zmk_event_t *eh) { @@ -791,7 +840,7 @@ static int behavior_hold_tap_init(const struct device *dev) { } #define KP_INST(n) \ - static struct behavior_hold_tap_config behavior_hold_tap_config_##n = { \ + static const struct behavior_hold_tap_config behavior_hold_tap_config_##n = { \ .tapping_term_ms = DT_INST_PROP(n, tapping_term_ms), \ .hold_behavior_dev = DEVICE_DT_NAME(DT_INST_PHANDLE_BY_IDX(n, bindings, 0)), \ .tap_behavior_dev = DEVICE_DT_NAME(DT_INST_PHANDLE_BY_IDX(n, bindings, 1)), \ @@ -807,9 +856,10 @@ static int behavior_hold_tap_init(const struct device *dev) { .hold_trigger_key_positions = DT_INST_PROP(n, hold_trigger_key_positions), \ .hold_trigger_key_positions_len = DT_INST_PROP_LEN(n, hold_trigger_key_positions), \ }; \ - BEHAVIOR_DT_INST_DEFINE(n, behavior_hold_tap_init, NULL, NULL, &behavior_hold_tap_config_##n, \ - POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ - &behavior_hold_tap_driver_api); + static struct behavior_hold_tap_data behavior_hold_tap_data_##n = {}; \ + BEHAVIOR_DT_INST_DEFINE(n, behavior_hold_tap_init, NULL, &behavior_hold_tap_data_##n, \ + &behavior_hold_tap_config_##n, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_hold_tap_driver_api); DT_INST_FOREACH_STATUS_OKAY(KP_INST) diff --git a/app/src/behaviors/behavior_key_press.c b/app/src/behaviors/behavior_key_press.c index 566cfcfb..b090401e 100644 --- a/app/src/behaviors/behavior_key_press.c +++ b/app/src/behaviors/behavior_key_press.c @@ -16,6 +16,27 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static const struct behavior_parameter_value_metadata param_values[] = { + { + .display_name = "Key", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_HID_USAGE, + }, +}; + +static const struct behavior_parameter_metadata_set param_metadata_set[] = {{ + .param1_values = param_values, + .param1_values_len = ARRAY_SIZE(param_values), +}}; + +static const struct behavior_parameter_metadata metadata = { + .sets_len = ARRAY_SIZE(param_metadata_set), + .sets = param_metadata_set, +}; + +#endif + static int behavior_key_press_init(const struct device *dev) { return 0; }; static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, @@ -31,7 +52,12 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding, } static const struct behavior_driver_api behavior_key_press_driver_api = { - .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released}; + .binding_pressed = on_keymap_binding_pressed, + .binding_released = on_keymap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .parameter_metadata = &metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +}; #define KP_INST(n) \ BEHAVIOR_DT_INST_DEFINE(n, behavior_key_press_init, NULL, NULL, NULL, POST_KERNEL, \ diff --git a/app/src/behaviors/behavior_key_repeat.c b/app/src/behaviors/behavior_key_repeat.c index c93fa722..21343ae8 100644 --- a/app/src/behaviors/behavior_key_repeat.c +++ b/app/src/behaviors/behavior_key_repeat.c @@ -67,6 +67,9 @@ static int on_key_repeat_binding_released(struct zmk_behavior_binding *binding, static const struct behavior_driver_api behavior_key_repeat_driver_api = { .binding_pressed = on_key_repeat_binding_pressed, .binding_released = on_key_repeat_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; static int key_repeat_keycode_state_changed_listener(const zmk_event_t *eh); diff --git a/app/src/behaviors/behavior_key_toggle.c b/app/src/behaviors/behavior_key_toggle.c index 0dc0f5ab..72f2570b 100644 --- a/app/src/behaviors/behavior_key_toggle.c +++ b/app/src/behaviors/behavior_key_toggle.c @@ -31,9 +31,33 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding, return 0; } +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static const struct behavior_parameter_value_metadata param_values[] = { + { + .display_name = "Key", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_HID_USAGE, + }, +}; + +static const struct behavior_parameter_metadata_set param_metadata_set[] = {{ + .param1_values = param_values, + .param1_values_len = ARRAY_SIZE(param_values), +}}; + +static const struct behavior_parameter_metadata metadata = { + .sets_len = ARRAY_SIZE(param_metadata_set), + .sets = param_metadata_set, +}; + +#endif + static const struct behavior_driver_api behavior_key_toggle_driver_api = { .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .parameter_metadata = &metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; #define KT_INST(n) \ diff --git a/app/src/behaviors/behavior_macro.c b/app/src/behaviors/behavior_macro.c index acffe3d8..b535ed8b 100644 --- a/app/src/behaviors/behavior_macro.c +++ b/app/src/behaviors/behavior_macro.c @@ -34,6 +34,10 @@ struct behavior_macro_trigger_state { struct behavior_macro_state { struct behavior_macro_trigger_state release_state; +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + struct behavior_parameter_metadata_set set; +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + uint32_t press_bindings_count; }; @@ -209,9 +213,100 @@ static int on_macro_binding_released(struct zmk_behavior_binding *binding, return ZMK_BEHAVIOR_OPAQUE; } +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static void assign_values_to_set(enum param_source param_source, + struct behavior_parameter_metadata_set *set, + const struct behavior_parameter_value_metadata *values, + size_t values_len) { + if (param_source == PARAM_SOURCE_MACRO_1ST) { + set->param1_values = values; + set->param1_values_len = values_len; + } else { + set->param2_values = values; + set->param2_values_len = values_len; + } +} + +// This function will dynamically determine the parameter metadata for a particular macro by +// inspecting the macro *bindings* to see what behaviors in that list receive the macro parameters, +// and then using the metadata from those behaviors for the macro itself. +// +// Care need be taken, where a behavior in the list takes two parameters, and the macro passes along +// a value for the *second* parameter, we need to make sure we find the right metadata set for the +// referenced behavior that matches the first parameter. +static int get_macro_parameter_metadata(const struct device *macro, + struct behavior_parameter_metadata *param_metadata) { + const struct behavior_macro_config *cfg = macro->config; + struct behavior_macro_state *data = macro->data; + struct behavior_macro_trigger_state state = {0}; + + for (int i = 0; (i < cfg->count) && (!data->set.param1_values || !data->set.param2_values); + i++) { + if (handle_control_binding(&state, &cfg->bindings[i]) || + (state.param1_source == PARAM_SOURCE_BINDING && + state.param2_source == PARAM_SOURCE_BINDING)) { + continue; + } + + LOG_DBG("checking %d for the given state", i); + + struct behavior_parameter_metadata binding_meta; + int err = behavior_get_parameter_metadata( + zmk_behavior_get_binding(cfg->bindings[i].behavior_dev), &binding_meta); + if (err < 0 || binding_meta.sets_len == 0) { + LOG_WRN("Failed to fetch macro binding parameter details %d", err); + return -ENOTSUP; + } + + // If both macro parameters get passed to this one entry, use + // the metadata for this behavior verbatim. + if (state.param1_source != PARAM_SOURCE_BINDING && + state.param2_source != PARAM_SOURCE_BINDING) { + param_metadata->sets_len = binding_meta.sets_len; + param_metadata->sets = binding_meta.sets; + return 0; + } + + if (state.param1_source != PARAM_SOURCE_BINDING) { + assign_values_to_set(state.param1_source, &data->set, + binding_meta.sets[0].param1_values, + binding_meta.sets[0].param1_values_len); + } + + if (state.param2_source != PARAM_SOURCE_BINDING) { + // For the param2 metadata, we need to find a set that matches fully bound first + // parameter of our macro entry, and use the metadata from that set. + for (int s = 0; s < binding_meta.sets_len; s++) { + if (zmk_behavior_validate_param_values(binding_meta.sets[s].param1_values, + binding_meta.sets[s].param1_values_len, + cfg->bindings[i].param1) >= 0) { + assign_values_to_set(state.param2_source, &data->set, + binding_meta.sets[s].param2_values, + binding_meta.sets[s].param2_values_len); + break; + } + } + } + + state.param1_source = PARAM_SOURCE_BINDING; + state.param2_source = PARAM_SOURCE_BINDING; + } + + param_metadata->sets_len = 1; + param_metadata->sets = &data->set; + + return 0; +} + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + static const struct behavior_driver_api behavior_macro_driver_api = { .binding_pressed = on_macro_binding_pressed, .binding_released = on_macro_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = get_macro_parameter_metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; #define TRANSFORMED_BEHAVIORS(n) \ diff --git a/app/src/behaviors/behavior_mod_morph.c b/app/src/behaviors/behavior_mod_morph.c index 3a8bf08c..303f96a7 100644 --- a/app/src/behaviors/behavior_mod_morph.c +++ b/app/src/behaviors/behavior_mod_morph.c @@ -75,6 +75,9 @@ static int on_mod_morph_binding_released(struct zmk_behavior_binding *binding, static const struct behavior_driver_api behavior_mod_morph_driver_api = { .binding_pressed = on_mod_morph_binding_pressed, .binding_released = on_mod_morph_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; static int behavior_mod_morph_init(const struct device *dev) { return 0; } diff --git a/app/src/behaviors/behavior_momentary_layer.c b/app/src/behaviors/behavior_momentary_layer.c index 0c86e605..b781a953 100644 --- a/app/src/behaviors/behavior_momentary_layer.c +++ b/app/src/behaviors/behavior_momentary_layer.c @@ -15,6 +15,27 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static const struct behavior_parameter_value_metadata param_values[] = { + { + .display_name = "Layer", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_ID, + }, +}; + +static const struct behavior_parameter_metadata_set param_metadata_set[] = {{ + .param1_values = param_values, + .param1_values_len = ARRAY_SIZE(param_values), +}}; + +static const struct behavior_parameter_metadata metadata = { + .sets_len = ARRAY_SIZE(param_metadata_set), + .sets = param_metadata_set, +}; + +#endif + struct behavior_mo_config {}; struct behavior_mo_data {}; @@ -33,7 +54,12 @@ static int mo_keymap_binding_released(struct zmk_behavior_binding *binding, } static const struct behavior_driver_api behavior_mo_driver_api = { - .binding_pressed = mo_keymap_binding_pressed, .binding_released = mo_keymap_binding_released}; + .binding_pressed = mo_keymap_binding_pressed, + .binding_released = mo_keymap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .parameter_metadata = &metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +}; static const struct behavior_mo_config behavior_mo_config = {}; diff --git a/app/src/behaviors/behavior_none.c b/app/src/behaviors/behavior_none.c index 0137622a..b1dc4ad3 100644 --- a/app/src/behaviors/behavior_none.c +++ b/app/src/behaviors/behavior_none.c @@ -31,6 +31,9 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding, static const struct behavior_driver_api behavior_none_driver_api = { .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; BEHAVIOR_DT_INST_DEFINE(0, behavior_none_init, NULL, NULL, NULL, POST_KERNEL, diff --git a/app/src/behaviors/behavior_outputs.c b/app/src/behaviors/behavior_outputs.c index d172c3a1..ffa57d16 100644 --- a/app/src/behaviors/behavior_outputs.c +++ b/app/src/behaviors/behavior_outputs.c @@ -20,6 +20,42 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static const struct behavior_parameter_value_metadata std_values[] = { + { + .value = OUT_TOG, + .display_name = "Toggle Outputs", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + }, +#if IS_ENABLED(CONFIG_ZMK_USB) + { + .value = OUT_USB, + .display_name = "USB Output", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + }, +#endif // IS_ENABLED(CONFIG_ZMK_USB) +#if IS_ENABLED(CONFIG_ZMK_BLE) + { + .value = OUT_BLE, + .display_name = "BLE Output", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + }, +#endif // IS_ENABLED(CONFIG_ZMK_BLE) +}; + +static const struct behavior_parameter_metadata_set std_set = { + .param1_values = std_values, + .param1_values_len = ARRAY_SIZE(std_values), +}; + +static const struct behavior_parameter_metadata metadata = { + .sets_len = 1, + .sets = &std_set, +}; + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { switch (binding->param1) { @@ -40,6 +76,9 @@ static int behavior_out_init(const struct device *dev) { return 0; } static const struct behavior_driver_api behavior_outputs_driver_api = { .binding_pressed = on_keymap_binding_pressed, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .parameter_metadata = &metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; BEHAVIOR_DT_INST_DEFINE(0, behavior_out_init, NULL, NULL, NULL, POST_KERNEL, diff --git a/app/src/behaviors/behavior_reset.c b/app/src/behaviors/behavior_reset.c index c559f17f..554132f4 100644 --- a/app/src/behaviors/behavior_reset.c +++ b/app/src/behaviors/behavior_reset.c @@ -38,6 +38,9 @@ static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, static const struct behavior_driver_api behavior_reset_driver_api = { .binding_pressed = on_keymap_binding_pressed, .locality = BEHAVIOR_LOCALITY_EVENT_SOURCE, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; #define RST_INST(n) \ diff --git a/app/src/behaviors/behavior_rgb_underglow.c b/app/src/behaviors/behavior_rgb_underglow.c index 774962e0..fa1d0797 100644 --- a/app/src/behaviors/behavior_rgb_underglow.c +++ b/app/src/behaviors/behavior_rgb_underglow.c @@ -19,6 +19,150 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) static uint8_t old_effect; +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static const struct behavior_parameter_value_metadata no_arg_values[] = { + { + .display_name = "Toggle On/Off", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_TOG_CMD, + }, + { + .display_name = "Turn On", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_ON_CMD, + }, + { + .display_name = "Turn OFF", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_OFF_CMD, + }, + { + .display_name = "Hue Up", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_HUI_CMD, + }, + { + .display_name = "Hue Down", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_HUD_CMD, + }, + { + .display_name = "Saturation Up", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_SAI_CMD, + }, + { + .display_name = "Saturation Down", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_SAD_CMD, + }, + { + .display_name = "Brightness Up", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_BRI_CMD, + }, + { + .display_name = "Brightness Down", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_BRD_CMD, + }, + { + .display_name = "Speed Up", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_SPI_CMD, + }, + { + .display_name = "Speed Down", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_SPD_CMD, + }, + { + .display_name = "Next Effect", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_EFF_CMD, + }, + { + .display_name = "Previous Effect", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_EFR_CMD, + }, +}; + +static const struct behavior_parameter_metadata_set no_args_set = { + .param1_values = no_arg_values, + .param1_values_len = ARRAY_SIZE(no_arg_values), +}; + +static const struct behavior_parameter_value_metadata eff_p1_value_metadata_values[] = { + { + .display_name = "Set Effect", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_EFS_CMD, + }, + { + .display_name = "Momentary Set Effect", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_MEFS_CMD, + }, +}; + +static const struct behavior_parameter_value_metadata eff_p2_value_metadata_values[] = { + { + .display_name = "Effect", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_RANGE, + .range = + { + .min = 0, + .max = 3, + }, + }, +}; + +static const struct behavior_parameter_metadata_set eff_value_metadata_set = { + .param1_values = eff_p1_value_metadata_values, + .param1_values_len = ARRAY_SIZE(eff_p1_value_metadata_values), + .param_values = eff_p2_value_metadata_values, + .param_values_len = ARRAY_SIZE(eff_p2_value_metadata_values), +}; + +/* +static const struct behavior_parameter_value_metadata hsv_p1_value_metadata_values[] = { + { + .display_name = "Set Color", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE, + .value = RGB_COLOR_HSB_CMD, + }, +}; + +static const struct behavior_parameter_value_metadata hsv_p2_value_metadata_values[] = { + { + .display_name = "Color", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_STANDARD, + .standard = BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HSV, + }, +}; + +static const struct behavior_parameter_metadata_set hsv_value_metadata_set = { + .param1_values = hsv_p1_value_metadata_values, + .param1_values_len = ARRAY_SIZE(hsv_p1_value_metadata_values), + .param_values = hsv_p2_value_metadata_values, + .param_values_len = ARRAY_SIZE(hsv_p2_value_metadata_values), +}; + +*/ + +static const struct behavior_parameter_metadata_set sets[] = { + no_args_set, eff_value_metadata_set, + // hsv_value_metadata_set, +}; + +static const struct behavior_parameter_metadata metadata = { + .sets_len = ARRAY_SIZE(sets), + .sets = sets, +}; + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) static int behavior_rgb_underglow_init(const struct device *dev) { return 0; } @@ -154,6 +298,9 @@ static const struct behavior_driver_api behavior_rgb_underglow_driver_api = { .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released, .locality = BEHAVIOR_LOCALITY_GLOBAL, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .parameter_metadata = &metadata, +#endif }; BEHAVIOR_DT_INST_DEFINE(0, behavior_rgb_underglow_init, NULL, NULL, NULL, POST_KERNEL, diff --git a/app/src/behaviors/behavior_soft_off.c b/app/src/behaviors/behavior_soft_off.c index 461ce933..fcffd09a 100644 --- a/app/src/behaviors/behavior_soft_off.c +++ b/app/src/behaviors/behavior_soft_off.c @@ -74,6 +74,9 @@ static const struct behavior_driver_api behavior_soft_off_driver_api = { .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released, .locality = BEHAVIOR_LOCALITY_GLOBAL, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; #define BSO_INST(n) \ diff --git a/app/src/behaviors/behavior_sticky_key.c b/app/src/behaviors/behavior_sticky_key.c index b0e9f3ed..d1299c78 100644 --- a/app/src/behaviors/behavior_sticky_key.c +++ b/app/src/behaviors/behavior_sticky_key.c @@ -188,9 +188,41 @@ static int on_sticky_key_binding_released(struct zmk_behavior_binding *binding, return ZMK_BEHAVIOR_OPAQUE; } +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static int sticky_key_parameter_domains(const struct device *sk, + struct behavior_parameter_metadata *param_metadata) { + const struct behavior_sticky_key_config *cfg = sk->config; + + struct behavior_parameter_metadata child_metadata; + + int err = behavior_get_parameter_metadata(zmk_behavior_get_binding(cfg->behavior.behavior_dev), + &child_metadata); + if (err < 0) { + LOG_WRN("Failed to get the sticky key bound behavior parameter: %d", err); + } + + for (int s = 0; s < child_metadata.sets_len; s++) { + const struct behavior_parameter_metadata_set *set = &child_metadata.sets[s]; + + if (set->param2_values_len > 0) { + return -ENOTSUP; + } + } + + *param_metadata = child_metadata; + + return 0; +} + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + static const struct behavior_driver_api behavior_sticky_key_driver_api = { .binding_pressed = on_sticky_key_binding_pressed, .binding_released = on_sticky_key_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = sticky_key_parameter_domains, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh); @@ -337,7 +369,7 @@ struct behavior_sticky_key_data {}; static struct behavior_sticky_key_data behavior_sticky_key_data; #define KP_INST(n) \ - static struct behavior_sticky_key_config behavior_sticky_key_config_##n = { \ + static const struct behavior_sticky_key_config behavior_sticky_key_config_##n = { \ .behavior = ZMK_KEYMAP_EXTRACT_BINDING(0, DT_DRV_INST(n)), \ .release_after_ms = DT_INST_PROP(n, release_after_ms), \ .quick_release = DT_INST_PROP(n, quick_release), \ diff --git a/app/src/behaviors/behavior_tap_dance.c b/app/src/behaviors/behavior_tap_dance.c index 4f6fa1a1..ce57b70f 100644 --- a/app/src/behaviors/behavior_tap_dance.c +++ b/app/src/behaviors/behavior_tap_dance.c @@ -189,6 +189,9 @@ void behavior_tap_dance_timer_handler(struct k_work *item) { static const struct behavior_driver_api behavior_tap_dance_driver_api = { .binding_pressed = on_tap_dance_binding_pressed, .binding_released = on_tap_dance_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; static int tap_dance_position_state_changed_listener(const zmk_event_t *eh); diff --git a/app/src/behaviors/behavior_to_layer.c b/app/src/behaviors/behavior_to_layer.c index 1c87a925..f739ec8d 100644 --- a/app/src/behaviors/behavior_to_layer.c +++ b/app/src/behaviors/behavior_to_layer.c @@ -32,9 +32,33 @@ static int to_keymap_binding_released(struct zmk_behavior_binding *binding, return ZMK_BEHAVIOR_OPAQUE; } +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static const struct behavior_parameter_value_metadata param_values[] = { + { + .display_name = "Layer", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_ID, + }, +}; + +static const struct behavior_parameter_metadata_set param_metadata_set[] = {{ + .param1_values = param_values, + .param1_values_len = ARRAY_SIZE(param_values), +}}; + +static const struct behavior_parameter_metadata metadata = { + .sets_len = ARRAY_SIZE(param_metadata_set), + .sets = param_metadata_set, +}; + +#endif + static const struct behavior_driver_api behavior_to_driver_api = { .binding_pressed = to_keymap_binding_pressed, .binding_released = to_keymap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .parameter_metadata = &metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; BEHAVIOR_DT_INST_DEFINE(0, behavior_to_init, NULL, NULL, NULL, POST_KERNEL, diff --git a/app/src/behaviors/behavior_toggle_layer.c b/app/src/behaviors/behavior_toggle_layer.c index 817462df..ea46c79f 100644 --- a/app/src/behaviors/behavior_toggle_layer.c +++ b/app/src/behaviors/behavior_toggle_layer.c @@ -34,9 +34,33 @@ static int tog_keymap_binding_released(struct zmk_behavior_binding *binding, return ZMK_BEHAVIOR_OPAQUE; } +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +static const struct behavior_parameter_value_metadata param_values[] = { + { + .display_name = "Layer", + .type = BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_ID, + }, +}; + +static const struct behavior_parameter_metadata_set param_metadata_set[] = {{ + .param1_values = param_values, + .param1_values_len = ARRAY_SIZE(param_values), +}}; + +static const struct behavior_parameter_metadata metadata = { + .sets_len = ARRAY_SIZE(param_metadata_set), + .sets = param_metadata_set, +}; + +#endif + static const struct behavior_driver_api behavior_tog_driver_api = { .binding_pressed = tog_keymap_binding_pressed, .binding_released = tog_keymap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .parameter_metadata = &metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; static const struct behavior_tog_config behavior_tog_config = {}; diff --git a/app/src/behaviors/behavior_transparent.c b/app/src/behaviors/behavior_transparent.c index c7bf802b..32357046 100644 --- a/app/src/behaviors/behavior_transparent.c +++ b/app/src/behaviors/behavior_transparent.c @@ -31,6 +31,9 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding, static const struct behavior_driver_api behavior_transparent_driver_api = { .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released, +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + .get_parameter_metadata = zmk_behavior_get_empty_param_metadata, +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) }; BEHAVIOR_DT_INST_DEFINE(0, behavior_transparent_init, NULL, NULL, NULL, POST_KERNEL, diff --git a/app/src/ble.c b/app/src/ble.c index 7e1ae7d4..776730fe 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -318,6 +318,21 @@ int zmk_ble_prof_disconnect(uint8_t index) { bt_addr_le_t *zmk_ble_active_profile_addr(void) { return &profiles[active_profile].peer; } +struct bt_conn *zmk_ble_active_profile_conn(void) { + struct bt_conn *conn; + bt_addr_le_t *addr = zmk_ble_active_profile_addr(); + + if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) { + LOG_WRN("Not sending, no active address for current profile"); + return NULL; + } else if ((conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr)) == NULL) { + LOG_WRN("Not sending, not connected to active profile"); + return NULL; + } + + return conn; +} + char *zmk_ble_active_profile_name(void) { return profiles[active_profile].name; } #if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) @@ -430,7 +445,11 @@ static int ble_profiles_handle_set(const char *name, size_t len, settings_read_c return 0; }; -struct settings_handler profiles_handler = {.name = "ble", .h_set = ble_profiles_handle_set}; +static int zmk_ble_complete_startup(void); + +static struct settings_handler profiles_handler = { + .name = "ble", .h_set = ble_profiles_handle_set, .h_commit = zmk_ble_complete_startup}; + #endif /* IS_ENABLED(CONFIG_SETTINGS) */ static bool is_conn_active_profile(const struct bt_conn *conn) { @@ -629,29 +648,7 @@ static void zmk_ble_ready(int err) { update_advertising(); } -static int zmk_ble_init(void) { - int err = bt_enable(NULL); - - if (err) { - LOG_ERR("BLUETOOTH FAILED (%d)", err); - return err; - } - -#if IS_ENABLED(CONFIG_SETTINGS) - settings_subsys_init(); - - err = settings_register(&profiles_handler); - if (err) { - LOG_ERR("Failed to setup the profile settings handler (err %d)", err); - return err; - } - - k_work_init_delayable(&ble_save_work, ble_save_profile_work); - - settings_load_subtree("ble"); - settings_load_subtree("bt"); - -#endif +static int zmk_ble_complete_startup(void) { #if IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) LOG_WRN("Clearing all existing BLE bond information from the keyboard"); @@ -691,6 +688,24 @@ static int zmk_ble_init(void) { return 0; } +static int zmk_ble_init(void) { + int err = bt_enable(NULL); + + if (err < 0 && err != -EALREADY) { + LOG_ERR("BLUETOOTH FAILED (%d)", err); + return err; + } + +#if IS_ENABLED(CONFIG_SETTINGS) + settings_register(&profiles_handler); + k_work_init_delayable(&ble_save_work, ble_save_profile_work); +#else + zmk_ble_complete_startup(); +#endif + + return 0; +} + #if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) static bool zmk_ble_numeric_usage_to_value(const zmk_key_t key, const zmk_key_t one, diff --git a/app/src/endpoints.c b/app/src/endpoints.c index 7c9d15a3..65243853 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -263,7 +263,8 @@ static int endpoints_handle_set(const char *name, size_t len, settings_read_cb r return 0; } -struct settings_handler endpoints_handler = {.name = "endpoints", .h_set = endpoints_handle_set}; +SETTINGS_STATIC_HANDLER_DEFINE(endpoints, "endpoints", NULL, endpoints_handle_set, NULL, NULL); + #endif /* IS_ENABLED(CONFIG_SETTINGS) */ static bool is_usb_ready(void) { @@ -322,17 +323,7 @@ static struct zmk_endpoint_instance get_selected_instance(void) { static int zmk_endpoints_init(void) { #if IS_ENABLED(CONFIG_SETTINGS) - settings_subsys_init(); - - int err = settings_register(&endpoints_handler); - if (err) { - LOG_ERR("Failed to register the endpoints settings handler (err %d)", err); - return err; - } - k_work_init_delayable(&endpoints_save_work, endpoints_save_preferred_work); - - settings_load_subtree("endpoints"); #endif current_instance = get_selected_instance(); diff --git a/app/src/ext_power_generic.c b/app/src/ext_power_generic.c index 2586f436..17b3ba64 100644 --- a/app/src/ext_power_generic.c +++ b/app/src/ext_power_generic.c @@ -121,12 +121,27 @@ static int ext_power_settings_set(const char *name, size_t len, settings_read_cb return -ENOENT; } -struct settings_handler ext_power_conf = {.name = "ext_power/state", - .h_set = ext_power_settings_set}; +static int ext_power_settings_commit() { + const struct device *dev = DEVICE_DT_GET(DT_DRV_INST(0)); + struct ext_power_generic_data *data = dev->data; + + if (!data->settings_init) { + + data->status = true; + k_work_schedule(&ext_power_save_work, K_NO_WAIT); + + ext_power_enable(dev); + } + + return 0; +} + +SETTINGS_STATIC_HANDLER_DEFINE(ext_power, "ext_power/state", NULL, ext_power_settings_set, + ext_power_settings_commit, NULL); + #endif static int ext_power_generic_init(const struct device *dev) { - struct ext_power_generic_data *data = dev->data; const struct ext_power_generic_config *config = dev->config; if (gpio_pin_configure_dt(&config->control, GPIO_OUTPUT_INACTIVE)) { @@ -135,30 +150,12 @@ static int ext_power_generic_init(const struct device *dev) { } #if IS_ENABLED(CONFIG_SETTINGS) - settings_subsys_init(); - - int err = settings_register(&ext_power_conf); - if (err) { - LOG_ERR("Failed to register the ext_power settings handler (err %d)", err); - return err; - } - k_work_init_delayable(&ext_power_save_work, ext_power_save_state_work); - - // Set default value (on) if settings isn't set - settings_load_subtree("ext_power"); - if (!data->settings_init) { - - data->status = true; - k_work_schedule(&ext_power_save_work, K_NO_WAIT); - - ext_power_enable(dev); - } -#else - // Default to the ext_power being open when no settings - ext_power_enable(dev); #endif + // Enable by default. We may get disabled again once settings load. + ext_power_enable(dev); + if (config->init_delay_ms) { k_msleep(config->init_delay_ms); } diff --git a/app/src/hid_indicators.c b/app/src/hid_indicators.c index 1b489068..a2220b1b 100644 --- a/app/src/hid_indicators.c +++ b/app/src/hid_indicators.c @@ -64,5 +64,5 @@ static int profile_listener(const zmk_event_t *eh) { return 0; } -static ZMK_LISTENER(profile_listener, profile_listener); -static ZMK_SUBSCRIPTION(profile_listener, zmk_endpoint_changed); +ZMK_LISTENER(profile_listener, profile_listener); +ZMK_SUBSCRIPTION(profile_listener, zmk_endpoint_changed); diff --git a/app/src/hid_listener.c b/app/src/hid_listener.c index 2b847082..2d17a395 100644 --- a/app/src/hid_listener.c +++ b/app/src/hid_listener.c @@ -66,6 +66,17 @@ static int hid_listener_keycode_released(const struct zmk_keycode_state_changed return err; } +#if IS_ENABLED(CONFIG_ZMK_HID_SEPARATE_MOD_RELEASE_REPORT) + + // send report of normal key release early to fix the issue + // of some programs recognizing the implicit_mod release before the actual key release + err = zmk_endpoints_send_report(ev->usage_page); + if (err < 0) { + LOG_ERR("Failed to send key report for the released keycode (%d)", err); + } + +#endif // IS_ENABLED(CONFIG_ZMK_HID_SEPARATE_MOD_RELEASE_REPORT) + explicit_mods_changed = zmk_hid_unregister_mods(ev->explicit_modifiers); // There is a minor issue with this code. // If LC(A) is pressed, then LS(B), then LC(A) is released, the shift for B will be released @@ -73,7 +84,7 @@ static int hid_listener_keycode_released(const struct zmk_keycode_state_changed // Solving this would require keeping track of which key's implicit modifiers are currently // active and only releasing modifiers at that time. implicit_mods_changed = zmk_hid_implicit_modifiers_release(); - ; + if (ev->usage_page != HID_USAGE_KEY && (explicit_mods_changed > 0 || implicit_mods_changed > 0)) { err = zmk_endpoints_send_report(HID_USAGE_KEY); diff --git a/app/src/hog.c b/app/src/hog.c index f17f759c..82fafc29 100644 --- a/app/src/hog.c +++ b/app/src/hog.c @@ -220,21 +220,6 @@ BT_GATT_SERVICE_DEFINE( BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT, BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE, NULL, write_ctrl_point, &ctrl_point)); -struct bt_conn *destination_connection(void) { - struct bt_conn *conn; - bt_addr_le_t *addr = zmk_ble_active_profile_addr(); - LOG_DBG("Address pointer %p", addr); - if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) { - LOG_WRN("Not sending, no active address for current profile"); - return NULL; - } else if ((conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr)) == NULL) { - LOG_WRN("Not sending, not connected to active profile"); - return NULL; - } - - return conn; -} - K_THREAD_STACK_DEFINE(hog_q_stack, CONFIG_ZMK_BLE_THREAD_STACK_SIZE); struct k_work_q hog_work_q; @@ -246,7 +231,7 @@ void send_keyboard_report_callback(struct k_work *work) { struct zmk_hid_keyboard_report_body report; while (k_msgq_get(&zmk_hog_keyboard_msgq, &report, K_NO_WAIT) == 0) { - struct bt_conn *conn = destination_connection(); + struct bt_conn *conn = zmk_ble_active_profile_conn(); if (conn == NULL) { return; } @@ -298,7 +283,7 @@ void send_consumer_report_callback(struct k_work *work) { struct zmk_hid_consumer_report_body report; while (k_msgq_get(&zmk_hog_consumer_msgq, &report, K_NO_WAIT) == 0) { - struct bt_conn *conn = destination_connection(); + struct bt_conn *conn = zmk_ble_active_profile_conn(); if (conn == NULL) { return; } @@ -351,7 +336,7 @@ K_MSGQ_DEFINE(zmk_hog_mouse_msgq, sizeof(struct zmk_hid_mouse_report_body), void send_mouse_report_callback(struct k_work *work) { struct zmk_hid_mouse_report_body report; while (k_msgq_get(&zmk_hog_mouse_msgq, &report, K_NO_WAIT) == 0) { - struct bt_conn *conn = destination_connection(); + struct bt_conn *conn = zmk_ble_active_profile_conn(); if (conn == NULL) { return; } diff --git a/app/src/kscan.c b/app/src/kscan.c deleted file mode 100644 index 5c7a5535..00000000 --- a/app/src/kscan.c +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2020 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#include -#include -#include -#include -#include -#include - -LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); - -#include -#include -#include - -#define ZMK_KSCAN_EVENT_STATE_PRESSED 0 -#define ZMK_KSCAN_EVENT_STATE_RELEASED 1 - -struct zmk_kscan_event { - uint32_t row; - uint32_t column; - uint32_t state; -}; - -struct zmk_kscan_msg_processor { - struct k_work work; -} msg_processor; - -K_MSGQ_DEFINE(zmk_kscan_msgq, sizeof(struct zmk_kscan_event), CONFIG_ZMK_KSCAN_EVENT_QUEUE_SIZE, 4); - -static void zmk_kscan_callback(const struct device *dev, uint32_t row, uint32_t column, - bool pressed) { - struct zmk_kscan_event ev = { - .row = row, - .column = column, - .state = (pressed ? ZMK_KSCAN_EVENT_STATE_PRESSED : ZMK_KSCAN_EVENT_STATE_RELEASED)}; - - k_msgq_put(&zmk_kscan_msgq, &ev, K_NO_WAIT); - k_work_submit(&msg_processor.work); -} - -void zmk_kscan_process_msgq(struct k_work *item) { - struct zmk_kscan_event ev; - - while (k_msgq_get(&zmk_kscan_msgq, &ev, K_NO_WAIT) == 0) { - bool pressed = (ev.state == ZMK_KSCAN_EVENT_STATE_PRESSED); - int32_t position = zmk_matrix_transform_row_column_to_position(ev.row, ev.column); - - if (position < 0) { - LOG_WRN("Not found in transform: row: %d, col: %d, pressed: %s", ev.row, ev.column, - (pressed ? "true" : "false")); - continue; - } - - LOG_DBG("Row: %d, col: %d, position: %d, pressed: %s", ev.row, ev.column, position, - (pressed ? "true" : "false")); - raise_zmk_position_state_changed( - (struct zmk_position_state_changed){.source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL, - .state = pressed, - .position = position, - .timestamp = k_uptime_get()}); - } -} - -int zmk_kscan_init(const struct device *dev) { - if (dev == NULL) { - LOG_ERR("Failed to get the KSCAN device"); - return -EINVAL; - } - - k_work_init(&msg_processor.work, zmk_kscan_process_msgq); - -#if IS_ENABLED(CONFIG_PM_DEVICE) - if (pm_device_wakeup_is_capable(dev)) { - pm_device_wakeup_enable(dev, true); - } -#endif // IS_ENABLED(CONFIG_PM_DEVICE) - - kscan_config(dev, zmk_kscan_callback); - kscan_enable_callback(dev); - - return 0; -} diff --git a/app/src/kscan_sideband_behaviors.c b/app/src/kscan_sideband_behaviors.c index f3992ebc..602cae12 100644 --- a/app/src/kscan_sideband_behaviors.c +++ b/app/src/kscan_sideband_behaviors.c @@ -26,6 +26,7 @@ struct ksbb_entry { struct ksbb_config { const struct device *kscan; + bool auto_enable; struct ksbb_entry *entries; size_t entries_len; }; @@ -93,34 +94,65 @@ void ksbb_inner_kscan_callback(const struct device *dev, uint32_t row, uint32_t } static int ksbb_configure(const struct device *dev, kscan_callback_t callback) { - const struct ksbb_config *cfg = dev->config; struct ksbb_data *data = dev->data; data->callback = callback; -#if IS_ENABLED(CONFIG_PM_DEVICE) - if (pm_device_wakeup_is_enabled(dev) && pm_device_wakeup_is_capable(cfg->kscan)) { - pm_device_wakeup_enable(cfg->kscan, true); - } -#endif // IS_ENABLED(CONFIG_PM_DEVICE) - return 0; } static int ksbb_enable(const struct device *dev) { struct ksbb_data *data = dev->data; + const struct ksbb_config *config = dev->config; data->enabled = true; +#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) + if (!pm_device_runtime_is_enabled(dev) && pm_device_runtime_is_enabled(config->kscan)) { + pm_device_runtime_get(config->kscan); + } +#elif IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_action_run(config->kscan, PM_DEVICE_ACTION_RESUME); +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + + kscan_config(config->kscan, &ksbb_inner_kscan_callback); + kscan_enable_callback(config->kscan); + return 0; } static int ksbb_disable(const struct device *dev) { struct ksbb_data *data = dev->data; + const struct ksbb_config *config = dev->config; data->enabled = false; + kscan_disable_callback(config->kscan); + +#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) + if (!pm_device_runtime_is_enabled(dev) && pm_device_runtime_is_enabled(config->kscan)) { + pm_device_runtime_put(config->kscan); + } +#elif IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_action_run(config->kscan, PM_DEVICE_ACTION_SUSPEND); +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + return 0; } +#if IS_ENABLED(CONFIG_PM_DEVICE) + +static int ksbb_pm_action(const struct device *dev, enum pm_device_action action) { + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + return ksbb_disable(dev); + case PM_DEVICE_ACTION_RESUME: + return ksbb_enable(dev); + default: + return -ENOTSUP; + } +} + +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + static int ksbb_init(const struct device *dev) { const struct ksbb_config *config = dev->config; @@ -129,8 +161,16 @@ static int ksbb_init(const struct device *dev) { return -ENODEV; } - kscan_config(config->kscan, &ksbb_inner_kscan_callback); - kscan_enable_callback(config->kscan); + if (config->auto_enable) { +#if !IS_ENABLED(CONFIG_PM_DEVICE) + kscan_config(config->kscan, &ksbb_inner_kscan_callback); + kscan_enable_callback(config->kscan); +#else + ksbb_pm_action(dev, PM_DEVICE_ACTION_RESUME); + } else { + pm_device_init_suspended(dev); +#endif + } return 0; } @@ -141,21 +181,6 @@ static const struct kscan_driver_api ksbb_api = { .disable_callback = ksbb_disable, }; -#if IS_ENABLED(CONFIG_PM_DEVICE) - -static int ksbb_pm_action(const struct device *dev, enum pm_device_action action) { - switch (action) { - case PM_DEVICE_ACTION_SUSPEND: - return ksbb_disable(dev); - case PM_DEVICE_ACTION_RESUME: - return ksbb_disable(dev); - default: - return -ENOTSUP; - } -} - -#endif // IS_ENABLED(CONFIG_PM_DEVICE) - #define ENTRY(e) \ { \ .row = DT_PROP(e, row), .column = DT_PROP(e, column), \ @@ -167,13 +192,14 @@ static int ksbb_pm_action(const struct device *dev, enum pm_device_action action DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(n, ENTRY, (, ))}; \ const struct ksbb_config ksbb_config_##n = { \ .kscan = DEVICE_DT_GET(DT_INST_PHANDLE(n, kscan)), \ + .auto_enable = DT_INST_PROP_OR(n, auto_enable, false), \ .entries = entries_##n, \ .entries_len = ARRAY_SIZE(entries_##n), \ }; \ struct ksbb_data ksbb_data_##n = {}; \ PM_DEVICE_DT_INST_DEFINE(n, ksbb_pm_action); \ DEVICE_DT_INST_DEFINE(n, ksbb_init, PM_DEVICE_DT_INST_GET(n), &ksbb_data_##n, \ - &ksbb_config_##n, APPLICATION, \ + &ksbb_config_##n, POST_KERNEL, \ CONFIG_ZMK_KSCAN_SIDEBAND_BEHAVIORS_INIT_PRIORITY, &ksbb_api); DT_INST_FOREACH_STATUS_OKAY(KSBB_INST) diff --git a/app/src/main.c b/app/src/main.c index 9bd7af32..60df1a45 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -12,17 +12,15 @@ #include LOG_MODULE_REGISTER(zmk, CONFIG_ZMK_LOG_LEVEL); -#include -#include #include -#include int main(void) { LOG_INF("Welcome to ZMK!\n"); - if (zmk_kscan_init(DEVICE_DT_GET(ZMK_MATRIX_NODE_ID)) != 0) { - return -ENOTSUP; - } +#if IS_ENABLED(CONFIG_SETTINGS) + settings_subsys_init(); + settings_load(); +#endif #ifdef CONFIG_ZMK_DISPLAY zmk_display_init(); diff --git a/app/src/matrix_transform.c b/app/src/matrix_transform.c index 6c616d5e..97ab0efe 100644 --- a/app/src/matrix_transform.c +++ b/app/src/matrix_transform.c @@ -4,12 +4,23 @@ * SPDX-License-Identifier: MIT */ +#include #include +#include #include #include #include -#ifdef ZMK_KEYMAP_TRANSFORM_NODE +#define DT_DRV_COMPAT zmk_matrix_transform + +struct zmk_matrix_transform { + uint32_t const *lookup_table; + size_t len; + uint8_t rows; + uint8_t columns; + uint8_t col_offset; + uint8_t row_offset; +}; /* the transform in the device tree is a list of (row,column) pairs that is * indexed by by the keymap position of that key. We want to invert this in @@ -28,38 +39,58 @@ #define INDEX_OFFSET 1 -#define TRANSFORM_ENTRY(i, _) \ - [(KT_ROW(DT_PROP_BY_IDX(ZMK_KEYMAP_TRANSFORM_NODE, map, i)) * ZMK_MATRIX_COLS) + \ - KT_COL(DT_PROP_BY_IDX(ZMK_KEYMAP_TRANSFORM_NODE, map, i))] = i + INDEX_OFFSET +#if DT_HAS_COMPAT_STATUS_OKAY(zmk_matrix_transform) -static uint32_t transform[] = {LISTIFY(ZMK_KEYMAP_LEN, TRANSFORM_ENTRY, (, ), 0)}; +#define TRANSFORM_LOOKUP_ENTRY(i, n) \ + [(KT_ROW(DT_INST_PROP_BY_IDX(n, map, i)) * DT_INST_PROP(n, columns)) + \ + KT_COL(DT_INST_PROP_BY_IDX(n, map, i))] = i + INDEX_OFFSET -#endif +#define MATRIX_TRANSFORM_INIT(n) \ + static const uint32_t _CONCAT(zmk_transform_lookup_table_, n)[] = { \ + LISTIFY(DT_INST_PROP_LEN(n, map), TRANSFORM_LOOKUP_ENTRY, (, ), n)}; \ + const struct zmk_matrix_transform _CONCAT(zmk_matrix_transform_, DT_DRV_INST(n)) = { \ + .rows = DT_INST_PROP(n, rows), \ + .columns = DT_INST_PROP(n, columns), \ + .col_offset = DT_INST_PROP(n, col_offset), \ + .row_offset = DT_INST_PROP(n, row_offset), \ + .lookup_table = _CONCAT(zmk_transform_lookup_table_, n), \ + .len = ARRAY_SIZE(_CONCAT(zmk_transform_lookup_table_, n)), \ + }; -int32_t zmk_matrix_transform_row_column_to_position(uint32_t row, uint32_t column) { -#if DT_NODE_HAS_PROP(ZMK_KEYMAP_TRANSFORM_NODE, col_offset) - column += DT_PROP(ZMK_KEYMAP_TRANSFORM_NODE, col_offset); -#endif +DT_INST_FOREACH_STATUS_OKAY(MATRIX_TRANSFORM_INIT); -#if DT_NODE_HAS_PROP(ZMK_KEYMAP_TRANSFORM_NODE, row_offset) - row += DT_PROP(ZMK_KEYMAP_TRANSFORM_NODE, row_offset); -#endif +#elif DT_HAS_CHOSEN(zmk_kscan) && defined(ZMK_MATRIX_COLS) && defined(ZMK_MATRIX_ROWS) - const uint32_t matrix_index = (row * ZMK_MATRIX_COLS) + column; - -#ifdef ZMK_KEYMAP_TRANSFORM_NODE - if (matrix_index >= ARRAY_SIZE(transform)) { - return -EINVAL; - } - - const uint32_t value = transform[matrix_index]; - - if (!value) { - return -EINVAL; - } - - return value - INDEX_OFFSET; -#else - return matrix_index; -#endif /* ZMK_KEYMAP_TRANSFORM_NODE */ +const struct zmk_matrix_transform zmk_matrix_transform_default = { + .rows = ZMK_MATRIX_ROWS, + .columns = ZMK_MATRIX_COLS, + .len = ZMK_KEYMAP_LEN, }; + +#else + +#error "Need a matrix transform or compatible kscan selected to determine keymap size!" +` +#endif // DT_HAS_COMPAT_STATUS_OKAY(zmk_matrix_transform) + +int32_t zmk_matrix_transform_row_column_to_position(zmk_matrix_transform_t mt, uint32_t row, + uint32_t column) { + column += mt->col_offset; + row += mt->row_offset; + + if (!mt->lookup_table) { + return (row * mt->columns) + column; + } + + uint16_t lookup_index = ((row * mt->columns) + column); + if (lookup_index >= mt->len) { + return -EINVAL; + } + + int32_t val = mt->lookup_table[lookup_index]; + if (val == 0) { + return -EINVAL; + } + + return val - INDEX_OFFSET; +}; \ No newline at end of file diff --git a/app/src/physical_layouts.c b/app/src/physical_layouts.c new file mode 100644 index 00000000..16b13e71 --- /dev/null +++ b/app/src/physical_layouts.c @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_SETTINGS) +#include +#endif + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include +#include +#include + +#define DT_DRV_COMPAT zmk_physical_layout + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +#define ZKPA_INIT(i, n) \ + (const struct zmk_key_physical_attrs) { \ + .width = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, width), \ + .height = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, height), \ + .x = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, x), \ + .y = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, y), \ + .rx = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, rx), \ + .ry = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, ry), \ + .r = (int16_t)(int32_t)DT_INST_PHA_BY_IDX(n, keys, i, r), \ + } + +#define ZMK_LAYOUT_INST(n) \ + static const struct zmk_key_physical_attrs const _CONCAT( \ + _zmk_physical_layout_keys_, n)[DT_INST_PROP_LEN_OR(n, keys, 0)] = { \ + LISTIFY(DT_INST_PROP_LEN_OR(n, keys, 0), ZKPA_INIT, (, ), n)}; \ + ZMK_MATRIX_TRANSFORM_EXTERN(DT_INST_PHANDLE(n, transform)); \ + static const struct zmk_physical_layout const _CONCAT(_zmk_physical_layout_, \ + DT_DRV_INST(n)) = { \ + .display_name = DT_INST_PROP_OR(n, display_name, "Layout #" #n), \ + .matrix_transform = ZMK_MATRIX_TRANSFORM_T_FOR_NODE(DT_INST_PHANDLE(n, transform)), \ + .keys = _CONCAT(_zmk_physical_layout_keys_, n), \ + .keys_len = DT_INST_PROP_LEN_OR(n, keys, 0), \ + .kscan = DEVICE_DT_GET(COND_CODE_1(DT_INST_PROP_LEN(n, kscan), \ + (DT_INST_PHANDLE(n, kscan)), (DT_CHOSEN(zmk_kscan))))}; + +DT_INST_FOREACH_STATUS_OKAY(ZMK_LAYOUT_INST) + +#define POS_MAP_COMPAT zmk_physical_layout_position_map +#define HAVE_POS_MAP DT_HAS_COMPAT_STATUS_OKAY(POS_MAP_COMPAT) + +#define POS_MAP_COMPLETE (HAVE_POS_MAP && DT_PROP(DT_INST(0, POS_MAP_COMPAT), complete)) + +#if HAVE_POS_MAP + +// Using sizeof + union trick to calculate the "positions" length statically. +#define ZMK_POS_MAP_POSITIONS_ARRAY(node_id) \ + uint8_t _CONCAT(prop_, node_id)[DT_PROP_LEN(node_id, positions)]; + +#define ZMK_POS_MAP_LEN \ + sizeof(union {DT_FOREACH_CHILD(DT_INST(0, POS_MAP_COMPAT), ZMK_POS_MAP_POSITIONS_ARRAY)}) + +struct position_map_entry { + const struct zmk_physical_layout *layout; + const uint32_t positions[ZMK_POS_MAP_LEN]; +}; + +#define ZMK_POS_MAP_ENTRY(node_id) \ + { \ + .layout = &_CONCAT(_zmk_physical_layout_, DT_PHANDLE(node_id, physical_layout)), \ + .positions = DT_PROP(node_id, positions), \ + } + +static const struct position_map_entry positions_maps[] = { + DT_FOREACH_CHILD_SEP(DT_INST(0, POS_MAP_COMPAT), ZMK_POS_MAP_ENTRY, (, ))}; + +#endif + +#define ZMK_LAYOUT_REF(n) &_CONCAT(_zmk_physical_layout_, DT_DRV_INST(n)), + +static const struct zmk_physical_layout *const layouts[] = { + DT_INST_FOREACH_STATUS_OKAY(ZMK_LAYOUT_REF)}; + +#elif DT_HAS_CHOSEN(zmk_matrix_transform) + +ZMK_MATRIX_TRANSFORM_EXTERN(DT_CHOSEN(zmk_matrix_transform)); + +static const struct zmk_physical_layout _CONCAT(_zmk_physical_layout_, chosen) = { + .display_name = "Default", + .matrix_transform = ZMK_MATRIX_TRANSFORM_T_FOR_NODE(DT_CHOSEN(zmk_matrix_transform)), + COND_CODE_1(DT_HAS_CHOSEN(zmk_kscan), (.kscan = DEVICE_DT_GET(DT_CHOSEN(zmk_kscan)), ), ())}; + +static const struct zmk_physical_layout *const layouts[] = { + &_CONCAT(_zmk_physical_layout_, chosen)}; + +#elif DT_HAS_CHOSEN(zmk_kscan) + +ZMK_MATRIX_TRANSFORM_DEFAULT_EXTERN(); +static const struct zmk_physical_layout _CONCAT(_zmk_physical_layout_, chosen) = { + .display_name = "Default", + .matrix_transform = &zmk_matrix_transform_default, + .kscan = DEVICE_DT_GET(DT_CHOSEN(zmk_kscan)), +}; + +static const struct zmk_physical_layout *const layouts[] = { + &_CONCAT(_zmk_physical_layout_, chosen)}; + +#endif + +const struct zmk_physical_layout *active; + +size_t zmk_physical_layouts_get_list(struct zmk_physical_layout const *const **dest_layouts) { + *dest_layouts = &layouts[0]; + + return ARRAY_SIZE(layouts); +} + +#define ZMK_KSCAN_EVENT_STATE_PRESSED 0 +#define ZMK_KSCAN_EVENT_STATE_RELEASED 1 + +struct zmk_kscan_event { + uint32_t row; + uint32_t column; + uint32_t state; +}; + +static struct zmk_kscan_msg_processor { struct k_work work; } msg_processor; + +K_MSGQ_DEFINE(physical_layouts_kscan_msgq, sizeof(struct zmk_kscan_event), + CONFIG_ZMK_KSCAN_EVENT_QUEUE_SIZE, 4); + +static void zmk_physical_layout_kscan_callback(const struct device *dev, uint32_t row, + uint32_t column, bool pressed) { + if (dev != active->kscan) { + return; + } + + struct zmk_kscan_event ev = { + .row = row, + .column = column, + .state = (pressed ? ZMK_KSCAN_EVENT_STATE_PRESSED : ZMK_KSCAN_EVENT_STATE_RELEASED)}; + + k_msgq_put(&physical_layouts_kscan_msgq, &ev, K_NO_WAIT); + k_work_submit(&msg_processor.work); +} + +static void zmk_physical_layouts_kscan_process_msgq(struct k_work *item) { + struct zmk_kscan_event ev; + + while (k_msgq_get(&physical_layouts_kscan_msgq, &ev, K_NO_WAIT) == 0) { + bool pressed = (ev.state == ZMK_KSCAN_EVENT_STATE_PRESSED); + int32_t position = zmk_matrix_transform_row_column_to_position(active->matrix_transform, + ev.row, ev.column); + + if (position < 0) { + LOG_WRN("Not found in transform: row: %d, col: %d, pressed: %s", ev.row, ev.column, + (pressed ? "true" : "false")); + continue; + } + + LOG_DBG("Row: %d, col: %d, position: %d, pressed: %s", ev.row, ev.column, position, + (pressed ? "true" : "false")); + raise_zmk_position_state_changed( + (struct zmk_position_state_changed){.source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL, + .state = pressed, + .position = position, + .timestamp = k_uptime_get()}); + } +} + +int zmk_physical_layouts_select_layout(const struct zmk_physical_layout *dest_layout) { + if (!dest_layout) { + return -ENODEV; + } + + if (dest_layout == active) { + return 0; + } + + if (active) { + if (active->kscan) { + kscan_disable_callback(active->kscan); +#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) + pm_device_runtime_put(active->kscan); +#elif IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_action_run(active->kscan, PM_DEVICE_ACTION_SUSPEND); +#endif + } + } + + active = dest_layout; + + if (active->kscan) { +#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) + int err = pm_device_runtime_get(active->kscan); + if (err < 0) { + LOG_WRN("PM runtime get of kscan device to enable it %d", err); + return err; + } +#elif IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_action_run(active->kscan, PM_DEVICE_ACTION_RESUME); +#endif + kscan_config(active->kscan, zmk_physical_layout_kscan_callback); + kscan_enable_callback(active->kscan); + } + + return 0; +} + +int zmk_physical_layouts_select(uint8_t index) { + if (index >= ARRAY_SIZE(layouts)) { + return -EINVAL; + } + + return zmk_physical_layouts_select_layout(layouts[index]); +} + +int zmk_physical_layouts_get_selected(void) { + for (int i = 0; i < ARRAY_SIZE(layouts); i++) { + if (layouts[i] == active) { + return i; + } + } + + return -ENODEV; +} + +#if IS_ENABLED(CONFIG_SETTINGS) + +static int8_t saved_selected_index = -1; + +#endif + +int zmk_physical_layouts_select_initial(void) { + const struct zmk_physical_layout *initial; + +#if DT_HAS_CHOSEN(zmk_physical_layout) + initial = &_CONCAT(_zmk_physical_layout_, DT_CHOSEN(zmk_physical_layout)); +#else + initial = layouts[0]; +#endif + + int ret = zmk_physical_layouts_select_layout(initial); + + return ret; +} + +int zmk_physical_layouts_check_unsaved_selection(void) { +#if IS_ENABLED(CONFIG_SETTINGS) + return saved_selected_index < 0 || + saved_selected_index == (uint8_t)zmk_physical_layouts_get_selected() + ? 0 + : 1; +#else + return -ENOTSUP; +#endif +} + +int zmk_physical_layouts_save_selected(void) { +#if IS_ENABLED(CONFIG_SETTINGS) + uint8_t val = (uint8_t)zmk_physical_layouts_get_selected(); + + return settings_save_one("physical_layouts/selected", &val, sizeof(val)); +#else + return -ENOTSUP; +#endif +} + +int zmk_physical_layouts_revert_selected(void) { return zmk_physical_layouts_select_initial(); } + +int zmk_physical_layouts_get_position_map(uint8_t source, uint8_t dest, uint32_t *map) { + if (source >= ARRAY_SIZE(layouts) || dest >= ARRAY_SIZE(layouts)) { + return -EINVAL; + } + + const struct zmk_physical_layout *src_layout = layouts[source]; + const struct zmk_physical_layout *dest_layout = layouts[dest]; + +#if HAVE_POS_MAP + const struct position_map_entry *src_pos_map = NULL; + const struct position_map_entry *dest_pos_map = NULL; + + for (int pm = 0; pm < ARRAY_SIZE(positions_maps); pm++) { + if (positions_maps[pm].layout == src_layout) { + src_pos_map = &positions_maps[pm]; + } + + if (positions_maps[pm].layout == dest_layout) { + dest_pos_map = &positions_maps[pm]; + } + } +#endif + + memset(map, UINT32_MAX, dest_layout->keys_len); + + for (int b = 0; b < dest_layout->keys_len; b++) { + bool found = false; + +#if HAVE_POS_MAP + if (src_pos_map && dest_pos_map) { + for (int m = 0; m < ZMK_POS_MAP_LEN; m++) { + if (dest_pos_map->positions[m] == b) { + map[b] = src_pos_map->positions[m]; + found = true; + break; + } + } + } +#endif + +#if !POS_MAP_COMPLETE + if (!found) { + const struct zmk_key_physical_attrs *key = &dest_layout->keys[b]; + for (int old_b = 0; old_b < src_layout->keys_len; old_b++) { + const struct zmk_key_physical_attrs *candidate_key = &src_layout->keys[old_b]; + + if (candidate_key->x == key->x && candidate_key->y == key->y) { + map[b] = old_b; + found = true; + break; + } + } + } +#endif + + if (!found || map[b] >= src_layout->keys_len) { + map[b] = UINT32_MAX; + } + } + + return dest_layout->keys_len; +} + +#if IS_ENABLED(CONFIG_SETTINGS) + +static int physical_layouts_handle_set(const char *name, size_t len, settings_read_cb read_cb, + void *cb_arg) { + const char *next; + + if (settings_name_steq(name, "selected", &next) && !next) { + if (len != sizeof(saved_selected_index)) { + return -EINVAL; + } + + int err = read_cb(cb_arg, &saved_selected_index, len); + if (err <= 0) { + LOG_ERR("Failed to handle selected physical dest_layout from settings (err %d)", err); + return err; + } + + return zmk_physical_layouts_select(saved_selected_index); + } + + return 0; +}; + +SETTINGS_STATIC_HANDLER_DEFINE(physical_layouts, "physical_layouts", NULL, + physical_layouts_handle_set, NULL, NULL); + +#endif // IS_ENABLED(CONFIG_SETTINGS) + +static int zmk_physical_layouts_init(void) { + k_work_init(&msg_processor.work, zmk_physical_layouts_kscan_process_msgq); + +#if IS_ENABLED(CONFIG_PM_DEVICE) + for (int l = 0; l < ARRAY_SIZE(layouts); l++) { + const struct zmk_physical_layout *pl = layouts[l]; + if (pl->kscan) { + if (pm_device_wakeup_is_capable(pl->kscan)) { + pm_device_wakeup_enable(pl->kscan, true); + } + } + } +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + + return zmk_physical_layouts_select_initial(); +} + +SYS_INIT(zmk_physical_layouts_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 5bf1ef25..3453fb44 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -221,6 +221,10 @@ static int rgb_settings_set(const char *name, size_t len, settings_read_cb read_ rc = read_cb(cb_arg, &state, sizeof(state)); if (rc >= 0) { + if (state.on) { + k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); + } + return 0; } @@ -230,7 +234,7 @@ static int rgb_settings_set(const char *name, size_t len, settings_read_cb read_ return -ENOENT; } -struct settings_handler rgb_conf = {.name = "rgb/underglow", .h_set = rgb_settings_set}; +SETTINGS_STATIC_HANDLER_DEFINE(rgb_underglow, "rgb/underglow", NULL, rgb_settings_set, NULL, NULL); static void zmk_rgb_underglow_save_state_work(struct k_work *_work) { settings_save_one("rgb/underglow/state", &state, sizeof(state)); @@ -262,17 +266,7 @@ static int zmk_rgb_underglow_init(void) { }; #if IS_ENABLED(CONFIG_SETTINGS) - settings_subsys_init(); - - int err = settings_register(&rgb_conf); - if (err) { - LOG_ERR("Failed to register the ext_power settings handler (err %d)", err); - return err; - } - k_work_init_delayable(&underglow_save_work, zmk_rgb_underglow_save_state_work); - - settings_load_subtree("rgb/underglow"); #endif #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) diff --git a/app/src/split/bluetooth/Kconfig b/app/src/split/bluetooth/Kconfig index 4da50528..7f362fde 100644 --- a/app/src/split/bluetooth/Kconfig +++ b/app/src/split/bluetooth/Kconfig @@ -76,7 +76,7 @@ if !ZMK_SPLIT_ROLE_CENTRAL config ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE int "BLE split peripheral notify thread stack size" - default 650 + default 756 config ZMK_SPLIT_BLE_PERIPHERAL_PRIORITY int "BLE split peripheral notify thread priority" diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index ee21a12f..0f4cd78b 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -865,13 +866,34 @@ int zmk_split_bt_update_hid_indicator(zmk_hid_indicators_t indicators) { #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) +static int finish_init() { + return IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) ? 0 : start_scanning(); +} + +#if IS_ENABLED(CONFIG_SETTINGS) + +static int central_ble_handle_set(const char *name, size_t len, settings_read_cb read_cb, + void *cb_arg) { + return 0; +} + +static struct settings_handler ble_central_settings_handler = { + .name = "ble_central", .h_set = central_ble_handle_set, .h_commit = finish_init}; + +#endif // IS_ENABLED(CONFIG_SETTINGS) + static int zmk_split_bt_central_init(void) { k_work_queue_start(&split_central_split_run_q, split_central_split_run_q_stack, K_THREAD_STACK_SIZEOF(split_central_split_run_q_stack), CONFIG_ZMK_BLE_THREAD_PRIORITY, NULL); bt_conn_cb_register(&conn_callbacks); - return IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) ? 0 : start_scanning(); +#if IS_ENABLED(CONFIG_SETTINGS) + settings_register(&ble_central_settings_handler); + return 0; +#else + return finish_init(); +#endif // IS_ENABLED(CONFIG_SETTINGS) } SYS_INIT(zmk_split_bt_central_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY); diff --git a/app/src/split/bluetooth/peripheral.c b/app/src/split/bluetooth/peripheral.c index 6ce82d0a..5a12e0fc 100644 --- a/app/src/split/bluetooth/peripheral.c +++ b/app/src/split/bluetooth/peripheral.c @@ -146,21 +146,7 @@ bool zmk_split_bt_peripheral_is_connected(void) { return is_connected; } bool zmk_split_bt_peripheral_is_bonded(void) { return is_bonded; } -static int zmk_peripheral_ble_init(void) { - int err = bt_enable(NULL); - - if (err) { - LOG_ERR("BLUETOOTH FAILED (%d)", err); - return err; - } - -#if IS_ENABLED(CONFIG_SETTINGS) - settings_subsys_init(); - - settings_load_subtree("ble"); - settings_load_subtree("bt"); -#endif - +static int zmk_peripheral_ble_complete_startup(void) { #if IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) LOG_WRN("Clearing all existing BLE bond information from the keyboard"); @@ -176,4 +162,35 @@ static int zmk_peripheral_ble_init(void) { return 0; } +#if IS_ENABLED(CONFIG_SETTINGS) + +static int peripheral_ble_handle_set(const char *name, size_t len, settings_read_cb read_cb, + void *cb_arg) { + return 0; +} + +static struct settings_handler ble_peripheral_settings_handler = { + .name = "ble_peripheral", + .h_set = peripheral_ble_handle_set, + .h_commit = zmk_peripheral_ble_complete_startup}; + +#endif // IS_ENABLED(CONFIG_SETTINGS) + +static int zmk_peripheral_ble_init(void) { + int err = bt_enable(NULL); + + if (err) { + LOG_ERR("BLUETOOTH FAILED (%d)", err); + return err; + } + +#if IS_ENABLED(CONFIG_SETTINGS) + settings_register(&ble_peripheral_settings_handler); +#else + zmk_peripheral_ble_complete_startup(); +#endif + + return 0; +} + SYS_INIT(zmk_peripheral_ble_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY); diff --git a/app/src/usb_hid.c b/app/src/usb_hid.c index cd3ef920..9db10952 100644 --- a/app/src/usb_hid.c +++ b/app/src/usb_hid.c @@ -195,4 +195,4 @@ static int zmk_usb_hid_init(void) { return 0; } -SYS_INIT(zmk_usb_hid_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); +SYS_INIT(zmk_usb_hid_init, APPLICATION, CONFIG_ZMK_USB_HID_INIT_PRIORITY); diff --git a/docs/docs/config/system.md b/docs/docs/config/system.md index 27923453..5d63ca52 100644 --- a/docs/docs/config/system.md +++ b/docs/docs/config/system.md @@ -29,10 +29,11 @@ Making changes to any of the settings in this section modifies the HID report de ::: -| Config | Type | Description | Default | -| ------------------------------------- | ---- | -------------------------------------------------------------- | ------- | -| `CONFIG_ZMK_HID_INDICATORS` | bool | Enable receipt of HID/LED indicator state from connected hosts | n | -| `CONFIG_ZMK_HID_CONSUMER_REPORT_SIZE` | int | Number of consumer keys simultaneously reportable | 6 | +| Config | Type | Description | Default | +| -------------------------------------------- | ---- | ---------------------------------------------------------------- | ------- | +| `CONFIG_ZMK_HID_INDICATORS` | bool | Enable receipt of HID/LED indicator state from connected hosts | n | +| `CONFIG_ZMK_HID_CONSUMER_REPORT_SIZE` | int | Number of consumer keys simultaneously reportable | 6 | +| `CONFIG_ZMK_HID_SEPARATE_MOD_RELEASE_REPORT` | bool | Send modifier release event **after** non-modifier release event | n | Exactly zero or one of the following options may be set to `y`. The first is used if none are set. diff --git a/docs/docs/development/new-shield.mdx b/docs/docs/development/new-shield.mdx index d48e0d1d..60299abf 100644 --- a/docs/docs/development/new-shield.mdx +++ b/docs/docs/development/new-shield.mdx @@ -19,7 +19,8 @@ The high level steps are: - Create a new shield directory. - Add the base Kconfig files. - Add the shield overlay file to define the KSCAN driver for detecting key press/release. -- (Optional) Add the matrix transform for mapping KSCAN row/column values to sane key positions. This is needed for non-rectangular keyboards, or where the underlying row/column pin arrangement does not map one to one with logical locations on the keyboard. +- Add the matrix transform for mapping KSCAN row/column values to key positions in the keymap. +- Add a physical layout definition to select the matrix transform and KSCAN instance. - Add a default keymap, which users can override in their own configs as needed. - Add a `.zmk.yml` metadata file to document the high level details of your shield, and the features it supports. - Update the `build.yaml` file from the repository template to have some sample builds of the firmware to test. @@ -318,7 +319,7 @@ The shared configuration in `my_awesome_split_board.conf` is only applied when y -## (Optional) Matrix Transform +## Matrix Transform Internally ZMK translates all row/column events into "key position" events to maintain a consistent model that works no matter what any possible GPIO matrix may look like for a certain keyboard. This is particularly helpful when: @@ -328,15 +329,7 @@ Internally ZMK translates all row/column events into "key position" events to ma A "key position" is the numeric index (zero-based) of a given key, which identifies the logical key location as perceived by the end user. All _keymap_ mappings actually bind behaviors to _key positions_, not to row/column values. -_Without_ a matrix transform, that intentionally map each key position to the row/column pair that position corresponds to, the default equation to determine that is: - -```c -($row * NUMBER_OF_COLUMNS) + $column -``` - -Which effectively amounts to numbering the key positions by traversing each row from top to bottom and assigning numerically incrementing key positions. - -Whenever that default key position mapping is insufficient, the `.overlay` file should _also_ include a matrix transform. +The `.overlay` must include a matrix transform that defines this mapping from row/column values to key positions. Here is an example for the [nice60](https://github.com/Nicell/nice60), which uses an efficient 8x8 GPIO matrix, and uses a transform: @@ -344,11 +337,6 @@ Here is an example for the [nice60](https://github.com/Nicell/nice60), which use #include / { - chosen { - zmk,kscan = &kscan0; - zmk,matrix-transform = &default_transform; - }; - /* define kscan node with label `kscan0`... */ default_transform: keymap_transform_0 { @@ -377,10 +365,61 @@ Some important things to note: - The `#include ` is critical. The `RC` macro is used to generate the internal storage in the matrix transform, and is actually replaced by a C preprocessor before the final devicetree is compiled into ZMK. - `RC(row, column)` is placed sequentially to define what row and column values that position corresponds to. -- If you have a keyboard with options for `2u` keys in certain positions, or break away portions, it is a good idea to set the chosen `zmk,matrix-transform` to the default arrangement, and include _other_ possible matrix transform nodes in the devicetree that users can select in their user config by overriding the chosen node. +- If you have a keyboard with options for `2u` keys in certain positions, ANSI vs. ISO layouts, or break away portions, define one matrix transform for each possible arrangement to be used in the physical layouts. This will allow the users to select the right layout in their keymap files. See the [matrix transform section](../config/kscan.md#matrix-transform) in the Keyboard Scan configuration documentation for details and more examples of matrix transforms. +## Physical Layout + +The physical layout is the top level entity that aggregates all details about a certain possible layout for a keyboard: the matrix transform that defines the set of key positions and what row/column they correspond to, what kscan driver is used for that layout, etc. +For keyboards that support multiple layouts, setting a `chosen` node to a defined physical layout in your keymap will allow selecting the specific layout that you've built. + +A physical layout is very basic, e.g.: + +``` +/ { + default_layout: default_layout { + compatible = "zmk,physical-layout"; + display-name = "Default Layout"; + transform = <&default_transform>; + kscan = <&kscan0>; + }; +}; +``` + +When supporting multiple layouts, define the multiple layout nodes and then set a `chosen` for the default: + +``` +/ { + chosen { + zmk,physical-layout = &default_layout; + ... + }; + + default_layout: default_layout { + compatible = "zmk,physical-layout"; + display-name = "Default Layout"; + transform = <&default_transform>; + kscan = <&kscan0>; + }; + + alt_layout: alt_layout { + compatible = "zmk,physical-layout"; + display-name = "Alternate Layout"; + transform = <&alt_transform>; + kscan = <&alt_kscan0>; + }; +}; +``` + +This way, users can select a different layout by overriding the `zmk,physical-layout` chosen node in their keymap file. + +:::note +Some keyboards use different GPIO pins for different layouts, and need different kscan nodes created for each layout. +However, if all of your physical layouts use the same `kscan` node under the hood, you can skip setting the `kscan` property on each +layout and instead assign the `zmk,kscan` chosen node to your single kscan instance. +::: + ## Default Keymap Each keyboard should provide a default keymap to be used when building the firmware, which can be overridden and customized by user configs. For "shield keyboards", this should be placed in the `boards/shields//.keymap` file. The keymap is configured as an additional devicetree overlay that includes the following: diff --git a/docs/docs/development/usb-logging.mdx b/docs/docs/development/usb-logging.mdx index cb9508a2..b7c3d233 100644 --- a/docs/docs/development/usb-logging.mdx +++ b/docs/docs/development/usb-logging.mdx @@ -18,26 +18,33 @@ It is recommended to only enable logging when needed, and not leaving it on by d ::: -## Kconfig +## USB Logging Snippet -The `CONFIG_ZMK_USB_LOGGING` Kconfig enables USB logging. This can be set at the keyboard level, typically in the `config/.conf` -file if you are using a [user config repository](user-setup.mdx). It can also be enabled at the ZMK level using the `app/prj.conf` file, or other -search locations described in the [configuration overview](config/index.md#config-file-locations). +The `zmk-usb-logging` snippet is used to enable logging. + +If using GitHub Actions to build your firmware, enabling logging +requires adding a `snippet: zmk-usb-logging` to your `build.yaml` file for any build you want logging enabled, e.g. + +```yaml +--- +include: + - board: nice_nano_v2 + shield: corne_left + snippet: zmk-usb-logging +``` + +When building locally, the `-S`/`--snippet` flag can be passed to `west build` to enable the snippet, e.g. + +```sh +west build -b nice_nano_v2 -S zmk-usb-logging -- -DSHIELD="corne_left" +``` + +### Additional Config Logging can be further configured using Kconfig described in [the Zephyr documentation](https://docs.zephyrproject.org/3.5.0/services/logging/index.html). For instance, setting `CONFIG_LOG_PROCESS_THREAD_STARTUP_DELAY_MS` to a large value such as `8000` might help catch issues that happen near keyboard boot, before you can connect to view the logs. -:::note -In Github Actions, you can check the ` Kconfig file` step output to verify the options above have been enabled -for you successfully. -::: - -```ini -# Turn on logging, and set ZMK logging to debug output -CONFIG_ZMK_USB_LOGGING=y -``` - ## Viewing Logs After flashing the updated ZMK image, the board should expose a USB CDC ACM device that you can connect to and view the logs. @@ -89,27 +96,29 @@ From there, you should see the various log messages from ZMK and Zephyr, dependi Standard boards such as the nice!nano and Seeeduino XIAO family have the necessary configuration for logging already added, however if you are developing your own standalone board you may wish to add the ability to use USB logging in the future. -To add USB logging to a board you need to define the USB CDC ACM device that the serial output gets piped to, as well as adding the console in the `chosen` node inside `.dts`. +To do so, you need to follow the upstream Zephyr [`cdc-acm-console` snippet requirements](https://docs.zephyrproject.org/3.5.0/snippets/cdc-acm-console/README.html#requirements) steps. -Inside the USB device (`&usbd`), add the CDC ACM node: +Usually, this just requires ensuring that the USB node has been tagged with the `zephyr_udc0` label, e.g. ```dts -&usbd { +zephyr_udc0: &usbd { status = "okay"; - cdc_acm_uart: cdc_acm_uart { - compatible = "zephyr,cdc-acm-uart"; - }; }; ``` -Then you can add the `zephyr,console` binding in the `chosen` node: +## Enabling Logging on Older Boards -```dts -/ { - chosen { - ... - zephyr,console = &cdc_acm_uart; - }; - ... -}; +Previously, enabling logging required setting the `CONFIG_ZMK_USB_LOGGING` Kconfig symbol. If for whatever reason +a custom board definition does not support the new `zmk-usb-logging` snippet, you can try setting this symbol at the keyboard level, typically in the `config/.conf` +file if you are using a [user config repository](user-setup.mdx). It can also be enabled at the ZMK level using the `app/prj.conf` file, or other +search locations described in the [configuration overview](config/index.md#config-file-locations). + +:::note +In Github Actions, you can check the ` Kconfig file` step output to verify the options above have been enabled +for you successfully. +::: + +```ini +# Turn on logging, and set ZMK logging to debug output +CONFIG_ZMK_USB_LOGGING=y ``` diff --git a/docs/docs/features/soft-off.md b/docs/docs/features/soft-off.md index 7018afa0..207bb13f 100644 --- a/docs/docs/features/soft-off.md +++ b/docs/docs/features/soft-off.md @@ -121,6 +121,7 @@ With that in place, the kscan sideband behavior will wrap the new driver: compatible = "zmk,kscan-sideband-behaviors"; kscan = <&soft_off_direct_scan>; + auto-enable; wakeup-source; soft_off { diff --git a/docs/docs/troubleshooting/connection-issues.mdx b/docs/docs/troubleshooting/connection-issues.mdx index 59a6a208..5fdd1c83 100644 --- a/docs/docs/troubleshooting/connection-issues.mdx +++ b/docs/docs/troubleshooting/connection-issues.mdx @@ -61,8 +61,8 @@ Save the file, commit the changes and push them to GitHub. Download the new firm -1. [Open the GitHub `Actions` tab and select the `Build` workflow](https://github.com/zmkfirmware/zmk/actions?query=workflow%3ABuild+branch%3Amain+event%3Apush). -1. Find one of the 'results' for which the core-coverage job was successfully run, indicated by a green checkmark in the core-coverage bubble like the image example below. +1. [Open the `Build` workflow](https://github.com/zmkfirmware/zmk/actions/workflows/build.yml?query=event%3Apush+branch%3Amain+is%3Asuccess) from the `Actions` tab of the ZMK GitHub repository. +1. Find one of the results for which the `core-coverage` job ran successfully, indicated by a green checkmark in the "core-coverage" bubble like the image example below. 1. From the next page under "Artifacts", download and unzip the `-settings_reset-zmk` zip file for the UF2 image. | ![Successful core-coverage Job](../../docs/assets/troubleshooting/splitpairing/corecoverage.png) |