Merge remote-tracking branch 'upstream/main' into split_battery_service

This commit is contained in:
Gabor Hornyak 2022-05-24 14:24:56 +00:00
commit efe06502c5
48 changed files with 1501 additions and 468 deletions

View file

@ -18,6 +18,11 @@ on:
default: "bin"
required: false
type: string
artifact_name:
description: 'Artifact output file name'
default: 'firmware'
required: false
type: string
jobs:
matrix:
@ -32,9 +37,10 @@ jobs:
- name: Install yaml2json
run: python3 -m pip install remarshal
- id: set-matrix
name: Fetch Build Matrix
- name: Fetch Build Matrix
id: set-matrix
run: |
set -x
matrix=$(yaml2json ${{ inputs.build_matrix_path }} | jq -c .)
yaml2json ${{ inputs.build_matrix_path }}
echo "::set-output name=matrix::${matrix}"
@ -52,7 +58,9 @@ jobs:
- name: Prepare variables
id: variables
run: |
if [ -n "${{ matrix.shield }}" ]; then
set -x
if [ -n "${{ matrix.shield }}" ]
then
EXTRA_CMAKE_ARGS="-DSHIELD=${{ matrix.shield }}"
ARTIFACT_NAME="${{ matrix.shield }}-${{ matrix.board }}-zmk"
DISPLAY_NAME="${{ matrix.shield }} - ${{ matrix.board }}"
@ -70,7 +78,7 @@ jobs:
uses: actions/checkout@v2
- name: Cache west modules
uses: actions/cache@v3.0.1
uses: actions/cache@v3.0.2
continue-on-error: true
env:
cache-name: cache-zephyr-${{ steps.variables.outputs.zephyr-version }}-modules
@ -98,13 +106,15 @@ jobs:
- name: West Build (${{ steps.variables.outputs.display-name }})
run: |
set -x
west build -s zmk/app -b ${{ matrix.board }} -- -DZMK_CONFIG=${GITHUB_WORKSPACE}/${{ inputs.config_path }} ${{ steps.variables.outputs.extra-cmake-args }} ${{ matrix.cmake-args }}
- name: ${{ steps.variables.outputs.display-name }} Kconfig file
run: cat build/zephyr/.config | grep -v "^#" | grep -v "^$" | sort
run: grep -v -e "^#" -e "^$" build/zephyr/.config | sort
- name: Rename artifacts
run: |
set -x
mkdir build/artifacts
if [ -f build/zephyr/zmk.uf2 ]
then
@ -117,5 +127,5 @@ jobs:
- name: Archive (${{ steps.variables.outputs.display-name }})
uses: actions/upload-artifact@v2
with:
name: firmware
name: ${{ inputs.artifact_name }}
path: build/artifacts

View file

@ -26,7 +26,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Cache west modules
uses: actions/cache@v2
uses: actions/cache@v3.0.2
env:
cache-name: cache-zephyr-modules
with:

View file

@ -38,7 +38,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Cache west modules
uses: actions/cache@v2
uses: actions/cache@v3.0.2
env:
cache-name: cache-zephyr-modules
with:

View file

@ -26,25 +26,19 @@ target_sources(app PRIVATE src/stdlib.c)
target_sources(app PRIVATE src/activity.c)
target_sources(app PRIVATE src/kscan.c)
target_sources(app PRIVATE src/matrix_transform.c)
target_sources(app PRIVATE src/hid.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)
target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/ext_power_generic.c)
target_sources(app PRIVATE src/events/activity_state_changed.c)
target_sources(app PRIVATE src/events/position_state_changed.c)
target_sources(app PRIVATE src/events/layer_state_changed.c)
target_sources(app PRIVATE src/events/keycode_state_changed.c)
target_sources(app PRIVATE src/events/modifiers_state_changed.c)
target_sources(app PRIVATE src/events/endpoint_selection_changed.c)
target_sources(app PRIVATE src/events/sensor_event.c)
target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/events/wpm_state_changed.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/ble_active_profile_changed.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/battery_state_changed.c)
target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/events/usb_conn_state_changed.c)
target_sources(app PRIVATE src/behaviors/behavior_reset.c)
target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/behaviors/behavior_ext_power.c)
if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
target_sources(app PRIVATE src/hid.c)
target_sources(app PRIVATE src/behaviors/behavior_key_press.c)
target_sources(app PRIVATE src/behaviors/behavior_hold_tap.c)
target_sources(app PRIVATE src/behaviors/behavior_sticky_key.c)
@ -63,28 +57,48 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
target_sources(app PRIVATE src/combo.c)
target_sources(app PRIVATE src/behavior_queue.c)
target_sources(app PRIVATE src/conditional_layer.c)
target_sources(app PRIVATE src/endpoints.c)
target_sources(app PRIVATE src/events/endpoint_selection_changed.c)
target_sources(app PRIVATE src/hid_listener.c)
target_sources(app PRIVATE src/keymap.c)
target_sources(app PRIVATE src/events/layer_state_changed.c)
target_sources(app PRIVATE src/events/modifiers_state_changed.c)
target_sources(app PRIVATE src/events/keycode_state_changed.c)
if (CONFIG_ZMK_BLE)
target_sources(app PRIVATE src/events/ble_active_profile_changed.c)
target_sources(app PRIVATE src/behaviors/behavior_bt.c)
target_sources(app PRIVATE src/ble.c)
target_sources(app PRIVATE src/hog.c)
if (CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
target_sources(app PRIVATE src/battery_split.c)
else()
target_sources(app PRIVATE src/battery.c)
endif()
endif()
endif()
target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_rgb_underglow.c)
target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/behaviors/behavior_backlight.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/behaviors/behavior_bt.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/ble.c)
if (CONFIG_ZMK_SPLIT_BLE AND (NOT CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL))
target_sources(app PRIVATE src/split_listener.c)
target_sources(app PRIVATE src/split/bluetooth/service.c)
target_sources(app PRIVATE src/battery.c)
endif()
if (CONFIG_ZMK_SPLIT_BLE AND CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
target_sources(app PRIVATE src/split/bluetooth/central.c)
target_sources(app PRIVATE src/battery_split.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/battery_state_changed.c)
if (CONFIG_ZMK_SPLIT_BLE)
if (NOT CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
target_sources(app PRIVATE src/split_listener.c)
target_sources(app PRIVATE src/split/bluetooth/service.c)
target_sources(app PRIVATE src/split/bluetooth/peripheral.c)
target_sources(app PRIVATE src/events/split_peripheral_status_changed.c)
endif()
if (CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
target_sources(app PRIVATE src/split/bluetooth/central.c)
endif()
endif()
target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/usb.c)
target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_hid.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/hog.c)
target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c)
target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/backlight.c)
target_sources(app PRIVATE src/endpoints.c)
target_sources(app PRIVATE src/hid_listener.c)
target_sources(app PRIVATE src/main.c)
add_subdirectory(src/display/)

View file

@ -523,6 +523,11 @@ config ZMK_SETTINGS_SAVE_DEBOUNCE
#SETTINGS
endif
config ZMK_BATTERY_REPORT_INTERVAL
depends on ZMK_BLE
int "Battery level report interval in seconds"
default 60
#Advanced
endmenu

View file

@ -72,7 +72,7 @@
&spi2 {
status = "okay";
pinctrl-0 = <&spi2_sck_pb13 &spi2_miso_pb14 &spi2_mosi_pb15>;
pinctrl-0 = <&spi2_sck_pb13 &spi2_mosi_pb15>;
pinctrl-names = "default";
led_strip: ws2812@0 {

View file

@ -38,7 +38,7 @@
vbatt: vbatt {
compatible = "zmk,battery-voltage-divider";
label = "BATTERY";
io-channels = <&adc 2>;
io-channels = <&adc 1>;
output-ohms = <10000000>;
full-ohms = <(10000000 + 4000000)>;
};

View file

@ -6,3 +6,10 @@ CONFIG_UART_INTERRUPT_DRIVEN=n
CONFIG_ZMK_USB=y
CONFIG_ZMK_BLE=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

View file

@ -4,83 +4,297 @@
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_kscan_gpio_direct
#include "debounce.h"
#include <device.h>
#include <drivers/kscan.h>
#include <devicetree.h>
#include <drivers/gpio.h>
#include <drivers/kscan.h>
#include <kernel.h>
#include <logging/log.h>
#include <sys/util.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
struct kscan_gpio_item_config {
char *label;
gpio_pin_t pin;
gpio_flags_t flags;
};
#define DT_DRV_COMPAT zmk_kscan_gpio_direct
union work_reference {
struct k_work_delayable delayed;
struct k_work direct;
};
#if CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS >= 0
#define INST_DEBOUNCE_PRESS_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS
#else
#define INST_DEBOUNCE_PRESS_MS(n) \
DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_press_ms))
#endif
struct kscan_gpio_config {
uint8_t num_of_inputs;
uint8_t debounce_period;
struct kscan_gpio_item_config inputs[];
};
#if CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS >= 0
#define INST_DEBOUNCE_RELEASE_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS
#else
#define INST_DEBOUNCE_RELEASE_MS(n) \
DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_release_ms))
#endif
struct kscan_gpio_data {
#if defined(CONFIG_ZMK_KSCAN_DIRECT_POLLING)
struct k_timer poll_timer;
#endif /* defined(CONFIG_ZMK_KSCAN_DIRECT_POLLING) */
kscan_callback_t callback;
union work_reference work;
#define USE_POLLING IS_ENABLED(CONFIG_ZMK_KSCAN_DIRECT_POLLING)
#define USE_INTERRUPTS (!USE_POLLING)
#define COND_INTERRUPTS(code) COND_CODE_1(CONFIG_ZMK_KSCAN_DIRECT_POLLING, (), code)
#define COND_POLL_OR_INTERRUPTS(pollcode, intcode) \
COND_CODE_1(CONFIG_ZMK_KSCAN_DIRECT_POLLING, pollcode, intcode)
#define INST_INPUTS_LEN(n) DT_INST_PROP_LEN(n, input_gpios)
#define KSCAN_DIRECT_INPUT_CFG_INIT(idx, inst_idx) \
GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), input_gpios, idx),
struct kscan_direct_irq_callback {
const struct device *dev;
uint32_t pin_state;
const struct device *inputs[];
};
static const struct device **kscan_gpio_input_devices(const struct device *dev) {
struct kscan_gpio_data *data = dev->data;
return data->inputs;
}
static const struct kscan_gpio_item_config *kscan_gpio_input_configs(const struct device *dev) {
const struct kscan_gpio_config *cfg = dev->config;
return cfg->inputs;
}
static void kscan_gpio_direct_queue_read(union work_reference *work, uint8_t debounce_period) {
if (debounce_period > 0) {
k_work_reschedule(&work->delayed, K_MSEC(debounce_period));
} else {
k_work_submit(&work->direct);
}
}
#if !defined(CONFIG_ZMK_KSCAN_DIRECT_POLLING)
struct kscan_gpio_irq_callback {
const struct device *dev;
union work_reference *work;
uint8_t debounce_period;
struct gpio_callback callback;
};
static int kscan_gpio_config_interrupts(const struct device *dev, gpio_flags_t flags) {
const struct kscan_gpio_config *cfg = dev->config;
const struct device **devices = kscan_gpio_input_devices(dev);
const struct kscan_gpio_item_config *configs = kscan_gpio_input_configs(dev);
struct kscan_direct_data {
const struct device *dev;
kscan_callback_t callback;
struct k_work_delayable work;
#if USE_INTERRUPTS
/** Array of length config->inputs.len */
struct kscan_direct_irq_callback *irqs;
#endif
/** Timestamp of the current or scheduled scan. */
int64_t scan_time;
/** Current state of the inputs as an array of length config->inputs.len */
struct debounce_state *pin_state;
};
for (int i = 0; i < cfg->num_of_inputs; i++) {
const struct device *dev = devices[i];
const struct kscan_gpio_item_config *cfg = &configs[i];
struct kscan_gpio_list {
const struct gpio_dt_spec *gpios;
size_t len;
};
int err = gpio_pin_interrupt_configure(dev, cfg->pin, flags);
/** Define a kscan_gpio_list from a compile-time GPIO array. */
#define KSCAN_GPIO_LIST(gpio_array) \
((struct kscan_gpio_list){.gpios = gpio_array, .len = ARRAY_SIZE(gpio_array)})
struct kscan_direct_config {
struct kscan_gpio_list inputs;
struct debounce_config debounce_config;
int32_t debounce_scan_period_ms;
int32_t poll_period_ms;
bool toggle_mode;
};
#if USE_INTERRUPTS
static int kscan_direct_interrupt_configure(const struct device *dev, const gpio_flags_t flags) {
const struct kscan_direct_config *config = dev->config;
for (int i = 0; i < config->inputs.len; i++) {
const struct gpio_dt_spec *gpio = &config->inputs.gpios[i];
int err = gpio_pin_interrupt_configure_dt(gpio, flags);
if (err) {
LOG_ERR("Unable to configure interrupt for pin %u on %s", gpio->pin, gpio->port->name);
return err;
}
}
return 0;
}
#endif
#if USE_INTERRUPTS
static int kscan_direct_interrupt_enable(const struct device *dev) {
return kscan_direct_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE);
}
#endif
#if USE_INTERRUPTS
static int kscan_direct_interrupt_disable(const struct device *dev) {
return kscan_direct_interrupt_configure(dev, GPIO_INT_DISABLE);
}
#endif
#if USE_INTERRUPTS
static void kscan_direct_irq_callback_handler(const struct device *port, struct gpio_callback *cb,
const gpio_port_pins_t pin) {
struct kscan_direct_irq_callback *irq_data =
CONTAINER_OF(cb, struct kscan_direct_irq_callback, callback);
struct kscan_direct_data *data = irq_data->dev->data;
// Disable our interrupts temporarily to avoid re-entry while we scan.
kscan_direct_interrupt_disable(data->dev);
data->scan_time = k_uptime_get();
k_work_reschedule(&data->work, K_NO_WAIT);
}
#endif
static gpio_flags_t kscan_gpio_get_extra_flags(const struct gpio_dt_spec *gpio, bool active) {
if (!active) {
return ((BIT(0) & gpio->dt_flags) ? GPIO_PULL_UP : GPIO_PULL_DOWN);
}
return 0;
}
static int kscan_inputs_set_flags(const struct kscan_gpio_list *inputs,
const struct gpio_dt_spec *active_gpio) {
gpio_flags_t extra_flags;
for (int i = 0; i < inputs->len; i++) {
extra_flags = GPIO_INPUT | kscan_gpio_get_extra_flags(&inputs->gpios[i],
&inputs->gpios[i] == active_gpio);
LOG_DBG("Extra flags equal to: %d", extra_flags);
int err = gpio_pin_configure_dt(&inputs->gpios[i], extra_flags);
if (err) {
LOG_ERR("Unable to configure flags on pin %d on %s", inputs->gpios[i].pin,
inputs->gpios[i].port->name);
return err;
}
}
return 0;
}
static void kscan_direct_read_continue(const struct device *dev) {
const struct kscan_direct_config *config = dev->config;
struct kscan_direct_data *data = dev->data;
data->scan_time += config->debounce_scan_period_ms;
k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
}
static void kscan_direct_read_end(const struct device *dev) {
#if USE_INTERRUPTS
// Return to waiting for an interrupt.
kscan_direct_interrupt_enable(dev);
#else
struct kscan_direct_data *data = dev->data;
const struct kscan_direct_config *config = dev->config;
data->scan_time += config->poll_period_ms;
// Return to polling slowly.
k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
#endif
}
static int kscan_direct_read(const struct device *dev) {
struct kscan_direct_data *data = dev->data;
const struct kscan_direct_config *config = dev->config;
// Read the inputs.
for (int i = 0; i < config->inputs.len; i++) {
const struct gpio_dt_spec *gpio = &config->inputs.gpios[i];
const bool active = gpio_pin_get_dt(gpio);
debounce_update(&data->pin_state[i], active, config->debounce_scan_period_ms,
&config->debounce_config);
}
// Process the new state.
bool continue_scan = false;
for (int i = 0; i < config->inputs.len; i++) {
struct debounce_state *state = &data->pin_state[i];
if (debounce_get_changed(state)) {
const bool pressed = debounce_is_pressed(state);
LOG_DBG("Sending event at 0,%i state %s", i, pressed ? "on" : "off");
data->callback(dev, 0, i, pressed);
if (config->toggle_mode && pressed) {
kscan_inputs_set_flags(&config->inputs, &config->inputs.gpios[i]);
}
}
continue_scan = continue_scan || debounce_is_active(state);
}
if (continue_scan) {
// At least one key is pressed or the debouncer has not yet decided if
// it is pressed. Poll quickly until everything is released.
kscan_direct_read_continue(dev);
} else {
// All keys are released. Return to normal.
kscan_direct_read_end(dev);
}
return 0;
}
static void kscan_direct_work_handler(struct k_work *work) {
struct k_work_delayable *dwork = CONTAINER_OF(work, struct k_work_delayable, work);
struct kscan_direct_data *data = CONTAINER_OF(dwork, struct kscan_direct_data, work);
kscan_direct_read(data->dev);
}
static int kscan_direct_configure(const struct device *dev, kscan_callback_t callback) {
struct kscan_direct_data *data = dev->data;
if (!callback) {
return -EINVAL;
}
data->callback = callback;
return 0;
}
static int kscan_direct_enable(const struct device *dev) {
struct kscan_direct_data *data = dev->data;
data->scan_time = k_uptime_get();
// Read will automatically start interrupts/polling once done.
return kscan_direct_read(dev);
}
static int kscan_direct_disable(const struct device *dev) {
struct kscan_direct_data *data = dev->data;
k_work_cancel_delayable(&data->work);
#if USE_INTERRUPTS
return kscan_direct_interrupt_disable(dev);
#else
return 0;
#endif
}
static int kscan_direct_init_input_inst(const struct device *dev, const struct gpio_dt_spec *gpio,
const int index, bool toggle_mode) {
if (!device_is_ready(gpio->port)) {
LOG_ERR("GPIO is not ready: %s", gpio->port->name);
return -ENODEV;
}
int err = gpio_pin_configure_dt(
gpio, GPIO_INPUT | (toggle_mode ? kscan_gpio_get_extra_flags(gpio, false) : 0));
if (err) {
LOG_ERR("Unable to configure pin %u on %s for input", gpio->pin, gpio->port->name);
return err;
}
LOG_DBG("Configured pin %u on %s for input", gpio->pin, gpio->port->name);
#if USE_INTERRUPTS
struct kscan_direct_data *data = dev->data;
struct kscan_direct_irq_callback *irq = &data->irqs[index];
irq->dev = dev;
gpio_init_callback(&irq->callback, kscan_direct_irq_callback_handler, BIT(gpio->pin));
err = gpio_add_callback(gpio->port, &irq->callback);
if (err) {
LOG_ERR("Error adding the callback to the input device: %i", err);
return err;
}
#endif
return 0;
}
static int kscan_direct_init_inputs(const struct device *dev) {
const struct kscan_direct_config *config = dev->config;
for (int i = 0; i < config->inputs.len; i++) {
const struct gpio_dt_spec *gpio = &config->inputs.gpios[i];
int err = kscan_direct_init_input_inst(dev, gpio, i, config->toggle_mode);
if (err) {
LOG_ERR("Unable to enable direct GPIO interrupt");
return err;
}
}
@ -88,155 +302,55 @@ static int kscan_gpio_config_interrupts(const struct device *dev, gpio_flags_t f
return 0;
}
static int kscan_gpio_direct_enable(const struct device *dev) {
return kscan_gpio_config_interrupts(dev, GPIO_INT_LEVEL_ACTIVE);
}
static int kscan_gpio_direct_disable(const struct device *dev) {
return kscan_gpio_config_interrupts(dev, GPIO_INT_DISABLE);
}
static int kscan_direct_init(const struct device *dev) {
struct kscan_direct_data *data = dev->data;
static void kscan_gpio_irq_callback_handler(const struct device *dev, struct gpio_callback *cb,
gpio_port_pins_t pin) {
struct kscan_gpio_irq_callback *data =
CONTAINER_OF(cb, struct kscan_gpio_irq_callback, callback);
data->dev = dev;
kscan_gpio_direct_disable(data->dev);
kscan_gpio_direct_queue_read(data->work, data->debounce_period);
}
kscan_direct_init_inputs(dev);
#else /* !defined(CONFIG_ZMK_KSCAN_DIRECT_POLLING) */
static void kscan_gpio_timer_handler(struct k_timer *timer) {
struct kscan_gpio_data *data = CONTAINER_OF(timer, struct kscan_gpio_data, poll_timer);
kscan_gpio_direct_queue_read(&data->work, 0);
}
static int kscan_gpio_direct_enable(const struct device *dev) {
struct kscan_gpio_data *data = dev->data;
k_timer_start(&data->poll_timer, K_MSEC(10), K_MSEC(10));
return 0;
}
static int kscan_gpio_direct_disable(const struct device *dev) {
struct kscan_gpio_data *data = dev->data;
k_timer_stop(&data->poll_timer);
return 0;
}
#endif /* defined(CONFIG_ZMK_KSCAN_DIRECT_POLLING) */
static int kscan_gpio_direct_configure(const struct device *dev, kscan_callback_t callback) {
struct kscan_gpio_data *data = dev->data;
if (!callback) {
return -EINVAL;
}
data->callback = callback;
return 0;
}
static int kscan_gpio_read(const struct device *dev) {
struct kscan_gpio_data *data = dev->data;
const struct kscan_gpio_config *cfg = dev->config;
uint32_t read_state = data->pin_state;
bool submit_follow_up_read = false;
for (int i = 0; i < cfg->num_of_inputs; i++) {
const struct device *in_dev = kscan_gpio_input_devices(dev)[i];
const struct kscan_gpio_item_config *in_cfg = &kscan_gpio_input_configs(dev)[i];
WRITE_BIT(read_state, i, gpio_pin_get(in_dev, in_cfg->pin) > 0);
}
for (int i = 0; i < cfg->num_of_inputs; i++) {
bool prev_pressed = BIT(i) & data->pin_state;
bool pressed = (BIT(i) & read_state) != 0;
submit_follow_up_read = (submit_follow_up_read || pressed);
if (pressed != prev_pressed) {
LOG_DBG("Sending event at %d,%d state %s", 0, i, (pressed ? "on" : "off"));
WRITE_BIT(data->pin_state, i, pressed);
data->callback(dev, 0, i, pressed);
}
}
#if !defined(CONFIG_ZMK_KSCAN_DIRECT_POLLING)
if (submit_follow_up_read) {
kscan_gpio_direct_queue_read(&data->work, cfg->debounce_period);
} else {
kscan_gpio_direct_enable(dev);
}
#endif
k_work_init_delayable(&data->work, kscan_direct_work_handler);
return 0;
}
static void kscan_gpio_work_handler(struct k_work *work) {
struct kscan_gpio_data *data = CONTAINER_OF(work, struct kscan_gpio_data, work);
kscan_gpio_read(data->dev);
}
static const struct kscan_driver_api gpio_driver_api = {
.config = kscan_gpio_direct_configure,
.enable_callback = kscan_gpio_direct_enable,
.disable_callback = kscan_gpio_direct_disable,
static const struct kscan_driver_api kscan_direct_api = {
.config = kscan_direct_configure,
.enable_callback = kscan_direct_enable,
.disable_callback = kscan_direct_disable,
};
#define KSCAN_DIRECT_INPUT_ITEM(i, n) \
{ \
.label = DT_INST_GPIO_LABEL_BY_IDX(n, input_gpios, i), \
.pin = DT_INST_GPIO_PIN_BY_IDX(n, input_gpios, i), \
.flags = DT_INST_GPIO_FLAGS_BY_IDX(n, input_gpios, i), \
},
#define KSCAN_DIRECT_INIT(n) \
BUILD_ASSERT(INST_DEBOUNCE_PRESS_MS(n) <= DEBOUNCE_COUNTER_MAX, \
"ZMK_KSCAN_DEBOUNCE_PRESS_MS or debounce-press-ms is too large"); \
BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \
"ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \
\
static const struct gpio_dt_spec kscan_direct_inputs_##n[] = { \
UTIL_LISTIFY(INST_INPUTS_LEN(n), KSCAN_DIRECT_INPUT_CFG_INIT, n)}; \
\
static struct debounce_state kscan_direct_state_##n[INST_INPUTS_LEN(n)]; \
\
COND_INTERRUPTS( \
(static struct kscan_direct_irq_callback kscan_direct_irqs_##n[INST_INPUTS_LEN(n)];)) \
\
static struct kscan_direct_data kscan_direct_data_##n = { \
.pin_state = kscan_direct_state_##n, COND_INTERRUPTS((.irqs = kscan_direct_irqs_##n, ))}; \
\
static struct kscan_direct_config kscan_direct_config_##n = { \
.inputs = KSCAN_GPIO_LIST(kscan_direct_inputs_##n), \
.debounce_config = \
{ \
.debounce_press_ms = INST_DEBOUNCE_PRESS_MS(n), \
.debounce_release_ms = INST_DEBOUNCE_RELEASE_MS(n), \
}, \
.debounce_scan_period_ms = DT_INST_PROP(n, debounce_scan_period_ms), \
.poll_period_ms = DT_INST_PROP(n, poll_period_ms), \
.toggle_mode = DT_INST_PROP(n, toggle_mode), \
}; \
\
DEVICE_DT_INST_DEFINE(n, &kscan_direct_init, NULL, &kscan_direct_data_##n, \
&kscan_direct_config_##n, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY, \
&kscan_direct_api);
#define INST_INPUT_LEN(n) DT_INST_PROP_LEN(n, input_gpios)
#define GPIO_INST_INIT(n) \
COND_CODE_0(IS_ENABLED(CONFIG_ZMK_KSCAN_DIRECT_POLLING), \
(static struct kscan_gpio_irq_callback irq_callbacks_##n[INST_INPUT_LEN(n)];), ()) \
static struct kscan_gpio_data kscan_gpio_data_##n = { \
.inputs = {[INST_INPUT_LEN(n) - 1] = NULL}}; \
static int kscan_gpio_init_##n(const struct device *dev) { \
struct kscan_gpio_data *data = dev->data; \
const struct kscan_gpio_config *cfg = dev->config; \
int err; \
const struct device **input_devices = kscan_gpio_input_devices(dev); \
for (int i = 0; i < cfg->num_of_inputs; i++) { \
const struct kscan_gpio_item_config *in_cfg = &kscan_gpio_input_configs(dev)[i]; \
input_devices[i] = device_get_binding(in_cfg->label); \
if (!input_devices[i]) { \
LOG_ERR("Unable to find input GPIO device"); \
return -EINVAL; \
} \
err = gpio_pin_configure(input_devices[i], in_cfg->pin, GPIO_INPUT | in_cfg->flags); \
if (err) { \
LOG_ERR("Unable to configure pin %d on %s for input", in_cfg->pin, in_cfg->label); \
return err; \
} \
COND_CODE_0( \
IS_ENABLED(CONFIG_ZMK_KSCAN_DIRECT_POLLING), \
(irq_callbacks_##n[i].work = &data->work; irq_callbacks_##n[i].dev = dev; \
irq_callbacks_##n[i].debounce_period = cfg->debounce_period; \
gpio_init_callback(&irq_callbacks_##n[i].callback, \
kscan_gpio_irq_callback_handler, BIT(in_cfg->pin)); \
err = gpio_add_callback(input_devices[i], &irq_callbacks_##n[i].callback); \
if (err) { \
LOG_ERR("Error adding the callback to the column device"); \
return err; \
}), \
()) \
} \
data->dev = dev; \
COND_CODE_1(IS_ENABLED(CONFIG_ZMK_KSCAN_DIRECT_POLLING), \
(k_timer_init(&data->poll_timer, kscan_gpio_timer_handler, NULL);), ()) \
if (cfg->debounce_period > 0) { \
k_work_init_delayable(&data->work.delayed, kscan_gpio_work_handler); \
} else { \
k_work_init(&data->work.direct, kscan_gpio_work_handler); \
} \
return 0; \
} \
static const struct kscan_gpio_config kscan_gpio_config_##n = { \
.inputs = {UTIL_LISTIFY(INST_INPUT_LEN(n), KSCAN_DIRECT_INPUT_ITEM, n)}, \
.num_of_inputs = INST_INPUT_LEN(n), \
.debounce_period = DT_INST_PROP(n, debounce_period)}; \
DEVICE_DT_INST_DEFINE(n, kscan_gpio_init_##n, NULL, &kscan_gpio_data_##n, \
&kscan_gpio_config_##n, POST_KERNEL, CONFIG_ZMK_KSCAN_INIT_PRIORITY, \
&gpio_driver_api);
DT_INST_FOREACH_STATUS_OKAY(GPIO_INST_INIT)
DT_INST_FOREACH_STATUS_OKAY(KSCAN_DIRECT_INIT);

View file

@ -418,46 +418,46 @@ static const struct kscan_driver_api kscan_matrix_api = {
.disable_callback = kscan_matrix_disable,
};
#define KSCAN_MATRIX_INIT(index) \
BUILD_ASSERT(INST_DEBOUNCE_PRESS_MS(index) <= DEBOUNCE_COUNTER_MAX, \
#define KSCAN_MATRIX_INIT(n) \
BUILD_ASSERT(INST_DEBOUNCE_PRESS_MS(n) <= DEBOUNCE_COUNTER_MAX, \
"ZMK_KSCAN_DEBOUNCE_PRESS_MS or debounce-press-ms is too large"); \
BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(index) <= DEBOUNCE_COUNTER_MAX, \
BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \
"ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \
\
static const struct gpio_dt_spec kscan_matrix_rows_##index[] = { \
UTIL_LISTIFY(INST_ROWS_LEN(index), KSCAN_GPIO_ROW_CFG_INIT, index)}; \
static const struct gpio_dt_spec kscan_matrix_rows_##n[] = { \
UTIL_LISTIFY(INST_ROWS_LEN(n), KSCAN_GPIO_ROW_CFG_INIT, n)}; \
\
static const struct gpio_dt_spec kscan_matrix_cols_##index[] = { \
UTIL_LISTIFY(INST_COLS_LEN(index), KSCAN_GPIO_COL_CFG_INIT, index)}; \
static const struct gpio_dt_spec kscan_matrix_cols_##n[] = { \
UTIL_LISTIFY(INST_COLS_LEN(n), KSCAN_GPIO_COL_CFG_INIT, n)}; \
\
static struct debounce_state kscan_matrix_state_##index[INST_MATRIX_LEN(index)]; \
static struct debounce_state kscan_matrix_state_##n[INST_MATRIX_LEN(n)]; \
\
COND_INTERRUPTS((static struct kscan_matrix_irq_callback \
kscan_matrix_irqs_##index[INST_INPUTS_LEN(index)];)) \
COND_INTERRUPTS( \
(static struct kscan_matrix_irq_callback kscan_matrix_irqs_##n[INST_INPUTS_LEN(n)];)) \
\
static struct kscan_matrix_data kscan_matrix_data_##index = { \
.matrix_state = kscan_matrix_state_##index, \
COND_INTERRUPTS((.irqs = kscan_matrix_irqs_##index, ))}; \
static struct kscan_matrix_data kscan_matrix_data_##n = { \
.matrix_state = kscan_matrix_state_##n, \
COND_INTERRUPTS((.irqs = kscan_matrix_irqs_##n, ))}; \
\
static struct kscan_matrix_config kscan_matrix_config_##index = { \
.rows = KSCAN_GPIO_LIST(kscan_matrix_rows_##index), \
.cols = KSCAN_GPIO_LIST(kscan_matrix_cols_##index), \
.inputs = KSCAN_GPIO_LIST( \
COND_DIODE_DIR(index, (kscan_matrix_cols_##index), (kscan_matrix_rows_##index))), \
.outputs = KSCAN_GPIO_LIST( \
COND_DIODE_DIR(index, (kscan_matrix_rows_##index), (kscan_matrix_cols_##index))), \
static struct kscan_matrix_config kscan_matrix_config_##n = { \
.rows = KSCAN_GPIO_LIST(kscan_matrix_rows_##n), \
.cols = KSCAN_GPIO_LIST(kscan_matrix_cols_##n), \
.inputs = \
KSCAN_GPIO_LIST(COND_DIODE_DIR(n, (kscan_matrix_cols_##n), (kscan_matrix_rows_##n))), \
.outputs = \
KSCAN_GPIO_LIST(COND_DIODE_DIR(n, (kscan_matrix_rows_##n), (kscan_matrix_cols_##n))), \
.debounce_config = \
{ \
.debounce_press_ms = INST_DEBOUNCE_PRESS_MS(index), \
.debounce_release_ms = INST_DEBOUNCE_RELEASE_MS(index), \
.debounce_press_ms = INST_DEBOUNCE_PRESS_MS(n), \
.debounce_release_ms = INST_DEBOUNCE_RELEASE_MS(n), \
}, \
.debounce_scan_period_ms = DT_INST_PROP(index, debounce_scan_period_ms), \
.poll_period_ms = DT_INST_PROP(index, poll_period_ms), \
.diode_direction = INST_DIODE_DIR(index), \
.debounce_scan_period_ms = DT_INST_PROP(n, debounce_scan_period_ms), \
.poll_period_ms = DT_INST_PROP(n, poll_period_ms), \
.diode_direction = INST_DIODE_DIR(n), \
}; \
\
DEVICE_DT_INST_DEFINE(index, &kscan_matrix_init, NULL, &kscan_matrix_data_##index, \
&kscan_matrix_config_##index, APPLICATION, \
CONFIG_APPLICATION_INIT_PRIORITY, &kscan_matrix_api);
DEVICE_DT_INST_DEFINE(n, &kscan_matrix_init, NULL, &kscan_matrix_data_##n, \
&kscan_matrix_config_##n, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY, \
&kscan_matrix_api);
DT_INST_FOREACH_STATUS_OKAY(KSCAN_MATRIX_INIT);

View file

@ -12,5 +12,26 @@ properties:
type: phandle-array
required: true
debounce-period:
type: int
required: false
deprecated: true
description: Deprecated. Use debounce-press-ms and debounce-release-ms instead.
debounce-press-ms:
type: int
default: 5
description: Debounce time for key press in milliseconds. Use 0 for eager debouncing.
debounce-release-ms:
type: int
default: 5
description: Debounce time for key release in milliseconds.
debounce-scan-period-ms:
type: int
default: 1
description: Time between reads in milliseconds when any key is pressed.
poll-period-ms:
type: int
default: 10
description: Time between reads in milliseconds when no key is pressed and ZMK_KSCAN_DIRECT_POLLING is enabled.
toggle-mode:
type: boolean
description: Enable toggle-switch mode.

View file

@ -930,7 +930,7 @@
/* Consumer Closed Caption */
#define C_CAPTIONS (ZMK_HID_USAGE(HID_USAGE_CONSUMER, HID_USAGE_CONSUMER_CLOSED_CAPTION))
#define C_SUBTITILES (C_CAPTIONS)
#define C_SUBTITLES (C_CAPTIONS)
/* Consumer Snapshot */
#define C_SNAPSHOT (ZMK_HID_USAGE(HID_USAGE_CONSUMER, HID_USAGE_CONSUMER_SNAPSHOT))

View file

@ -0,0 +1,19 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <lvgl.h>
#include <kernel.h>
struct zmk_widget_peripheral_status {
sys_snode_t node;
lv_obj_t *obj;
};
int zmk_widget_peripheral_status_init(struct zmk_widget_peripheral_status *widget,
lv_obj_t *parent);
lv_obj_t *zmk_widget_peripheral_status_obj(struct zmk_widget_peripheral_status *widget);

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr.h>
#include <zmk/event_manager.h>
struct zmk_split_peripheral_status_changed {
bool connected;
};
ZMK_EVENT_DECLARE(zmk_split_peripheral_status_changed);

View file

@ -0,0 +1,9 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
bool zmk_split_bt_peripheral_is_connected(void);

View file

@ -68,9 +68,9 @@ void activity_work_handler(struct k_work *work) {
int32_t inactive_time = current - activity_last_uptime;
#if IS_ENABLED(CONFIG_ZMK_SLEEP)
if (inactive_time > MAX_SLEEP_MS && !is_usb_power_present()) {
// Put devices in low power mode before sleeping
// Put devices in suspend power mode before sleeping
set_state(ZMK_ACTIVITY_SLEEP);
pm_power_state_set((struct pm_state_info){PM_STATE_SOFT_OFF, 0, 0});
pm_power_state_force(0U, (struct pm_state_info){PM_STATE_SOFT_OFF, 0, 0});
} else
#endif /* IS_ENABLED(CONFIG_ZMK_SLEEP) */
if (inactive_time > MAX_IDLE_MS) {

View file

@ -104,7 +104,7 @@ static int zmk_battery_init(const struct device *_arg) {
return rc;
}
k_timer_start(&battery_timer, K_MINUTES(1), K_MINUTES(1));
k_timer_start(&battery_timer, K_MINUTES(1), K_SECONDS(CONFIG_ZMK_BATTERY_REPORT_INTERVAL));
return 0;
}

View file

@ -36,14 +36,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/event_manager.h>
#include <zmk/events/ble_active_profile_changed.h>
#define IS_HOST_PERIPHERAL \
(!IS_ENABLED(CONFIG_ZMK_SPLIT) || IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL))
#define IS_SPLIT_PERIPHERAL \
(IS_ENABLED(CONFIG_ZMK_SPLIT) && !IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL))
#define DO_PASSKEY_ENTRY (IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) && !IS_SPLIT_PERIPHERAL)
#if DO_PASSKEY_ENTRY
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
#include <zmk/events/keycode_state_changed.h>
#define PASSKEY_DIGITS 6
@ -52,7 +45,7 @@ static struct bt_conn *auth_passkey_entry_conn;
static uint8_t passkey_entries[PASSKEY_DIGITS] = {};
static uint8_t passkey_digit = 0;
#endif
#endif /* IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) */
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
#define PROFILE_COUNT (CONFIG_BT_MAX_PAIRED - 1)
@ -81,20 +74,12 @@ static uint8_t active_profile;
BUILD_ASSERT(DEVICE_NAME_LEN <= 16, "ERROR: BLE device name is too long. Max length: 16");
static const struct bt_data zmk_ble_ad[] = {
#if IS_HOST_PERIPHERAL
BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
BT_DATA_BYTES(BT_DATA_GAP_APPEARANCE, 0xC1, 0x03),
#endif
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_SOME,
#if IS_HOST_PERIPHERAL
0x12, 0x18, /* HID Service */
#endif
0x0f, 0x18 /* Battery Service */
BT_DATA_BYTES(BT_DATA_UUID16_SOME, 0x12, 0x18, /* HID Service */
0x0f, 0x18 /* Battery Service */
),
#if IS_SPLIT_PERIPHERAL
BT_DATA_BYTES(BT_DATA_UUID128_ALL, ZMK_SPLIT_BT_SERVICE_UUID)
#endif
};
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
@ -473,7 +458,7 @@ static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey) {
}
*/
#if DO_PASSKEY_ENTRY
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
static void auth_passkey_entry(struct bt_conn *conn) {
char addr[BT_ADDR_LE_STR_LEN];
@ -492,7 +477,7 @@ static void auth_cancel(struct bt_conn *conn) {
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
#if DO_PASSKEY_ENTRY
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
if (auth_passkey_entry_conn) {
bt_conn_unref(auth_passkey_entry_conn);
auth_passkey_entry_conn = NULL;
@ -504,7 +489,6 @@ static void auth_cancel(struct bt_conn *conn) {
LOG_DBG("Pairing cancelled: %s", log_strdup(addr));
}
#if IS_HOST_PERIPHERAL
static enum bt_security_err auth_pairing_accept(struct bt_conn *conn,
const struct bt_conn_pairing_feat *const feat) {
struct bt_conn_info info;
@ -518,7 +502,6 @@ static enum bt_security_err auth_pairing_accept(struct bt_conn *conn,
return BT_SECURITY_ERR_SUCCESS;
};
#endif /* IS_HOST_PERIPHERAL */
static void auth_pairing_complete(struct bt_conn *conn, bool bonded) {
struct bt_conn_info info;
@ -533,26 +516,22 @@ static void auth_pairing_complete(struct bt_conn *conn, bool bonded) {
return;
}
#if IS_HOST_PERIPHERAL
if (!zmk_ble_active_profile_is_open()) {
LOG_ERR("Pairing completed but current profile is not open: %s", log_strdup(addr));
bt_unpair(BT_ID_DEFAULT, dst);
return;
}
#endif /* IS_HOST_PERIPHERAL */
set_profile_address(active_profile, dst);
update_advertising();
};
static struct bt_conn_auth_cb zmk_ble_auth_cb_display = {
#if IS_HOST_PERIPHERAL
.pairing_accept = auth_pairing_accept,
#endif /* IS_HOST_PERIPHERAL */
.pairing_complete = auth_pairing_complete,
// .passkey_display = auth_passkey_display,
#if DO_PASSKEY_ENTRY
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
.passkey_entry = auth_passkey_entry,
#endif
.cancel = auth_cancel,
@ -595,9 +574,7 @@ static int zmk_ble_init(const struct device *_arg) {
#if IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START)
LOG_WRN("Clearing all existing BLE bond information from the keyboard");
for (int i = 0; i < 10; i++) {
bt_unpair(i, NULL);
}
bt_unpair(BT_ID_DEFAULT, NULL);
for (int i = 0; i < ZMK_BLE_PROFILE_COUNT; i++) {
char setting_name[15];
@ -618,21 +595,7 @@ static int zmk_ble_init(const struct device *_arg) {
return 0;
}
int zmk_ble_unpair_all() {
int resp = 0;
for (int i = BT_ID_DEFAULT; i < CONFIG_BT_ID_MAX; i++) {
int err = bt_unpair(BT_ID_DEFAULT, NULL);
if (err) {
resp = err;
LOG_ERR("Failed to unpair devices (err %d)", err);
}
}
return resp;
};
#if DO_PASSKEY_ENTRY
#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,
const zmk_key_t zero, uint32_t *value) {
@ -705,6 +668,6 @@ static int zmk_ble_listener(const zmk_event_t *eh) {
ZMK_LISTENER(zmk_ble, zmk_ble_listener);
ZMK_SUBSCRIPTION(zmk_ble, zmk_keycode_state_changed);
#endif /* DO_PASSKEY_ENTRY */
#endif /* IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) */
SYS_INIT(zmk_ble_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY);

View file

@ -5,6 +5,7 @@
*/
#include <zmk/display/widgets/output_status.h>
#include <zmk/display/widgets/peripheral_status.h>
#include <zmk/display/widgets/battery_status.h>
#include <zmk/display/widgets/layer_status.h>
#include <zmk/display/widgets/wpm_status.h>
@ -21,6 +22,10 @@ static struct zmk_widget_battery_status battery_status_widget;
static struct zmk_widget_output_status output_status_widget;
#endif
#if IS_ENABLED(CONFIG_ZMK_WIDGET_PERIPHERAL_STATUS)
static struct zmk_widget_peripheral_status peripheral_status_widget;
#endif
#if IS_ENABLED(CONFIG_ZMK_WIDGET_LAYER_STATUS)
static struct zmk_widget_layer_status layer_status_widget;
#endif
@ -46,6 +51,12 @@ lv_obj_t *zmk_display_status_screen() {
0);
#endif
#if IS_ENABLED(CONFIG_ZMK_WIDGET_PERIPHERAL_STATUS)
zmk_widget_peripheral_status_init(&peripheral_status_widget, screen);
lv_obj_align(zmk_widget_peripheral_status_obj(&peripheral_status_widget), NULL,
LV_ALIGN_IN_TOP_LEFT, 0, 0);
#endif
#if IS_ENABLED(CONFIG_ZMK_WIDGET_LAYER_STATUS)
zmk_widget_layer_status_init(&layer_status_widget, screen);
lv_obj_set_style_local_text_font(zmk_widget_layer_status_obj(&layer_status_widget),

View file

@ -3,5 +3,6 @@
target_sources_ifdef(CONFIG_ZMK_WIDGET_BATTERY_STATUS app PRIVATE battery_status.c)
target_sources_ifdef(CONFIG_ZMK_WIDGET_OUTPUT_STATUS app PRIVATE output_status.c)
target_sources_ifdef(CONFIG_ZMK_WIDGET_PERIPHERAL_STATUS app PRIVATE peripheral_status.c)
target_sources_ifdef(CONFIG_ZMK_WIDGET_LAYER_STATUS app PRIVATE layer_status.c)
target_sources_ifdef(CONFIG_ZMK_WIDGET_WPM_STATUS app PRIVATE wpm_status.c)

View file

@ -17,8 +17,14 @@ config ZMK_WIDGET_BATTERY_STATUS
config ZMK_WIDGET_OUTPUT_STATUS
bool "Widget for keyboard output status icons"
depends on BT
default y if BT
depends on BT && (!ZMK_SPLIT_BLE || ZMK_SPLIT_BLE_ROLE_CENTRAL)
default y if BT && (!ZMK_SPLIT_BLE || ZMK_SPLIT_BLE_ROLE_CENTRAL)
select LVGL_USE_LABEL
config ZMK_WIDGET_PERIPHERAL_STATUS
bool "Widget for split peripheral status icons"
depends on BT && ZMK_SPLIT_BLE && !ZMK_SPLIT_BLE_ROLE_CENTRAL
default y if BT && ZMK_SPLIT_BLE && !ZMK_SPLIT_BLE_ROLE_CENTRAL
select LVGL_USE_LABEL
config ZMK_WIDGET_WPM_STATUS

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <kernel.h>
#include <bluetooth/services/bas.h>
#include <logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/display.h>
#include <zmk/display/widgets/peripheral_status.h>
#include <zmk/event_manager.h>
#include <zmk/split/bluetooth/peripheral.h>
#include <zmk/events/split_peripheral_status_changed.h>
static sys_slist_t widgets = SYS_SLIST_STATIC_INIT(&widgets);
struct peripheral_status_state {
bool connected;
};
static struct peripheral_status_state get_state(const zmk_event_t *_eh) {
return (struct peripheral_status_state){.connected = zmk_split_bt_peripheral_is_connected()};
}
static void set_status_symbol(lv_obj_t *label, struct peripheral_status_state state) {
const char *text =
state.connected ? (LV_SYMBOL_WIFI " " LV_SYMBOL_OK) : (LV_SYMBOL_WIFI " " LV_SYMBOL_CLOSE);
LOG_DBG("connected? %s", state.connected ? "true" : "false");
lv_label_set_text(label, text);
}
static void output_status_update_cb(struct peripheral_status_state state) {
struct zmk_widget_peripheral_status *widget;
SYS_SLIST_FOR_EACH_CONTAINER(&widgets, widget, node) { set_status_symbol(widget->obj, state); }
}
ZMK_DISPLAY_WIDGET_LISTENER(widget_peripheral_status, struct peripheral_status_state,
output_status_update_cb, get_state)
ZMK_SUBSCRIPTION(widget_peripheral_status, zmk_split_peripheral_status_changed);
int zmk_widget_peripheral_status_init(struct zmk_widget_peripheral_status *widget,
lv_obj_t *parent) {
widget->obj = lv_label_create(parent, NULL);
lv_obj_set_size(widget->obj, 40, 15);
sys_slist_append(&widgets, &widget->node);
widget_peripheral_status_init();
return 0;
}
lv_obj_t *zmk_widget_peripheral_status_obj(struct zmk_widget_peripheral_status *widget) {
return widget->obj;
}

View file

@ -0,0 +1,10 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <kernel.h>
#include <zmk/events/split_peripheral_status_changed.h>
ZMK_EVENT_IMPL(zmk_split_peripheral_status_changed);

View file

@ -176,10 +176,10 @@ static int ext_power_generic_init(const struct device *dev) {
#ifdef CONFIG_PM_DEVICE
static int ext_power_generic_pm_action(const struct device *dev, enum pm_device_action action) {
switch (action) {
case PM_DEVICE_ACTION_TURN_ON:
case PM_DEVICE_ACTION_RESUME:
ext_power_generic_enable(dev);
return 0;
case PM_DEVICE_ACTION_TURN_OFF:
case PM_DEVICE_ACTION_SUSPEND:
ext_power_generic_disable(dev);
return 0;
default:

View file

@ -0,0 +1,128 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <device.h>
#include <init.h>
#include <errno.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <settings/settings.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/conn.h>
#include <bluetooth/hci.h>
#include <bluetooth/uuid.h>
#include <bluetooth/gatt.h>
#include <bluetooth/hci_err.h>
#if IS_ENABLED(CONFIG_SETTINGS)
#include <settings/settings.h>
#endif
#include <logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/event_manager.h>
#include <zmk/events/split_peripheral_status_changed.h>
#include <zmk/ble.h>
#include <zmk/split/bluetooth/uuid.h>
static const struct bt_data zmk_ble_ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_SOME, 0x0f, 0x18 /* Battery Service */
),
BT_DATA_BYTES(BT_DATA_UUID128_ALL, ZMK_SPLIT_BT_SERVICE_UUID)};
static bool is_connected = false;
static int start_advertising() {
return bt_le_adv_start(BT_LE_ADV_CONN, zmk_ble_ad, ARRAY_SIZE(zmk_ble_ad), NULL, 0);
};
static void connected(struct bt_conn *conn, uint8_t err) {
is_connected = (err == 0);
ZMK_EVENT_RAISE(new_zmk_split_peripheral_status_changed(
(struct zmk_split_peripheral_status_changed){.connected = is_connected}));
}
static void disconnected(struct bt_conn *conn, uint8_t reason) {
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_DBG("Disconnected from %s (reason 0x%02x)", log_strdup(addr), reason);
is_connected = false;
ZMK_EVENT_RAISE(new_zmk_split_peripheral_status_changed(
(struct zmk_split_peripheral_status_changed){.connected = is_connected}));
}
static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) {
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (!err) {
LOG_DBG("Security changed: %s level %u", log_strdup(addr), level);
} else {
LOG_ERR("Security failed: %s level %u err %d", log_strdup(addr), level, err);
}
}
static void le_param_updated(struct bt_conn *conn, uint16_t interval, uint16_t latency,
uint16_t timeout) {
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_DBG("%s: interval %d latency %d timeout %d", log_strdup(addr), interval, latency, timeout);
}
static struct bt_conn_cb conn_callbacks = {
.connected = connected,
.disconnected = disconnected,
.security_changed = security_changed,
.le_param_updated = le_param_updated,
};
bool zmk_split_bt_peripheral_is_connected() { return is_connected; }
static int zmk_peripheral_ble_init(const struct device *_arg) {
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
#if IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START)
LOG_WRN("Clearing all existing BLE bond information from the keyboard");
bt_unpair(BT_ID_DEFAULT, NULL);
#endif
bt_conn_cb_register(&conn_callbacks);
start_advertising();
return 0;
}
SYS_INIT(zmk_peripheral_ble_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY);

View file

@ -10,10 +10,10 @@ kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided hold-timer (balanced decision moment timer)
kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
ht_binding_pressed: 0 new undecided hold_tap

View file

@ -1,12 +1,12 @@
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided hold-timer (balanced decision moment timer)
kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_pressed: 1 new undecided hold_tap
ht_decide: 1 decided hold-timer (balanced decision moment timer)
kp_pressed: usage_page 0x07 keycode 0xe0 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
kp_released: usage_page 0x07 keycode 0xe0 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 1 cleaning up hold-tap

View file

@ -10,10 +10,10 @@ kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided hold-interrupt (hold-preferred decision moment other-key-down)
kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
ht_binding_pressed: 0 new undecided hold_tap

View file

@ -1,12 +1,12 @@
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided hold-timer (hold-preferred decision moment timer)
kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_pressed: 1 new undecided hold_tap
ht_decide: 1 decided hold-timer (hold-preferred decision moment timer)
kp_pressed: usage_page 0x07 keycode 0xe0 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
kp_released: usage_page 0x07 keycode 0xe0 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 1 cleaning up hold-tap

View file

@ -10,10 +10,10 @@ kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided hold-timer (tap-preferred decision moment timer)
kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
ht_binding_pressed: 0 new undecided hold_tap

View file

@ -1,12 +1,12 @@
ht_binding_pressed: 0 new undecided hold_tap
ht_decide: 0 decided hold-timer (tap-preferred decision moment timer)
kp_pressed: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_pressed: 1 new undecided hold_tap
ht_decide: 1 decided hold-timer (tap-preferred decision moment timer)
kp_pressed: usage_page 0x07 keycode 0xe0 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xe1 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
kp_released: usage_page 0x07 keycode 0xe0 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 1 cleaning up hold-tap

View file

@ -3,10 +3,10 @@ ht_decide: 0 decided tap (tap-unless-interrupted decision moment timer)
kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
ht_binding_pressed: 1 new undecided hold_tap
ht_decide: 1 decided tap (tap-unless-interrupted decision moment timer)
kp_pressed: usage_page 0x07 keycode 0x0d implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x0D implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 0 cleaning up hold-tap
kp_released: usage_page 0x07 keycode 0x0d implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x0D implicit_mods 0x00 explicit_mods 0x00
ht_binding_released: 1 cleaning up hold-tap

View file

@ -0,0 +1,479 @@
---
title: New Behavior
sidebar_label: New Behavior
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Overview
This document outlines how to develop a behavior for ZMK and prepare the changes for a pull request.
Behaviors are assigned to key positions and determine what happens when they are pressed and released. They are implemented in Zephyr as "devices": they consist of a devicetree binding file, which specifies the properties of the behavior, and a driver written in C code. This allows for the ability to create unique instances of these behaviors in [keymaps](../features/keymaps.md) or devicetree-source-include files (`.dtsi`). While instances of behaviors stored in keymaps are created by end-users for their personal needs, the instances that live in the .dtsi files are stored and documented in ZMK directly, which removes the need for end-users to set up common use-cases of these behaviors in their personal keymaps.
The general process for developing behaviors is:
1. [Create the behavior](#creating-the-behavior)
1. [Create the devicetree binding (`.yaml`)](#creating-the-devicetree-binding-yaml)
1. [Create the driver (`.c`)](#creating-the-driver-c)
1. [Update `app/CmakeLists.txt` to include the new driver](#updating-appcmakeliststxt-to-include-the-new-driver)
1. [Define common use-cases for the behavior (`.dtsi`) (Optional)](#defining-common-use-cases-for-the-behavior-dtsi-optional)
1. [Test changes locally](#testing-changes-locally)
1. [Document behavior functionality](#documenting-behavior-functionality)
1. [Create a pull request for review and inclusion into the ZMK sources](#submitting-a-pull-request)
:::info
Before developing new behaviors, developers should have a working knowledge of the Embedded Linux Devicetree.
The following resources are provided for those seeking further understanding:
- [Embedded Linux Wiki - Device Tree Usage](https://elinux.org/Device_Tree_Usage)
- [Zephyr Devicetree API](https://docs.zephyrproject.org/latest/build/dts/api/api.html)
- [Zephyr Device Driver Model](https://docs.zephyrproject.org/latest/kernel/drivers/index.html)
:::
## Creating the Behavior
### Creating the devicetree binding (`.yaml`)
The properties of the behavior are listed in the behavior's devicetree binding, which comes in the form of a `.yaml` file. Devicetree bindings are stored in the directory `app/dts/bindings/behaviors/` and are labelled in lowercase, beginning with the prefix `zmk,behavior-`, and ending with the behavior's name, using dashes to separate multiple words. For example, the directory for the hold-tap's devicetree binding would be located at `app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml`, which is shown below as a reference:
```yaml title="app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml"
# Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT
// highlight-next-line
description: Hold or Tap behavior
// highlight-next-line
compatible: "zmk,behavior-hold-tap"
// highlight-next-line
include: two_param.yaml
// highlight-next-line
properties:
bindings:
type: phandles
required: true
tapping-term-ms:
type: int
tapping_term_ms: # deprecated
type: int
quick-tap-ms:
type: int
default: -1
quick_tap_ms: # deprecated
type: int
flavor:
type: string
required: false
default: "hold-preferred"
enum:
- "hold-preferred"
- "balanced"
- "tap-preferred"
- "tap-unless-interrupted"
retro-tap:
type: boolean
hold-trigger-key-positions:
type: array
required: false
default: []
```
We see that the `.yaml` files used for new behaviors' devicetree bindings consist of the following properties:
#### `description`
A brief statement of what the behavior is. The value of this property is not seen by end-users; as such, the `description` value should be kept less than a sentence long, leaving explanations for end-users of how the behavior works for its documentation.
#### `compatible`
Allows ZMK to assign the correct driver to the behavior extracted from the keymap or `.dtsi`. The value of the `compatible` property is equal to the name of the [devicetree binding file](#creating-the-devicetree-binding-yaml) as a `string`.
As shown in the example above, `compatible: "zmk,behavior-hold-tap"` is the value of the `compatible` property of `zmk,behavior-hold-tap.yaml`.
#### `include`
Choose between `zero_param.yaml`, `one_param.yaml`, or `two_param.yaml` depending on how many additional parameters are required to complete the behavior's binding in a keymap. For example, we `include: two_param.yaml` in `zmk,behavior-hold-tap.yaml` because any user-defined or pre-defined instances of the hold-tap behavior take in two cells as inputs: one for the hold behavior and one for the tap behavior.
#### `properties` (Optional)
These are additional variables required to configure a particular instance of a behavior. `properties` can be of the following types:
- `path`
- `compound`
- `array`
- `string`
- `string-array`
- `boolean`
- `int`
- `uint8-array`
- `phandle`.
- `phandle-array`
- `phandles`
:::info
For more information on additional `properties`, refer to [Zephyr's documentation on Devicetree bindings](https://docs.zephyrproject.org/latest/build/dts/bindings.html#properties).
:::
### Creating the driver (`.c`)
:::info
Developing drivers for behaviors in ZMK makes extensive use of the Zephyr Devicetree API and Device Driver Model. Links to the Zephyr Project Documentation for both of these concepts can be found below:
- [Zephyr Devicetree API](https://docs.zephyrproject.org/latest/build/dts/api/api.html)
- [Zephyr Device Driver Model](https://docs.zephyrproject.org/latest/kernel/drivers/index.html)
:::
Driver files are stored in `app/src/behaviors/` and are labelled in lowercase, beginning with the prefix `behavior_`, and ending with the behavior's name, using underscores to separate multiple words. For example, the directory for the hold-tap's driver would be located at `app/src/behaviors/behavior_hold_tap.c`.
The code snippet below shows the essential components of a new driver.
```c
#define DT_DRV_COMPAT zmk_<behavior_name>
// Dependencies
#include <device.h>
#include <drivers/behavior.h>
#include <logging/log.h>
#include <zmk/behavior.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
// Instance-Unique Data Struct (Optional)
struct behavior_<behavior_name>_data {
bool example_data_param1;
bool example_data_param2;
bool example_data_param3;
};
// Instance-Unique Config Struct (Optional)
struct behavior_<behavior_name>_config {
bool example_config_param1;
bool example_config_param2;
bool example_config_param3;
};
// Initialization Function
static int <behavior_name>_init(const struct device *dev) {
return 0;
};
// API Structure
static const struct behavior_driver_api <behavior_name>_driver_api = {
};
DEVICE_DT_INST_DEFINE(0, // Instance Number (Equal to 0 for behaviors that don't require multiple instances,
// Equal to n for behaviors that do make use of multiple instances)
<behavior_name>_init, NULL, // Initialization Function, Power Management Device Pointer
&<behavior_name>_data, &<behavior_name>_config, // Behavior Data Pointer, Behavior Configuration Pointer (Both Optional)
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, // Initialization Level, Device Priority
&<behavior_name>_driver_api); // API Structure
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */
```
#### `DT_DRV_COMPAT`
Replace `zmk_<behavior_name>` in the `#define DT_DRV_COMPAT` statement with the name of your behavior. (e.g. `zmk_behavior_caps_word`)
#### Dependencies
The dependencies required for any ZMK behavior are:
- `device.h`: [Zephyr Device APIs](https://docs.zephyrproject.org/apidoc/latest/group__device__model.html)
- `drivers/behavior.h`: ZMK Behavior Functions (e.g. [`locality`](#api-structure), `behavior_keymap_binding_pressed`, `behavior_keymap_binding_released`, `behavior_sensor_keymap_binding_triggered`)
- `logging/log.h`: [Zephyr Logging APIs](https://docs.zephyrproject.org/latest/services/logging/index.html) (for more information on USB Logging in ZMK, see [USB Logging](usb-logging.md)).
- `zmk/behavior.h`: ZMK Behavior Information (e.g. parameters, position and timestamp of events)
- `return` values:
- `ZMK_BEHAVIOR_OPAQUE`: Used to terminate `on_<behavior_name>_binding_pressed` and `on_<behavior_name>_binding_released` functions that accept `(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event)` as parameters
- `ZMK_BEHAVIOR_TRANSPARENT`: Used in the `binding_pressed` and `binding_released` functions for the transparent (`&trans`) behavior
- `struct`s:
- `zmk_behavior_binding`: Stores the name of the behavior device (`char *behavior_dev`) as a `string` and up to two additional parameters (`uint32_t param1`, `uint32_t param2`)
- `zmk_behavior_binding_event`: Contains layer, position, and timestamp data for an active `zmk_behavior_binding`
Other common dependencies include `zmk/keymap.h`, which allows behaviors to access layer information and extract behavior bindings from keymaps, and `zmk/event_manager.h` which is detailed below.
##### ZMK Event Manager
Including `zmk/event_manager.h` is required for the following dependencies to function properly.
- `zmk/events/position_state_changed.h`: Position events' state (on/off), source, position, and timestamps
- `zmk/events/keycode_state_changed.h`: Keycode events' state (on/off), usage page, keycode value, modifiers, and timestamps
- `zmk/events/modifiers_state_changed.h`: Modifier events' state (on/off) and modifier value
Events can be used similarly to hardware interrupts, through the use of [listeners](#listeners-and-subscriptions).
###### Listeners and Subscriptions
The condensed form of lines 192-225 of the tap-dance driver, shown below, does an excellent job of showcasing the function of listeners and subscriptions with respect to the [ZMK Event Manager](#zmk-event-manager).
```c title="app/src/behaviors/behavior_tap_dance.c (Lines 192-197, 225)"
static int tap_dance_position_state_changed_listener(const zmk_event_t *eh);
ZMK_LISTENER(behavior_tap_dance, tap_dance_position_state_changed_listener);
ZMK_SUBSCRIPTION(behavior_tap_dance, zmk_position_state_changed);
static int tap_dance_position_state_changed_listener(const zmk_event_t *eh){
// Do stuff...
}
```
Listeners, defined by the `ZMK_LISTENER(mod, cb)` function, take in a listener name (`mod`) and a callback function (`cb`) as their parameters. On the other hand subscriptions are defined by the `ZMK_SUBSCRIPTION(mod, ev_type)`, and determine what kind of event (`ev_type`) should invoke the callback function from the listener. In the tap-dance example, this listener executes code depending on a `zmk_position_state_changed` event, or simply, a change in key position. Other types of ZMK events can be found as the name of the `struct` inside each of the files located at `app/include/zmk/events/<Event Type>.h`. All control paths in a listener should `return` one of the [`ZMK_EV_EVENT_*` values](#return-values), which are shown below.
###### `return` values:
- `ZMK_EV_EVENT_BUBBLE`: Keep propagating the event `struct` to the next listener.
- `ZMK_EV_EVENT_HANDLED`: Stop propagating the event `struct` to the next listener. The event manager still owns the `struct`'s memory, so it will be `free`d automatically. Do **not** free the memory in this function.
- `ZMK_EV_EVENT_CAPTURED`: Stop propagating the event `struct` to the next listener. The event `struct`'s memory is now owned by your code, so the event manager will not free the event `struct` memory. Make sure your code will release or free the event at some point in the future. (Use the [`ZMK_EVENT_*` macros](#macros) described below.)
###### Macros:
- `ZMK_EVENT_RAISE(ev)`: Start handling this event (`ev`) with the first registered event listener.
- `ZMK_EVENT_RAISE_AFTER(ev, mod)`: Start handling this event (`ev`) after the event is captured by the named [event listener](#listeners-and-subscriptions) (`mod`). The named event listener will be skipped as well.
- `ZMK_EVENT_RAISE_AT(ev, mod)`: Start handling this event (`ev`) at the named [event listener](#listeners-and-subscriptions) (`mod`). The named event listener is the first handler to be invoked.
- `ZMK_EVENT_RELEASE(ev)`: Continue handling this event (`ev`) at the next registered event listener.
- `ZMK_EVENT_FREE(ev)`: Free the memory associated with the event (`ev`).
#### `DEVICE_DT_INST_DEFINE`
:::info
For more information on this function, refer to [Zephyr's documentation on the Device Driver Model](https://docs.zephyrproject.org/latest/kernel/drivers/index.html#c.DEVICE_DT_INST_DEFINE).
:::
The example `DEVICE_DT_INST_DEFINE` call can be left as is with the first parameter, the instance number, equal to `0` for behaviors that only require a single instance (e.g. external power, backlighting, accessing layers). For behaviors that can have multiple instances (e.g. hold-taps, tap-dances, sticky-keys), `DEVICE_DT_INST_DEFINE` can be placed inside a `#define` statement, usually formatted as `#define <ABBREVIATED BEHAVIOR NAME>_INST(n)`, that sets up any [data pointers](#data-pointers-optional) and/or [configuration pointers](#configuration-pointers-optional) that are unique to each instance.
An example of this can be seen below, taking the `#define KP_INST(n)` from the hold-tap driver.
```c
#define KP_INST(n) \
static struct behavior_hold_tap_config behavior_hold_tap_config_##n = { \
.tapping_term_ms = DT_INST_PROP(n, tapping_term_ms), \
.hold_behavior_dev = DT_LABEL(DT_INST_PHANDLE_BY_IDX(n, bindings, 0)), \
.tap_behavior_dev = DT_LABEL(DT_INST_PHANDLE_BY_IDX(n, bindings, 1)), \
.quick_tap_ms = DT_INST_PROP(n, quick_tap_ms), \
.flavor = DT_ENUM_IDX(DT_DRV_INST(n), flavor), \
.retro_tap = DT_INST_PROP(n, retro_tap), \
.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), \
}; \
DEVICE_DT_INST_DEFINE(n, behavior_hold_tap_init, NULL, NULL, &behavior_hold_tap_config_##n, \
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
&behavior_hold_tap_driver_api);
DT_INST_FOREACH_STATUS_OKAY(KP_INST)
```
Note that in the hold-tap example, the instance number, `0`, has been replaced by `n`, signifying the unique `node_id` of each instance of a behavior. Furthermore, the DT_INST_FOREACH_STATUS_OKAY(KP_INST) macro iterates through each compatible, non-disabled devicetree node, creating and applying the proper values to any instance-specific configurations or data by invoking the KP_INST macro for each instance of the new behavior.
Behaviors also require the following parameters of `DEVICE_DT_INST_DEFINE` to be changed:
##### Initialization Function
Comes in the form `static int <behavior_name>_init(const struct device *dev)`. Initialization functions preconfigure any data, like resetting timers and position for hold-taps and tap-dances. All initialization functions `return 0;` once complete.
##### API Structure
Comes in the form `static const struct behavior_driver_api <behavior_name>_driver_api)`. Common items to include in the API Structure are:
- `.binding_pressed`: Used for behaviors that invoke an action on its keybind press. Set `.binding_pressed` equal to the function typically named [`on_<behavior_name>_binding_pressed`](#dependencies).
- `.binding_released`: Same as above, except for activating on keybind release events. Set `.binding_released` equal to the function typically named [`on_<behavior_name>_binding_released`](#dependencies).
- `.locality`: Defined in `<drivers/behavior.h>`. Describes how the behavior affects parts of a _split_ keyboard.
- `BEHAVIOR_LOCALITY_CENTRAL`: Behavior only affects the central half, which is the case for most keymap-related behavior.
- `BEHAVIOR_LOCALITY_EVENT_SOURCE`: Behavior affects only the central _or_ peripheral half depending on which side invoked the behavior binding, such as [reset behaviors](../behaviors/reset.md).
- `BEHAVIOR_LOCALITY_GLOBAL`: Behavior affects the entire keyboard, such as [external power](../behaviors/power.md) and lighting-related behaviors that need to be synchronized across halves.
:::note
For unibody keyboards, all locality values perform the same as `BEHAVIOR_LOCALITY_GLOBAL`.
:::
##### Data Pointers (Optional)
The data `struct` stores additional data required for **each new instance** of the behavior. Regardless of the instance number, `n`, `behavior_<behavior_name>_data_##n` is typically initialized as an empty `struct`. The data respective to each instance of the behavior can be accessed in functions like [`on_<behavior_name>_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event)`](#dependencies) by extracting the behavior device from the keybind like so:
```c
const struct device *dev = device_get_binding(binding->behavior_dev);
struct behavior_<behavior_name>_data *data = dev->data;
```
The variables stored inside the data `struct`, `data`, can be then modified as necessary.
The fourth cell of `DEVICE_DT_INST_DEFINE` can be set to `NULL` instead if instance-specific data is not required.
##### Configuration Pointers (Optional)
The configuration `struct` stores the properties declared from the behavior's `.yaml` for **each new instance** of the behavior. As seen in the `#define KP_INST(n)` of the hold-tap example, the configuration `struct`, `behavior_<behavior_name>_config_##n`, for each instance number, `n`, can be initialized using the [Zephyr Devicetree Instance-based APIs](https://docs.zephyrproject.org/latest/build/dts/api/api.html#instance-based-apis), which extract the values from the `properties` of each instance of the [devicetree binding](#creating-the-devicetree-binding-yaml) from a user's keymap or [predefined use-case `.dtsi` files](#defining-common-use-cases-for-the-behavior-dtsi-optional) stored in `app/dts/behaviors/`. We illustrate this further by comparing the [`#define KP_INST(n)` from the hold-tap driver](#device_dt_inst_define) and the [`properties` of the hold-tap devicetree binding.](#creating-the-devicetree-binding-yaml)
The fifth cell of `DEVICE_DT_INST_DEFINE` can be set to `NULL` instead if instance-specific configurations are not required.
:::caution
Remember that `.c` files should be formatted according to `clang-format` to ensure that checks run smoothly once the pull request is submitted.
:::
### Updating `app/CmakeLists.txt` to include the new driver
Most behavior drivers' are invoked according to the central half's [locality](#api-structure), and are therefore stored after the line `if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)` in the form, `target_sources(app PRIVATE src/behaviors/<behavior_name>.c)`, as shown below.
```txt title="app/CmakeLists.txt"
if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
target_sources(app PRIVATE src/behaviors/behavior_key_press.c)
target_sources(app PRIVATE src/behaviors/behavior_hold_tap.c)
target_sources(app PRIVATE src/behaviors/behavior_sticky_key.c)
target_sources(app PRIVATE src/behaviors/behavior_caps_word.c)
target_sources(app PRIVATE src/behaviors/behavior_key_repeat.c)
target_sources(app PRIVATE src/behaviors/behavior_momentary_layer.c)
target_sources(app PRIVATE src/behaviors/behavior_mod_morph.c)
target_sources(app PRIVATE src/behaviors/behavior_outputs.c)
target_sources(app PRIVATE src/behaviors/behavior_tap_dance.c)
target_sources(app PRIVATE src/behaviors/behavior_toggle_layer.c)
target_sources(app PRIVATE src/behaviors/behavior_to_layer.c)
target_sources(app PRIVATE src/behaviors/behavior_transparent.c)
target_sources(app PRIVATE src/behaviors/behavior_none.c)
target_sources(app PRIVATE src/behaviors/behavior_sensor_rotate_key_press.c)
target_sources(app PRIVATE src/combo.c)
target_sources(app PRIVATE src/conditional_layer.c)
target_sources(app PRIVATE src/keymap.c)
endif()
```
For behaviors that do not require central locality, the following options for updating `app/CmakeLists.txt` also exist:
- Behavior applies to unibody, or central or peripheral half of keyboard: place `target_sources(app PRIVATE <behavior_name>.c)` line _before_ `if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)`
- Behavior applies to _only_ central half of split keyboard: place `target_sources(app PRIVATE <behavior_name>.c)` after `if (CONFIG_ZMK_SPLIT_BLE AND CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)`
- Behavior applies to _only_ peripheral half of split keyboard: place `target_sources(app PRIVATE <behavior_name>.c)` after `if (CONFIG_ZMK_SPLIT_BLE AND (NOT CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL))`
- Behavior requires certain condition in a keyboard's `.conf` file to be met: use `target_sources_ifdef(CONFIG_<Configuration Requirement> app PRIVATE <behavior_name>.c)` instead of `target_sources(<behavior_name>.c)`
### Defining common use-cases for the behavior (`.dtsi`) (Optional)
`.dtsi` files, found in the directory `app/dts/behaviors/`, are only necessary for behaviors with more common use-cases. A common example is the mod-tap (`&mt`), which is a predefined type of hold-tap that takes a modifier key as the hold parameter and another key as the tap parameter.
For the purpose of this section, we will discuss the structure of `app/dts/behaviors/gresc.dtsi` below.
```dtsi title="app/dts/behaviors/gresc.dtsi"
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <dt-bindings/zmk/keys.h>
/ {
behaviors {
/omit-if-no-ref/ gresc: grave_escape {
compatible = "zmk,behavior-mod-morph";
label = "GRAVE_ESCAPE";
#binding-cells = <0>;
bindings = <&kp ESC>, <&kp GRAVE>;
mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>;
};
};
};
```
The format of a behavior's `.dtsi` file is identical to declaring an instance of the behavior in a user's keymap. The only major difference is that the value `/omit-if-no-ref/` should be placed adjacent to the name of the behavior, as seen in line 11 of the `gresc` example.
After creating the `.dtsi` from above, update `app/dts/behaviors.dtsi` to include your newly predefined behavior instance, making it accessible by the devicetree.
```dtsi title="app/dts/behaviors.dtsi"
#include <behaviors/key_press.dtsi>
#include <behaviors/transparent.dtsi>
#include <behaviors/none.dtsi>
#include <behaviors/mod_tap.dtsi>
#include <behaviors/layer_tap.dtsi>
#include <behaviors/gresc.dtsi>
#include <behaviors/sticky_key.dtsi>
#include <behaviors/momentary_layer.dtsi>
#include <behaviors/toggle_layer.dtsi>
#include <behaviors/to_layer.dtsi>
#include <behaviors/reset.dtsi>
#include <behaviors/sensor_rotate_key_press.dtsi>
#include <behaviors/rgb_underglow.dtsi>
#include <behaviors/bluetooth.dtsi>
#include <behaviors/ext_power.dtsi>
#include <behaviors/outputs.dtsi>
#include <behaviors/caps_word.dtsi>
#include <behaviors/key_repeat.dtsi>
#include <behaviors/backlight.dtsi>
#include <behaviors/macros.dtsi>
// highlight-next-line
#include <behaviors/new_behavior_instance.dtsi>
```
## Testing changes locally
Create a new folder in `app/tests/` to develop virtual test sets for all common use cases of the behavior. Behaviors should be tested thoroughly on both virtual testing environments using `west test` and real hardware.
:::note
Zephyr currently does not support logging over Bluetooth, so any use of the serial monitor for hardware testing must be done over hardware UART or USB virtual UART.
:::
:::info
- See [Tests](tests.md) for more information on how to create virtual test sets.
- For hardware-based testing, see [USB Logging](usb-logging.md).
:::
## Documenting behavior functionality
Consider the following prompts when writing documentation for new behaviors:
- What does it do? Describe some general use-cases for the behavior.
- Which properties included in the [devicetree binding](#creating-the-devicetree-binding-yaml) should be configured manually by the user? What do they do, and if applicable, what are their default values?
- What does an example implementation in a keymap look like? Include a code-snippet of the example implementation in the keymap file's `behaviors` node.
- Are there any [common use-cases of the behavior](#defining-common-use-cases-for-the-behavior-dtsi-optional)? Consider making a separate documentation page for these predefined variations, like how the [mod-tap](../behaviors/mod-tap.md) has a separate page from the [hold-tap](../behaviors/hold-tap.md).
- How does the behavior perform in edge cases? For example, tap-dances invoke the last binding in its list of `bindings` once the maximum number of keypresses has been reached.
Consider also including visual aids alongside written documentation if it adds clarity.
:::info
See [Documentation](documentation.md) for more information on writing, testing, and formatting ZMK documentation.
:::
## Submitting a pull request
Once the above sections are complete, the behavior is almost ready to submit as a pull request. New [devicetree bindings](#creating-the-devicetree-binding-yaml), new [drivers](#creating-the-driver-c), and [predefined use-cases](#defining-common-use-cases-for-the-behavior-dtsi-optional) of the new behavior must contain the appropriate copyright headers, which can be copied and pasted from the tabs below.
<Tabs
defaultValue="yaml"
values={[
{label: 'Devicetree Bindings (.yaml)', value: 'yaml'},
{label: 'Drivers (.c) and predefined use-cases (.dtsi)', value: 'c'},
]}>
<TabItem value="yaml">
```yaml
// highlight-next-line
# Copyright (c) XXXX The ZMK Contributors
# SPDX-License-Identifier: MIT
```
</TabItem>
<TabItem value="c">
```c
/*
// highlight-next-line
* Copyright (c) XXXX The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
```
</TabItem>
</Tabs>
:::caution
Remember to change the copyright year (`XXXX`) to the current year when adding the copyright headers to your newly created files.
:::
While you wait for your PR to become approved and merged into the main repository, please stay up to date for any code reviews and check in with users testing your new behavior. For more detailed information on contributing to ZMK, it is recommended to thoroughly review the [documentation for contributors](https://github.com/zmkfirmware/zmk/blob/main/CONTRIBUTING.md).

View file

@ -21,18 +21,18 @@ apt install -y gcc-multilib
## Building
To do this, you can build ZMK targeting the
`native_posix` board.
`native_posix_64` board.
```
west build --pristine --board native_posix
west build --pristine --board native_posix_64 -- -DZMK_CONFIG=tests/none/normal/
```
Once built, you can run the firmware locally:
```
./build/zephyr/zephyr.exe
./build/zephyr/zmk.exe
```
## Virtual Key Events
The virtual key presses are hardcoded in `boards/native_posix.overlay` file, should you want to change the sequence to test various actions like Mod-Tap, etc.
The virtual key presses are hardcoded in `boards/native_posix_64.overlay` file, should you want to change the sequence to test various actions like Mod-Tap, etc.

View file

@ -4,7 +4,7 @@ sidebar_label: Tests
---
- Running tests requires [native posix support](posix-board.md).
- Any folder under `/app/tests` containing `native_posix.keymap` will be selected when running `west test`.
- Any folder under `/app/tests` containing `native_posix_64.keymap` will be selected when running `west test`.
- Run tests from within the `/zmk/app` directory.
- Run a single test with `west test <testname>`, like `west test tests/toggle-layer/normal`.
@ -13,7 +13,7 @@ sidebar_label: Tests
1. Copy the test set that most closely resembles the tests you will be creating.
2. Rename the newly created test set to the behavior you're testing e.g, toggle-layer
3. Modify `behavior_keymap.dtsi` to create a keymap using the behavior and related behaviors
4. Modify `test_case/native_posix.keymap` for a simulated use case
4. Modify `test_case/native_posix_64.keymap` for a simulated use case
5. Modify `test_case/events.patterns` to collect relevant logs to the test
- See: [sed manual](https://www.gnu.org/software/sed/manual/sed.html) and
[tutorial](https://www.digitalocean.com/community/tutorials/the-basics-of-using-the-sed-stream-editor-to-manipulate-text-in-linux)

View file

@ -6,6 +6,7 @@ module.exports = {
url: "https://zmk.dev",
baseUrl: "/",
favicon: "img/favicon.ico",
trailingSlash: "false",
organizationName: "zmkfirmware", // Usually your GitHub org/user name.
projectName: "zmk", // Usually your repo name.
plugins: [

400
docs/package-lock.json generated
View file

@ -5193,9 +5193,9 @@
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA=="
},
"@tsconfig/docusaurus": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@tsconfig/docusaurus/-/docusaurus-1.0.2.tgz",
"integrity": "sha512-x4rRVb346vjyym6QbeB1Tv01XXwhbkujOmvDmtf0bT20oc2gbDhbmwpskKqZ5Of2Q/Vk4jNk1WMiLsZdJ9t7Dw==",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@tsconfig/docusaurus/-/docusaurus-1.0.5.tgz",
"integrity": "sha512-KM/TuJa9fugo67dTGx+ktIqf3fVc077J6jwHu845Hex4EQf7LABlNonP/mohDKT0cmncdtlYVHHF74xR/YpThg==",
"dev": true
},
"@types/body-parser": {
@ -5395,9 +5395,9 @@
}
},
"@types/react-helmet": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.0.tgz",
"integrity": "sha512-PYRoU1XJFOzQ3BHvWL1T8iDNbRjdMDJMT5hFmZKGbsq09kbSqJy61uwEpTrbTNWDopVphUT34zUSVLK9pjsgYQ==",
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.5.tgz",
"integrity": "sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA==",
"dev": true,
"requires": {
"@types/react": "*"
@ -5830,16 +5830,28 @@
"integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ=="
},
"array-includes": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz",
"integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz",
"integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3",
"es-abstract": "^1.19.1",
"define-properties": "^1.1.4",
"es-abstract": "^1.19.5",
"get-intrinsic": "^1.1.1",
"is-string": "^1.0.7"
},
"dependencies": {
"define-properties": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
"integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
"dev": true,
"requires": {
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
}
}
}
},
"array-union": {
@ -5848,14 +5860,15 @@
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="
},
"array.prototype.flatmap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz",
"integrity": "sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz",
"integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==",
"dev": true,
"requires": {
"call-bind": "^1.0.0",
"call-bind": "^1.0.2",
"define-properties": "^1.1.3",
"es-abstract": "^1.19.0"
"es-abstract": "^1.19.2",
"es-shim-unscopables": "^1.0.0"
}
},
"asap": {
@ -7380,31 +7393,42 @@
}
},
"es-abstract": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz",
"integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==",
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz",
"integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"function.prototype.name": "^1.1.5",
"get-intrinsic": "^1.1.1",
"get-symbol-description": "^1.0.0",
"has": "^1.0.3",
"has-symbols": "^1.0.2",
"has-property-descriptors": "^1.0.0",
"has-symbols": "^1.0.3",
"internal-slot": "^1.0.3",
"is-callable": "^1.2.4",
"is-negative-zero": "^2.0.1",
"is-negative-zero": "^2.0.2",
"is-regex": "^1.1.4",
"is-shared-array-buffer": "^1.0.1",
"is-shared-array-buffer": "^1.0.2",
"is-string": "^1.0.7",
"is-weakref": "^1.0.1",
"object-inspect": "^1.11.0",
"is-weakref": "^1.0.2",
"object-inspect": "^1.12.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.4",
"string.prototype.trimstart": "^1.0.4",
"unbox-primitive": "^1.0.1"
"regexp.prototype.flags": "^1.4.3",
"string.prototype.trimend": "^1.0.5",
"string.prototype.trimstart": "^1.0.5",
"unbox-primitive": "^1.0.2"
},
"dependencies": {
"has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"dev": true
}
}
},
"es-module-lexer": {
@ -7412,6 +7436,15 @@
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz",
"integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ=="
},
"es-shim-unscopables": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
"integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
"dev": true,
"requires": {
"has": "^1.0.3"
}
},
"es-to-primitive": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
@ -7575,9 +7608,9 @@
}
},
"eslint-config-prettier": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz",
"integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==",
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
"dev": true
},
"eslint-mdx": {
@ -7647,25 +7680,25 @@
}
},
"eslint-plugin-react": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz",
"integrity": "sha512-IOlFIRHzWfEQQKcAD4iyYDndHwTQiCMcJVJjxempf203jnNLUnW34AXLrV33+nEXoifJE2ZEGmcjKPL8957eSw==",
"version": "7.30.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.30.0.tgz",
"integrity": "sha512-RgwH7hjW48BleKsYyHK5vUAvxtE9SMPDKmcPRQgtRCYaZA0XQPt5FSkrU3nhz5ifzMZcA8opwmRJ2cmOO8tr5A==",
"dev": true,
"requires": {
"array-includes": "^3.1.4",
"array.prototype.flatmap": "^1.2.5",
"array-includes": "^3.1.5",
"array.prototype.flatmap": "^1.3.0",
"doctrine": "^2.1.0",
"estraverse": "^5.3.0",
"jsx-ast-utils": "^2.4.1 || ^3.0.0",
"minimatch": "^3.0.4",
"minimatch": "^3.1.2",
"object.entries": "^1.1.5",
"object.fromentries": "^2.0.5",
"object.hasown": "^1.1.0",
"object.hasown": "^1.1.1",
"object.values": "^1.1.5",
"prop-types": "^15.7.2",
"prop-types": "^15.8.1",
"resolve": "^2.0.0-next.3",
"semver": "^6.3.0",
"string.prototype.matchall": "^4.0.6"
"string.prototype.matchall": "^4.0.7"
},
"dependencies": {
"doctrine": {
@ -7683,6 +7716,26 @@
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true
},
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"resolve": {
"version": "2.0.0-next.3",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz",
@ -8273,12 +8326,30 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"function.prototype.name": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
"integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3",
"es-abstract": "^1.19.0",
"functions-have-names": "^1.2.2"
}
},
"functional-red-black-tree": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
"dev": true
},
"functions-have-names": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
"dev": true
},
"gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@ -8506,9 +8577,9 @@
}
},
"has-bigints": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz",
"integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
"integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
"dev": true
},
"has-flag": {
@ -8516,6 +8587,15 @@
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"has-property-descriptors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
"dev": true,
"requires": {
"get-intrinsic": "^1.1.1"
}
},
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
@ -9014,10 +9094,13 @@
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
},
"is-bigint": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz",
"integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==",
"dev": true
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
"integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
"dev": true,
"requires": {
"has-bigints": "^1.0.1"
}
},
"is-binary-path": {
"version": "2.1.0",
@ -9028,12 +9111,13 @@
}
},
"is-boolean-object": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz",
"integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
"integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2"
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
}
},
"is-buffer": {
@ -9064,10 +9148,13 @@
}
},
"is-date-object": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz",
"integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==",
"dev": true
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"dev": true,
"requires": {
"has-tostringtag": "^1.0.0"
}
},
"is-decimal": {
"version": "1.0.4",
@ -9117,9 +9204,9 @@
}
},
"is-negative-zero": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz",
"integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
"integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
"dev": true
},
"is-npm": {
@ -9133,10 +9220,13 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"is-number-object": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz",
"integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==",
"dev": true
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
"integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
"dev": true,
"requires": {
"has-tostringtag": "^1.0.0"
}
},
"is-obj": {
"version": "1.0.1",
@ -9193,10 +9283,13 @@
"integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg=="
},
"is-shared-array-buffer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz",
"integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==",
"dev": true
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
"integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2"
}
},
"is-stream": {
"version": "2.0.1",
@ -9413,12 +9506,12 @@
}
},
"jsx-ast-utils": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz",
"integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.0.tgz",
"integrity": "sha512-XzO9luP6L0xkxwhIJMTJQpZo/eeN60K08jHdexfD569AGxeNug6UketeHXEhROoM8aR7EcUoOQmIhcJQjcuq8Q==",
"dev": true,
"requires": {
"array-includes": "^3.1.2",
"array-includes": "^3.1.4",
"object.assign": "^4.1.2"
}
},
@ -10073,9 +10166,9 @@
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object-inspect": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
"integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==",
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.1.tgz",
"integrity": "sha512-Y/jF6vnvEtOPGiKD1+q+X0CiUYRQtEHp89MLLUJ7TUivtH8Ugn2+3A7Rynqk7BRsAoqeOQWnFnjpDrKSxDgIGA==",
"dev": true
},
"object-keys": {
@ -10117,13 +10210,25 @@
}
},
"object.hasown": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz",
"integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz",
"integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.19.1"
"define-properties": "^1.1.4",
"es-abstract": "^1.19.5"
},
"dependencies": {
"define-properties": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
"integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
"dev": true,
"requires": {
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
}
}
}
},
"object.values": {
@ -11523,13 +11628,14 @@
}
},
"regexp.prototype.flags": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz",
"integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==",
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
"integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
"define-properties": "^1.1.3",
"functions-have-names": "^1.2.2"
}
},
"regexpp": {
@ -12612,9 +12718,9 @@
"integrity": "sha512-mC1Ps9l77/97qeOZc+HrOL7TIaOboHqMZ24dGVQrlxFcpPpfCHpH+qfUT7Dz+6mlG8+JPA1KfBQo19iC/+Ngcw=="
},
"string-replace-loader": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/string-replace-loader/-/string-replace-loader-3.0.3.tgz",
"integrity": "sha512-8c26Dl6H9XmKNj3mFBvaUYR7ImOxQ4YRBFuUju78wXpa1cDpyDYvKmqGg8mfkxdYexQ/BBogB7PELlLnmR08nw==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-replace-loader/-/string-replace-loader-3.1.0.tgz",
"integrity": "sha512-5AOMUZeX5HE/ylKDnEa/KKBqvlnFmRZudSOjVJHxhoJg9QYTwl1rECx7SLR8BBH7tfxb4Rp7EM2XVfQFxIhsbQ==",
"dev": true,
"requires": {
"loader-utils": "^2.0.0",
@ -12639,39 +12745,73 @@
}
},
"string.prototype.matchall": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz",
"integrity": "sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg==",
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz",
"integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3",
"es-abstract": "^1.19.1",
"get-intrinsic": "^1.1.1",
"has-symbols": "^1.0.2",
"has-symbols": "^1.0.3",
"internal-slot": "^1.0.3",
"regexp.prototype.flags": "^1.3.1",
"regexp.prototype.flags": "^1.4.1",
"side-channel": "^1.0.4"
},
"dependencies": {
"has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"dev": true
}
}
},
"string.prototype.trimend": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
"integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
"integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
"define-properties": "^1.1.4",
"es-abstract": "^1.19.5"
},
"dependencies": {
"define-properties": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
"integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
"dev": true,
"requires": {
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
}
}
}
},
"string.prototype.trimstart": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
"integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz",
"integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
"define-properties": "^1.1.4",
"es-abstract": "^1.19.5"
},
"dependencies": {
"define-properties": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
"integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
"dev": true,
"requires": {
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
}
}
}
},
"string_decoder": {
@ -13062,15 +13202,23 @@
"integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ=="
},
"unbox-primitive": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
"integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
"integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
"has-bigints": "^1.0.1",
"has-symbols": "^1.0.2",
"call-bind": "^1.0.2",
"has-bigints": "^1.0.2",
"has-symbols": "^1.0.3",
"which-boxed-primitive": "^1.0.2"
},
"dependencies": {
"has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"dev": true
}
}
},
"unherit": {
@ -13423,13 +13571,13 @@
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"webpack": {
"version": "5.58.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.58.2.tgz",
"integrity": "sha512-3S6e9Vo1W2ijk4F4PPWRIu6D/uGgqaPmqw+av3W3jLDujuNkdxX5h5c+RQ6GkjVR+WwIPOfgY8av+j5j4tMqJw==",
"version": "5.72.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.72.1.tgz",
"integrity": "sha512-dXG5zXCLspQR4krZVR6QgajnZOjW2K/djHvdcRaDQvsjV9z9vaW6+ja5dZOYbqBBjF6kGXka/2ZyxNdc+8Jung==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.0",
"@types/estree": "^0.0.50",
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^0.0.51",
"@webassemblyjs/ast": "1.11.1",
"@webassemblyjs/wasm-edit": "1.11.1",
"@webassemblyjs/wasm-parser": "1.11.1",
@ -13437,34 +13585,44 @@
"acorn-import-assertions": "^1.7.6",
"browserslist": "^4.14.5",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.8.3",
"enhanced-resolve": "^5.9.3",
"es-module-lexer": "^0.9.0",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.2.4",
"json-parse-better-errors": "^1.0.2",
"graceful-fs": "^4.2.9",
"json-parse-even-better-errors": "^2.3.1",
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
"schema-utils": "^3.1.0",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.1.3",
"watchpack": "^2.2.0",
"webpack-sources": "^3.2.0"
"watchpack": "^2.3.1",
"webpack-sources": "^3.2.3"
},
"dependencies": {
"acorn": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
"integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==",
"@types/estree": {
"version": "0.0.51",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz",
"integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==",
"dev": true
},
"acorn-import-assertions": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
"integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
"acorn": {
"version": "8.7.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz",
"integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==",
"dev": true
},
"enhanced-resolve": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz",
"integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
}
}
}
},

View file

@ -45,22 +45,22 @@
"devDependencies": {
"@docusaurus/module-type-aliases": "^2.0.0-beta.18",
"@docusaurus/types": "^2.0.0-beta.18",
"@tsconfig/docusaurus": "^1.0.2",
"@tsconfig/docusaurus": "^1.0.5",
"@types/js-yaml": "^4.0.5",
"@types/react": "^17.0.3",
"@types/react-helmet": "^6.1.0",
"@types/react-helmet": "^6.1.5",
"@types/react-router-dom": "^5.1.7",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-mdx": "^1.17.0",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react": "^7.30.0",
"json-schema-to-typescript": "^10.1.5",
"mustache": "^4.2.0",
"null-loader": "^4.0.0",
"prebuild-webpack-plugin": "^1.1.1",
"prettier": "2.3.1",
"string-replace-loader": "^3.0.3",
"string-replace-loader": "^3.1.0",
"typescript": "^4.6.3",
"webpack": "^5.46.0"
"webpack": "^5.72.1"
}
}

View file

@ -68,6 +68,7 @@ module.exports = {
items: [
"development/new-shield",
"development/hardware-metadata-files",
"development/new-behavior",
],
},
],

View file

@ -4989,7 +4989,7 @@ export default [
footnotes: {},
},
{
names: ["C_CAPTIONS", "C_SUBTITILES"],
names: ["C_CAPTIONS", "C_SUBTITLES"],
description: "Closed Caption",
context: "Consumer",
clarify: true,