This commit is contained in:
Joel Spadin 2024-06-21 18:10:32 -05:00 committed by GitHub
commit 39fcb9712f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 979 additions and 13 deletions

View file

@ -12,6 +12,7 @@ zephyr_linker_sources(SECTIONS include/linker/zmk-behaviors.ld)
zephyr_linker_sources(RODATA include/linker/zmk-events.ld)
zephyr_syscall_header(${APPLICATION_SOURCE_DIR}/include/drivers/behavior.h)
zephyr_syscall_header(${APPLICATION_SOURCE_DIR}/include/drivers/character_map.h)
zephyr_syscall_header(${APPLICATION_SOURCE_DIR}/include/drivers/ext_power.h)
# Add your source file to the "app" target. This must come after
@ -20,9 +21,11 @@ target_include_directories(app PRIVATE include)
target_sources(app PRIVATE src/stdlib.c)
target_sources(app PRIVATE src/activity.c)
target_sources(app PRIVATE src/behavior.c)
target_sources_ifdef(CONFIG_ZMK_CHARACTER_MAP app PRIVATE src/character_map.c)
target_sources(app PRIVATE src/kscan.c)
target_sources_ifdef(CONFIG_ZMK_KSCAN_SIDEBAND_BEHAVIORS app PRIVATE src/kscan_sideband_behaviors.c)
target_sources(app PRIVATE src/matrix_transform.c)
target_sources(app PRIVATE src/send_string.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)
@ -55,6 +58,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
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_ifdef(CONFIG_ZMK_BEHAVIOR_SEND_STRING app PRIVATE src/behaviors/behavior_send_string.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE app PRIVATE src/behaviors/behavior_sensor_rotate.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE_VAR app PRIVATE src/behaviors/behavior_sensor_rotate_var.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE_COMMON app PRIVATE src/behaviors/behavior_sensor_rotate_common.c)

View file

@ -466,12 +466,13 @@ endmenu
menu "Behavior Options"
rsource "Kconfig.behaviors"
config ZMK_BEHAVIORS_QUEUE_SIZE
int "Maximum number of behaviors to allow queueing from a macro or other complex behavior"
default 256 if ZMK_BEHAVIOR_SEND_STRING
default 64
rsource "Kconfig.behaviors"
config ZMK_MACRO_DEFAULT_WAIT_MS
int "Default time to wait (in milliseconds) before triggering the next behavior in macros"
default 15
@ -480,6 +481,14 @@ config ZMK_MACRO_DEFAULT_TAP_MS
int "Default time to wait (in milliseconds) between the press and release events of a tapped behavior in macros"
default 30
config ZMK_SEND_STRING_DEFAULT_WAIT_MS
int "Default time to wait (in milliseconds) before pressing the next key in the text"
default 0
config ZMK_SEND_STRING_DEFAULT_TAP_MS
int "Default time to wait (in milliseconds) between the press and release for each key in the text"
default 5
endmenu
menu "Advanced"

View file

@ -35,4 +35,14 @@ config ZMK_BEHAVIOR_SENSOR_ROTATE_VAR
config ZMK_BEHAVIOR_MACRO
bool
default y
depends on DT_HAS_ZMK_BEHAVIOR_MACRO_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_ONE_PARAM_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_TWO_PARAM_ENABLED
depends on DT_HAS_ZMK_BEHAVIOR_MACRO_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_ONE_PARAM_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_TWO_PARAM_ENABLED
config ZMK_BEHAVIOR_SEND_STRING
bool
default y
depends on DT_HAS_ZMK_BEHAVIOR_SEND_STRING_ENABLED
config ZMK_CHARACTER_MAP
bool
default y
depends on DT_HAS_ZMK_CHARACTER_MAP_ENABLED

View file

@ -21,3 +21,5 @@
#include <behaviors/macros.dtsi>
#include <behaviors/mouse_key_press.dtsi>
#include <behaviors/soft_off.dtsi>
#include <behaviors/character_map.dtsi>
#include <behaviors/send_string.dtsi>

View file

@ -0,0 +1,117 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <dt-bindings/zmk/keys.h>
/ {
// Codepoint to keycode mapping for a US keyboard layout.
/omit-if-no-ref/ charmap_us: character_map_us {
compatible = "zmk,character-map";
behavior = <&kp>;
map = <0x08 BACKSPACE>
, <0x0A RETURN>
, <0x0B TAB>
, <0x1B ESCAPE>
, <0x20 SPACE>
, <0x21 EXCLAMATION>
, <0x22 DOUBLE_QUOTES>
, <0x23 HASH>
, <0x24 DOLLAR>
, <0x25 PERCENT>
, <0x26 AMPERSAND>
, <0x27 APOSTROPHE>
, <0x28 LEFT_PARENTHESIS>
, <0x29 RIGHT_PARENTHESIS>
, <0x2A ASTERISK>
, <0x2B PLUS>
, <0x2C COMMA>
, <0x2D MINUS>
, <0x2E PERIOD>
, <0x2F SLASH>
, <0x30 N0>
, <0x31 N1>
, <0x32 N2>
, <0x33 N3>
, <0x34 N4>
, <0x35 N5>
, <0x36 N6>
, <0x37 N7>
, <0x38 N8>
, <0x39 N9>
, <0x3A COLON>
, <0x3B SEMICOLON>
, <0x3C LESS_THAN>
, <0x3D EQUAL>
, <0x3E GREATER_THAN>
, <0x3F QUESTION>
, <0x40 AT_SIGN>
, <0x41 LS(A)>
, <0x42 LS(B)>
, <0x43 LS(C)>
, <0x44 LS(D)>
, <0x45 LS(E)>
, <0x46 LS(F)>
, <0x47 LS(G)>
, <0x48 LS(H)>
, <0x49 LS(I)>
, <0x4A LS(J)>
, <0x4B LS(K)>
, <0x4C LS(L)>
, <0x4D LS(M)>
, <0x4E LS(N)>
, <0x4F LS(O)>
, <0x50 LS(P)>
, <0x51 LS(Q)>
, <0x52 LS(R)>
, <0x53 LS(S)>
, <0x54 LS(T)>
, <0x55 LS(U)>
, <0x56 LS(V)>
, <0x57 LS(W)>
, <0x58 LS(X)>
, <0x59 LS(Y)>
, <0x5A LS(Z)>
, <0x5B LEFT_BRACKET>
, <0x5C BACKSLASH>
, <0x5D RIGHT_BRACKET>
, <0x5E CARET>
, <0x5F UNDERSCORE>
, <0x60 GRAVE>
, <0x61 A>
, <0x62 B>
, <0x63 C>
, <0x64 D>
, <0x65 E>
, <0x66 F>
, <0x67 G>
, <0x68 H>
, <0x69 I>
, <0x6A J>
, <0x6B K>
, <0x6C L>
, <0x6D M>
, <0x6E N>
, <0x6F O>
, <0x70 P>
, <0x71 Q>
, <0x72 R>
, <0x73 S>
, <0x74 T>
, <0x75 U>
, <0x76 V>
, <0x77 W>
, <0x78 X>
, <0x79 Y>
, <0x7A Z>
, <0x7B LEFT_BRACE>
, <0x7C PIPE>
, <0x7D RIGHT_BRACE>
, <0x7E TILDE>
, <0x7F DELETE>
;
};
};

View file

@ -0,0 +1,13 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define ZMK_SEND_STRING(name, string, ...) \
name: name { \
compatible = "zmk,behavior-send-string"; \
#binding-cells = <0>; \
text = string; \
__VA_ARGS__ \
};

View file

@ -0,0 +1,26 @@
# Copyright (c) 2023 The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Send String Behavior
compatible: "zmk,behavior-send-string"
include: zero_param.yaml
properties:
text:
type: string
required: true
description: The text to send.
wait-ms:
type: int
description: The time to wait (in milliseconds) before pressing the next key in the text.
tap-ms:
type: int
description: The time to wait (in milliseconds) between the press and release of each key in the text.
charmap:
type: phandle
description: A zmk,character-map instance to use. If omitted, the zmk,charmap chosen node is used.

View file

@ -0,0 +1,28 @@
# Copyright (c) 2023, The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Unicode codepoint to behavior binding mapping
compatible: "zmk,character-map"
properties:
behavior:
type: phandle
required: true
description: |
Behavior to use for a code point in the mapping (typically <&kp>).
The behavior is given one parameter which is the value for the code point
from the "map" property.
unicode-behavior:
type: phandle
description: |
Optional behavior to use for a code point not in the mapping.
The behavior is given one parameter which is the code point.
map:
type: array
required: true
description: |
List of <codepoint param> pairs. Each pair maps a codepoint to a parameter
given to "behavior" to type that code point.

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stddef.h>
#include <sys/errno.h>
#include <zephyr/device.h>
#include <zephyr/toolchain/common.h>
#include <zmk/behavior.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @cond INTERNAL_HIDDEN
*
* Character map driver API definition and system call entry points.
*
* (Internal use only.)
*/
typedef int (*character_map_codepoint_to_binding_t)(const struct device *device, uint32_t codepoint,
struct zmk_behavior_binding *binding);
__subsystem struct character_map_driver_api {
character_map_codepoint_to_binding_t codepoint_to_binding;
};
/**
* @endcond
*/
/**
* @brief Map a Unicode codepoint to a behavior binding.
* @param charmap Pointer to the device structure for the driver instance.
* @param codepoint Unicode codepoint to map.
* @param binding Corresponding behavior binding is written here if successful.
*
* @retval 0 If successful.
* @retval Negative errno code if failure.
*/
__syscall int character_map_codepoint_to_binding(const struct device *charmap, uint32_t codepoint,
struct zmk_behavior_binding *binding);
static inline int z_impl_character_map_codepoint_to_binding(const struct device *charmap,
uint32_t codepoint,
struct zmk_behavior_binding *binding) {
const struct character_map_driver_api *api =
(const struct character_map_driver_api *)charmap->api;
if (api->codepoint_to_binding == NULL) {
return -ENOTSUP;
}
return api->codepoint_to_binding(charmap, codepoint, binding);
}
#ifdef __cplusplus
}
#endif
#include <syscalls/character_map.h>

View file

@ -12,7 +12,7 @@
#define ZMK_BEHAVIOR_TRANSPARENT 1
struct zmk_behavior_binding {
char *behavior_dev;
const char *behavior_dev;
uint32_t param1;
uint32_t param2;
};

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/toolchain.h>
struct zmk_send_string_config {
//! zmk,character-map driver instance to use
const struct device *character_map;
//! Time in milliseconds to wait between key presses
uint32_t wait_ms;
//! Time in milliseconds to wait between the press and release of each key
uint32_t tap_ms;
};
/**
* Assert at compile time that a zmk,charmap chosen node is set.
*/
#define ZMK_BUILD_ASSERT_HAS_CHOSEN_CHARMAP() \
BUILD_ASSERT( \
DT_HAS_CHOSEN(zmk_charmap), \
"A zmk,charmap chosen node must be set. See " \
"https://zmk.dev/docs/behaviors/send-string#character-maps for more information.")
/**
* Get a struct zmk_send_string_config which uses the zmk,charmap chosen node
* and Kconfig options for timing.
*
* Use ZMK_BUILD_ASSERT_HAS_CHOSEN_CHARMAP() somewhere in the file before using
* this macro to provide a nice error message if a character map hasn't been set.
*/
#define ZMK_SEND_STRING_CONFIG_DEFAULT \
((struct zmk_send_string_config){ \
.character_map = DEVICE_DT_GET(DT_CHOSEN(zmk_charmap)), \
.wait_ms = CONFIG_ZMK_SEND_STRING_DEFAULT_WAIT_MS, \
.tap_ms = CONFIG_ZMK_SEND_STRING_DEFAULT_TAP_MS, \
})
/**
* Assert at compile time that a DT_DRV_INST(n) has a charmap property or a
* zmk,character-map chosen node is set.
*/
#define ZMK_BUILD_ASSERT_DT_INST_HAS_CHARMAP(n) \
BUILD_ASSERT(DT_INST_NODE_HAS_PROP(n, charmap) || DT_HAS_CHOSEN(zmk_charmap), \
"Node " DT_NODE_PATH(DT_DRV_INST( \
n)) " requires a charmap property or a zmk,charmap chosen node. " \
"See https://zmk.dev/docs/behaviors/send-string#character-maps for more " \
"information.")
/**
* Get a struct zmk_send_string_config from properties on DT_DRV_INST(n) with
* fallbacks to the values from ZMK_SEND_STRING_CONFIG_DEFAULT.
*
* The driver should have the following properties defined in its YAML file:
*
* charmap:
* type: phandle
* wait-ms:
* type: int
* tap-ms:
* type: int
*
* Use ZMK_BUILD_ASSERT_CHARACTER_MAP_DT_INST_PROP(n) somewhere in the file before using
* this macro to provide a nice error message if a character map hasn't been set.
*/
#define ZMK_SEND_STRING_CONFIG_DT_INST_PROP(n) \
((struct zmk_send_string_config){ \
.character_map = DEVICE_DT_GET(DT_INST_PROP_OR(n, charmap, DT_CHOSEN(zmk_charmap))), \
.wait_ms = DT_INST_PROP_OR(n, wait_ms, CONFIG_ZMK_SEND_STRING_DEFAULT_WAIT_MS), \
.tap_ms = DT_INST_PROP_OR(n, tap_ms, CONFIG_ZMK_SEND_STRING_DEFAULT_TAP_MS), \
})
/**
* Queues behaviors to type a string.
*
* @param config Character map and other configuration to use.
* Pass &ZMK_SEND_STRING_CONFIG_DEFAULT to use default values.
* @param position Key position to use for the key presses/releases
* @param text UTF-8 encoded string
*/
void zmk_send_string(const struct zmk_send_string_config *config, uint32_t position,
const char *text);

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_send_string
#include <drivers/behavior.h>
#include <drivers/character_map.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zmk/behavior.h>
#include <zmk/send_string.h>
struct behavior_send_string_config {
const char *text;
struct zmk_send_string_config config;
};
static int on_send_string_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
const struct device *dev = device_get_binding(binding->behavior_dev);
const struct behavior_send_string_config *config = dev->config;
zmk_send_string(&config->config, event.position, config->text);
return ZMK_BEHAVIOR_OPAQUE;
}
static int on_send_string_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
return ZMK_BEHAVIOR_OPAQUE;
}
static const struct behavior_driver_api behavior_send_string_driver_api = {
.binding_pressed = on_send_string_binding_pressed,
.binding_released = on_send_string_binding_released,
};
static int behavior_send_string_init(const struct device *dev) { return 0; }
#define SEND_STRING_INST(n) \
ZMK_BUILD_ASSERT_DT_INST_HAS_CHARMAP(n); \
\
static const struct behavior_send_string_config behavior_send_string_config_##n = { \
.text = DT_INST_PROP(n, text), \
.config = ZMK_SEND_STRING_CONFIG_DT_INST_PROP(n), \
}; \
BEHAVIOR_DT_INST_DEFINE( \
n, behavior_send_string_init, NULL, NULL, &behavior_send_string_config_##n, APPLICATION, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_send_string_driver_api);
DT_INST_FOREACH_STATUS_OKAY(SEND_STRING_INST);

103
app/src/character_map.c Normal file
View file

@ -0,0 +1,103 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_character_map
#include <stddef.h>
#include <stdlib.h>
#include <drivers/character_map.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/toolchain.h>
#include <zephyr/logging/log.h>
#include <zmk/behavior.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
struct codepoint_param {
uint32_t codepoint;
uint32_t param;
};
struct character_map_config {
const char *behavior_dev;
const char *fallback_behavior_dev;
struct codepoint_param *map;
size_t map_size;
};
static int compare_codepoints(const void *lhs, const void *rhs) {
const struct codepoint_param *lhs_item = (const struct codepoint_param *)lhs;
const struct codepoint_param *rhs_item = (const struct codepoint_param *)rhs;
return (int64_t)lhs_item->codepoint - (int64_t)rhs_item->codepoint;
}
static int codepoint_to_binding(const struct device *dev, uint32_t codepoint,
struct zmk_behavior_binding *binding) {
const struct character_map_config *config = dev->config;
const struct codepoint_param key = {.codepoint = codepoint};
const struct codepoint_param *result =
bsearch(&key, config->map, config->map_size, sizeof(config->map[0]), compare_codepoints);
if (result) {
*binding = (struct zmk_behavior_binding){.behavior_dev = config->behavior_dev,
.param1 = result->param};
return 0;
}
if (config->fallback_behavior_dev) {
*binding = (struct zmk_behavior_binding){.behavior_dev = config->fallback_behavior_dev,
.param1 = codepoint};
return 0;
}
return -ENOTSUP;
}
static const struct character_map_driver_api character_map_driver_api = {
.codepoint_to_binding = codepoint_to_binding,
};
static int character_map_init(const struct device *dev) {
const struct character_map_config *config = dev->config;
// Sort the character map by codepoint for faster lookup
qsort(config->map, config->map_size, sizeof(config->map[0]), compare_codepoints);
return 0;
}
#define MAP_LEN(n) DT_INST_PROP_LEN(n, map)
#define MAP_INIT(node_id, prop, idx) DT_PROP_BY_IDX(node_id, prop, idx),
#define CHARMAP_INST(n) \
BUILD_ASSERT(MAP_LEN(n) > 0, "'map' property must not be an empty array."); \
BUILD_ASSERT(MAP_LEN(n) % 2 == 0, \
"'map' property must be an array of <codepoint param> pairs."); \
\
/* Since we can't iterate over the "map" elements pairwise, we write all the values to a flat \
* array and then reinterpret it as an array of struct codepoint_param. */ \
static uint32_t character_map_array_##n[] = {DT_INST_FOREACH_PROP_ELEM(n, map, MAP_INIT)}; \
\
BUILD_ASSERT(sizeof(character_map_array_##n) % sizeof(struct codepoint_param) == 0, \
"sizeof(struct codepoint_param) must evenly divide array size"); \
\
static const struct character_map_config character_map_config_##n = { \
.behavior_dev = DEVICE_DT_NAME(DT_INST_PROP(n, behavior)), \
.fallback_behavior_dev = \
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, fallback_behavior), \
(DT_DEVICE_NAME(DT_INST_PROP(n, fallback_behavior))), (NULL)), \
.map = (struct codepoint_param *)character_map_array_##n, \
.map_size = ARRAY_SIZE(character_map_array_##n) / 2, \
}; \
\
DEVICE_DT_INST_DEFINE(n, character_map_init, NULL, NULL, &character_map_config_##n, \
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
&character_map_driver_api);
DT_INST_FOREACH_STATUS_OKAY(CHARMAP_INST);

55
app/src/send_string.c Normal file
View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <drivers/character_map.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/math_extras.h>
#include <zmk/send_string.h>
#include <zmk/behavior.h>
#include <zmk/behavior_queue.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
/**
* Reads a codepoint from the given UTF-8 string and advanced the string pointer
* to the next character.
*
* This assumes the input string is valid UTF-8. Behavior on ill-formed input
* is undefined.
*
* Based on public domain code at https://gist.github.com/tylerneylon/9773800
*/
static uint32_t decode_utf8(const char **str) {
int size = **str ? u32_count_leading_zeros(~(**str << 24)) : 0;
uint32_t mask = (1 << (8 - size)) - 1;
uint32_t codepoint = **str & mask;
for (++(*str), --size; size > 0 && **str; --size, ++(*str)) {
codepoint <<= 6;
codepoint += (**str & 0x3F);
}
return codepoint;
}
void zmk_send_string(const struct zmk_send_string_config *config, uint32_t position,
const char *text) {
const char *current = text;
uint32_t codepoint;
while ((codepoint = decode_utf8(&current))) {
struct zmk_behavior_binding binding;
int ret = character_map_codepoint_to_binding(config->character_map, codepoint, &binding);
if (ret != 0) {
LOG_WRN("Failed to map codepoint 0x%04x to a behavior binding: %d", codepoint, ret);
continue;
}
zmk_behavior_queue_add(position, binding, true, config->tap_ms);
zmk_behavior_queue_add(position, binding, false, config->wait_ms);
}
}

View file

@ -31,7 +31,7 @@ K_WORK_DEFINE(usb_status_notifier_work, raise_usb_status_changed_event);
enum usb_dc_status_code zmk_usb_get_status(void) { return usb_status; }
enum zmk_usb_conn_state zmk_usb_get_conn_state(void) {
LOG_DBG("state: %d", usb_status);
// LOG_DBG("state: %d", usb_status);
switch (usb_status) {
case USB_DC_SUSPEND:
case USB_DC_CONFIGURED:

View file

@ -0,0 +1 @@
s/.*hid_listener_keycode/kp/p

View file

@ -0,0 +1,26 @@
kp_pressed: usage_page 0x07 keycode 0x0B implicit_mods 0x02 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x0B implicit_mods 0x02 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x0F implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x0F implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x0F implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x0F implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x12 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x12 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x36 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x36 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x2C implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x2C implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x1A implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x1A implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x12 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x12 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x15 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x15 implicit_mods 0x00 explicit_mods 0x00
kp_pressed: usage_page 0x07 keycode 0x0F implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x0F 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_pressed: usage_page 0x07 keycode 0x1E implicit_mods 0x02 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x1E implicit_mods 0x02 explicit_mods 0x00

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,500)
>;
};

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
/ {
chosen {
zmk,charmap = &charmap_us;
};
behaviors {
ZMK_SEND_STRING(hello_world, "Hello, world!")
};
keymap {
compatible = "zmk,keymap";
default_layer {
bindings = <
&hello_world &none
&none &none
>;
};
};
};

View file

@ -8,6 +8,15 @@ sidebar_label: Macros
The macro behavior allows configuring a list of other behaviors to invoke
when the macro is pressed and/or released.
:::note
Some things that you can do with macros can be done more easily with other behaviors.
To send a single keycode with modifiers, for instance ctrl+tab, you can use the [key press behavior](key-press.md)
with [modifier functions](../codes/modifiers.mdx#modifier-functions).
To send a string of text, you can use the [send string behavior](send-string.md).
:::
## Macro Definition
Each macro you want to use in your keymap gets defined first, then bound in your keymap.
@ -43,11 +52,6 @@ The macro can then be bound in your keymap by referencing it by the label `&zed_
};
```
:::note
For use cases involving sending a single keycode with modifiers, for instance ctrl+tab, the [key press behavior](key-press.md)
with [modifier functions](../codes/modifiers.mdx#modifier-functions) can be used instead of a macro.
:::
### Bindings
Like [hold-taps](/docs/behaviors/hold-tap), macros are created by composing other behaviors, and any of those behaviors can

View file

@ -0,0 +1,255 @@
---
title: Send String Behavior
sidebar_label: Send String
---
## Summary
The send string behavior types a string of text when pressed.
## String Behavior Definition
Each string you want to send must be defined as a new behavior in your keymap, then bound to a key.
For example, the following defines a `&hello_world` behavior that types "Hello, world!" when triggered:
```dts
/ {
chosen {
zmk,charmap = &charmap_us;
};
behaviors {
hello_world: hello_world {
compatible = "zmk,behavior-send-string";
#binding-cells = <0>;
text = "Hello, world!";
};
};
keymap {
compatible = "zmk,keymap";
default_layer {
bindings = <&hello_world>;
};
};
};
```
The `text` property can contain almost any text, however some characters must be formatted specially due to the Devicetree file format, and some cannot be used at all due to bugs in Zephyr:
- Double quotes must be escaped with a backslash, e.g. `\"quoted text\"`.
- `\n` (enter) and `\\` (backslash) cannot currently be used, as they will cause syntax errors.
- `\t` (tab) will be stripped from the string and do nothing.
- `\x08` will press backspace.
- `\x7F` will press delete.
:::caution[Character Maps]
If you create any send string behaviors, you must also select a [character map](#character-maps) so ZMK knows which key to press for each character in the string.
:::
### Convenience Macros
You can use the `ZMK_SEND_STRING(name, text)` macro to reduce the boilerplate when defining a new string.
The following is equivalent to the first example on this page:
```dts
/ {
behaviors {
ZMK_SEND_STRING(hello_world, "Hello, world!")
};
};
```
You can also add a third parameter with extra properties such as [timing configuration](#timing-configuration) and [character map selection](#character-maps):
```dts
/ {
behaviors {
ZMK_SEND_STRING(hello_world, "Hello, world!",
wait-ms = <15>;
tap-ms = <15>;
)
};
};
```
### Timing Configuration
The wait time setting controls how long of a delay there is between key presses. The tap time setting controls how long each key is held. These default to 0 and 5 ms respectively, but they can be increased if strings aren't being typed correctly (at the cost of typing them more slowly).
You can set these timings globally in [your `.conf` file](../config/index.md) with the `CONFIG_ZMK_SEND_STRING_DEFAULT_WAIT_MS` and `CONFIG_ZMK_SEND_STRING_DEFAULT_TAP_MS` settings, e.g.:
```kconfig
CONFIG_ZMK_SEND_STRING_DEFAULT_WAIT_MS=15
CONFIG_ZMK_SEND_STRING_DEFAULT_TAP_MS=15
```
You can also set them per behavior with the `wait-ms` and `tap-ms` properties:
```dts
ZMK_SEND_STRING(hello_world, "Hello, world!",
wait-ms = <15>;
tap-ms = <15>;
)
```
### Behavior Queue Limit
Send string behaviors use an internal queue to handle each key press and release. Adding a send string behavior to your keymap will set the default size of the queue to 256. Each character queues one key press and one release, so this allows 128 characters to be queued.
If you need to send longer strings, you can change the size of this queue via the `CONFIG_ZMK_BEHAVIORS_QUEUE_SIZE` setting in your [`.conf` file](../config/index.md). For example, `CONFIG_ZMK_BEHAVIORS_QUEUE_SIZE=512` would allow a string of 256 characters.
## Character Maps
You must select a character map for ZMK to know which key to press to type each character. ZMK provides one character map, `&charmap_us`, which is designed to work if your operating system is set to a US keyboard layout. If your OS is set to a different layout, you can [create a new character map](#creating-character-maps).
To set the character map to use by default, set the `zmk,charmap` chosen node:
```dts
/ {
chosen {
zmk,charmap = &charmap_us;
};
};
```
You can also override this for individual send string behaviors with the `charmap` property:
```dts
/ {
behaviors {
ZMK_SEND_STRING(hello_world, "Hello, world!",
charmap = <&charmap_us>;
)
};
};
```
:::note
Properties with a `-map` suffix have a special meaning in Zephyr, so the property is named `charmap` instead of `character-map`.
:::
### Creating Character Maps
If your OS is set to a non-US keyboard layout, you will need to create a matching character map.
Add a node to your keymap with the following properties:
```dts
/ {
charmap_name: charmap_name {
compatible = "zmk,character-map";
behavior = <&kp>;
map = <codepoint keycode>
, <codepoint keycode>
...
;
};
};
```
The `behavior` property selects the behavior that the key codes will be sent to. This will typically be `&kp`.
The `map` property is a list of pairs of values. The first value in each pair is the Unicode [code point](https://en.wikipedia.org/wiki/Code_point) of a character, and the second value is the key code to send to `&kp` to type that character. Add a pair for every character that you want to use.
A character map for German might look like this (many characters are omitted from this example for brevity):
```dts
#include <behaviors.dtsi>
#include <dt-bindings/zmk/keys.h>
#define DE_A A
...
#define DE_Y Z
#define DE_Z Y
#define DE_A_UMLAUT SQT
#define DE_O_UMLAUT SEMI
#define DE_U_UMLAUT LBKT
...
/ {
charmap_de: charmap_de {
compatible = "zmk,character-map";
behavior = <&kp>;
map = <0x08 BACKSPACE>
, <0x20 SPACE>
, <0x21 DE_EXCL> // !
, <0x22 DE_DQT> // "
...
, <0x41 LS(DE_A)> // A
, <0x42 LS(DE_B)> // B
...
, <0x59 LS(DE_Y)> // Y
, <0x5A LS(DE_Z)> // Z
...
, <0x61 DE_A> // a
, <0x62 DE_B> // b
...
, <0x79 DE_Y> // y
, <0x7A DE_Z> // z
, <0x7B DE_LBRC> // [
, <0x7C DE_PIPE> // |
, <0x7D DE_RBRC> // ]
, <0x7E DE_TILDE> // ~
, <0x7F DELETE>
, <0xC4 LS(DE_A_UMLAUT)> // Ä
, <0xD6 LS(DE_O_UMLAUT)> // Ö
, <0xDC LS(DE_U_UMLAUT)> // Ü
, <0xE4 DE_A_UMLAUT> // ä
, <0xF6 DE_O_UMLAUT> // ö
, <0xFC DE_U_UMLAUT> // ü
;
};
};
```
You can then select this character map by setting the chosen node to its node label:
```dts
/ {
chosen {
zmk,charmap = &charmap_de;
};
};
```
If you want different strings to use different character maps (for example if you have different layers for different OS keyboard layouts), you can set the `charmap` property on each send string behavior:
```dts
/ {
chosen {
zmk,charmap = &charmap_us;
};
behaviors {
ZMK_SEND_STRING(hello_world_us, "Hello, world!")
ZMK_SEND_STRING(hello_world_de, "Hello, world!",
charmap = <&charmap_de>;
)
};
};
```
#### Unmapped Characters
By default, if a string contains a character whose code point is not in the character map, that character will be ignored. If you add a `fallback-behavior` property which refers to a one-parameter behavior, then that behavior will be invoked with the unmapped code point as its parameter instead.
ZMK does not yet have a behavior for sending arbitrary Unicode characters, but once this is added, it could be used as follows:
```dts
&uni {
// Unicode behavior configuration...
};
/ {
charmap_de: charmap_de {
compatible = "zmk,character-map";
behavior = <&kp>;
fallback-behavior = <&uni>;
...
};
};
```

View file

@ -13,9 +13,17 @@ See the [zmk/app/dts/behaviors/](https://github.com/zmkfirmware/zmk/tree/main/ap
### Kconfig
| Config | Type | Description | Default |
| --------------------------------- | ---- | ------------------------------------------------------------------------------------ | ------- |
| `CONFIG_ZMK_BEHAVIORS_QUEUE_SIZE` | int | Maximum number of behaviors to allow queueing from a macro or other complex behavior | 64 |
| Config | Type | Description | Default |
| --------------------------------- | ---- | ------------------------------------------------------------------------------------ | --------------------------------------------------- |
| `CONFIG_ZMK_BEHAVIORS_QUEUE_SIZE` | int | Maximum number of behaviors to allow queueing from a macro or other complex behavior | 256 if [send string](#send-string) is used, else 64 |
### Devicetree
Applies to: [`/chosen` node](https://docs.zephyrproject.org/latest/guides/dts/intro.html#aliases-and-chosen-nodes)
| Property | Type | Description |
| ------------- | ---- | -------------------------------------------------------------------------------------------- |
| `zmk,charmap` | path | The default [character map](#character-map) to use for [send string](#send-string) behaviors |
## Caps Word
@ -189,6 +197,57 @@ You can use the following nodes to tweak the default behaviors:
| -------- | ----------------------------------------- |
| `&gresc` | [Grave escape](../behaviors/mod-morph.md) |
## Send String
Creates a custom behavior that types a text string.
See the [send string behavior](../behaviors/send-string.md) documentation for more details and examples.
### Kconfig
| Config | Type | Description | Default |
| ---------------------------------------- | ---- | ----------------------------------------------------- | ------- |
| `CONFIG_ZMK_SEND_STRING_DEFAULT_WAIT_MS` | int | Default value for `wait-ms` in send string behaviors. | 0 |
| `CONFIG_ZMK_SEND_STRING_DEFAULT_TAP_MS` | int | Default value for `tap-ms` in send string behaviors. | 5 |
### Devicetree
Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-send-string.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-send-string.yaml)
Applies to: `compatible = "zmk,send-string"`
| Property | Type | Description | Default |
| ---------------- | ------- | ---------------------------------------------------------------------------------------- | ---------------------------------------- |
| `#binding-cells` | int | Must be `<0>` | |
| `text` | string | The text to send | |
| `charmap` | phandle | The [character map](#character-map) to use | `zmk,charmap` chosen node. |
| `wait-ms` | int | The time to wait (in milliseconds) before pressing the next key in the text | `CONFIG_ZMK_SEND_STRING_DEFAULT_WAIT_MS` |
| `tap-ms` | int | The time to wait (in milliseconds) between the press and release of each key in the text | `CONFIG_ZMK_SEND_STRING_DEFAULT_TAP_MS` |
### Character Map
Maps Unicode code points to key codes for [send string behaviors](#send-string).
See the [send string behavior](../behaviors/send-string.md#character-maps) documentation for more details and examples.
#### Devicetree
Definition file: [zmk/app/drivers/zephyr/dts/bindings/character_map/zmk,character-map.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/character_map/zmk%2Ccharacter-map.yaml)
Applies to: `compatible = "zmk,character-map"`
| Property | Type | Description |
| ------------------- | ------- | -------------------------------------------------------------------------------------------------------------------- |
| `behavior` | phandle | Behavior to use for a code point in the map (typically should be `<&kp>`) |
| `fallback-behavior` | phandle | Optional behavior which will be sent any code points not in the map |
| `map` | array | List of `<codepoint keycode>` pairs which give the [key code](../codes/index.mdx) to use for each Unicode code point |
You can use the following nodes to tweak the default behaviors:
| Node | Description |
| ------------- | ------------------------------------ |
| `&charmap_us` | Character map for US keyboard layout |
## Sensor Rotation
Creates a custom behavior which sends a tap of other behaviors when a sensor is rotated.

View file

@ -50,6 +50,7 @@ module.exports = {
"behaviors/tap-dance",
"behaviors/caps-word",
"behaviors/key-repeat",
"behaviors/send-string",
"behaviors/sensor-rotate",
"behaviors/mouse-emulation",
"behaviors/reset",