Skip to content

Commit c6e0281

Browse files
MarekPietakapi-no
authored andcommitted
applications: nrf_desktop: Add keys state utility
Change introduces keys state utility. The utility is used to track state of the pressed keys. Jira: NCSDK-33998 Signed-off-by: Marek Pieta <[email protected]>
1 parent a06333b commit c6e0281

File tree

5 files changed

+308
-0
lines changed

5 files changed

+308
-0
lines changed

applications/nrf_desktop/src/util/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ target_sources_ifdef(CONFIG_DESKTOP_HID_KEYMAP
1616
target_sources_ifdef(CONFIG_DESKTOP_HWID
1717
app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/hwid.c)
1818

19+
target_sources_ifdef(CONFIG_DESKTOP_KEYS_STATE
20+
app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/keys_state.c)
21+
1922
target_sources_ifdef(CONFIG_DESKTOP_ADV_PROV_UUID16_ALL
2023
app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/bt_le_adv_prov_uuid16.c)
2124

applications/nrf_desktop/src/util/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ rsource "Kconfig.hid_eventq"
1212
rsource "Kconfig.hid_keymap"
1313
rsource "Kconfig.hid_reportq"
1414
rsource "Kconfig.hwid"
15+
rsource "Kconfig.keys_state"
1516

1617
endmenu
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor ASA
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
7+
menuconfig DESKTOP_KEYS_STATE
8+
bool "Enable keys state utility"
9+
help
10+
The utility can be used to track the state of active keys.
11+
12+
if DESKTOP_KEYS_STATE
13+
14+
config DESKTOP_KEYS_STATE_KEY_CNT_MAX
15+
int "Maximum number of keys that can be simultaneously active"
16+
default 8
17+
range 1 255
18+
help
19+
The configuration option determines the maximum number of keys that
20+
can be simultaneously tracked by the keys state.
21+
22+
module = DESKTOP_KEYS_STATE
23+
module-str = keys state
24+
source "subsys/logging/Kconfig.template.log_config"
25+
26+
endif # DESKTOP_KEYS_STATE
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
7+
#include "keys_state.h"
8+
9+
#include <stdbool.h>
10+
#include <stddef.h>
11+
#include <stdint.h>
12+
13+
#include <zephyr/sys/__assert.h>
14+
15+
#include <zephyr/logging/log.h>
16+
LOG_MODULE_REGISTER(keys_state, CONFIG_DESKTOP_KEYS_STATE_LOG_LEVEL);
17+
18+
#define INVALID_KEY_IDX UINT8_MAX
19+
20+
21+
static bool keys_state_is_initialized(const struct keys_state *ks)
22+
{
23+
return (ks->cnt_max > 0);
24+
}
25+
26+
void keys_state_init(struct keys_state *ks, uint8_t key_cnt_max)
27+
{
28+
LOG_DBG("ks:%p, key_cnt_max:%" PRIu8, (void *)ks, key_cnt_max);
29+
30+
__ASSERT_NO_MSG(!keys_state_is_initialized(ks));
31+
__ASSERT_NO_MSG((key_cnt_max > 0) && (key_cnt_max <= ARRAY_SIZE(ks->keys)));
32+
33+
ks->cnt_max = key_cnt_max;
34+
keys_state_clear(ks);
35+
}
36+
37+
static struct active_key *get_key(struct keys_state *ks, uint8_t idx, uint16_t key_id)
38+
{
39+
if (idx >= ks->cnt) {
40+
return NULL;
41+
}
42+
43+
struct active_key *key = &ks->keys[idx];
44+
45+
return ((key->id == key_id) && (key->press_cnt > 0)) ? key : NULL;
46+
}
47+
48+
static struct active_key *alloc_key(struct keys_state *ks, uint8_t idx, uint16_t key_id)
49+
{
50+
LOG_DBG("ks:%p, key ID:0x%" PRIx16, (void *)ks, key_id);
51+
52+
__ASSERT_NO_MSG(ks->cnt <= ks->cnt_max);
53+
54+
if ((idx == INVALID_KEY_IDX) || (ks->cnt == ks->cnt_max)) {
55+
return NULL;
56+
}
57+
58+
__ASSERT_NO_MSG(idx <= ks->cnt);
59+
60+
/* Shift active keys to make space for the new key. */
61+
for (uint8_t i = ks->cnt; i > idx; i--) {
62+
ks->keys[i] = ks->keys[i - 1];
63+
}
64+
65+
struct active_key *key = &ks->keys[idx];
66+
67+
key->id = key_id;
68+
key->press_cnt = 0;
69+
70+
ks->cnt++;
71+
72+
return key;
73+
}
74+
75+
static uint8_t get_key_idx(struct keys_state *ks, uint16_t key_id)
76+
{
77+
BUILD_ASSERT(ARRAY_SIZE(ks->keys) <= INVALID_KEY_IDX);
78+
79+
uint8_t idx;
80+
81+
for (idx = 0; idx < ks->cnt; idx++) {
82+
/* Find slot for provided key ID. Active keys are sorted ascending by key ID. */
83+
if (key_id <= ks->keys[idx].id) {
84+
break;
85+
}
86+
}
87+
88+
__ASSERT_NO_MSG(idx <= ks->cnt_max);
89+
return (idx == ks->cnt_max) ? INVALID_KEY_IDX : idx;
90+
}
91+
92+
static void free_key(struct keys_state *ks, uint8_t idx)
93+
{
94+
__ASSERT_NO_MSG(idx < ks->cnt);
95+
96+
LOG_DBG("ks:%p, key ID:0x%" PRIx16, (void *)ks, ks->keys[idx].id);
97+
98+
/* Shift active keys to maintain ascending order. */
99+
for (uint8_t i = idx; i < (ks->cnt - 1); i++) {
100+
ks->keys[i] = ks->keys[i + 1];
101+
}
102+
ks->cnt--;
103+
104+
/* Free the last active key. */
105+
ks->keys[ks->cnt].id = 0;
106+
ks->keys[ks->cnt].press_cnt = 0;
107+
}
108+
109+
int keys_state_key_update(struct keys_state *ks, uint16_t key_id, bool pressed, bool *ks_changed)
110+
{
111+
LOG_DBG("ks:%p, key ID:0x%" PRIx16 " %s",
112+
(void *)ks, key_id, pressed ? "press" : "release");
113+
114+
__ASSERT_NO_MSG(ks_changed);
115+
116+
int err = 0;
117+
uint8_t prev_cnt = ks->cnt;
118+
uint8_t key_idx = get_key_idx(ks, key_id);
119+
struct active_key *key = get_key(ks, key_idx, key_id);
120+
121+
if (key) {
122+
/* Key already active. */
123+
} else if (pressed) {
124+
/* Try to allocate new active key for key press. */
125+
key = alloc_key(ks, key_idx, key_id);
126+
}
127+
128+
if (key) {
129+
__ASSERT_NO_MSG(pressed || (key->press_cnt > 0));
130+
key->press_cnt += (pressed ? (1) : (-1));
131+
132+
if (key->press_cnt == 0) {
133+
/* Key no longer active. */
134+
free_key(ks, key_idx);
135+
}
136+
} else {
137+
if (pressed) {
138+
/* Cannot allocate new active key. */
139+
err = -ENOBUFS;
140+
} else {
141+
/* Released key not active. */
142+
err = -ENOENT;
143+
}
144+
}
145+
146+
*ks_changed = (prev_cnt != ks->cnt);
147+
148+
return err;
149+
}
150+
151+
void keys_state_clear(struct keys_state *ks)
152+
{
153+
LOG_DBG("ks:%p", (void *)ks);
154+
155+
if (keys_state_is_initialized(ks)) {
156+
memset(ks->keys, 0, sizeof(ks->keys));
157+
ks->cnt = 0;
158+
}
159+
}
160+
161+
size_t keys_state_keys_get(const struct keys_state *ks, uint16_t *res, size_t res_size)
162+
{
163+
LOG_DBG("ks:%p", (void *)ks);
164+
165+
__ASSERT_NO_MSG(ks->cnt <= ks->cnt_max);
166+
__ASSERT_NO_MSG(res_size >= ks->cnt_max);
167+
ARG_UNUSED(res_size);
168+
169+
for (uint8_t i = 0; i < ks->cnt; i++) {
170+
res[i] = ks->keys[i].id;
171+
}
172+
173+
return ks->cnt;
174+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
7+
#ifndef _KEYS_STATE_H_
8+
#define _KEYS_STATE_H_
9+
10+
/**
11+
* @file
12+
* @defgroup keys_state Keys state
13+
* @{
14+
* @brief Utility used to track state of active keys.
15+
*/
16+
17+
#ifdef __cplusplus
18+
extern "C" {
19+
#endif
20+
21+
#include <stdbool.h>
22+
#include <stddef.h>
23+
#include <stdint.h>
24+
25+
#define KEYS_MAX_CNT CONFIG_DESKTOP_KEYS_STATE_KEY_CNT_MAX
26+
27+
/** @brief Structure used to track an active key. */
28+
struct active_key {
29+
uint16_t id; /**< Key ID. */
30+
uint16_t press_cnt; /**< Keypress counter. */
31+
};
32+
33+
/**@brief Keys state structure. */
34+
struct keys_state {
35+
struct active_key keys[KEYS_MAX_CNT]; /**< Active keys. */
36+
uint8_t cnt; /**< Current number of active keys. */
37+
uint8_t cnt_max; /**< Maximum number of active keys. */
38+
};
39+
40+
/**
41+
* @brief Initialize a keys state object.
42+
*
43+
* A keys state object must be initialized to track active keys.
44+
*
45+
* The function asserts if maximum number of active keys exceeds the value allowed by Kconfig
46+
* configuration.
47+
*
48+
* @param[in] ks A keys state object.
49+
* @param[in] key_cnt_max Maximum number of active keys.
50+
*/
51+
void keys_state_init(struct keys_state *ks, uint8_t key_cnt_max);
52+
53+
/**
54+
* @brief Notify keys state about a key press/release
55+
*
56+
* The utility can record multiple key presses for a given key ID. The same number of key releases
57+
* is needed for the utility to report release of the key.
58+
*
59+
* @param[in] ks A keys state object.
60+
* @param[in] key_id Key ID.
61+
* @param[in] pressed Information if key was pressed or released.
62+
* @param[out] ks_changed Information if state of the pressed keys changed.
63+
*
64+
* @retval 0 when successful.
65+
* @retval -ENOENT if key is released, but related key press was not recorded by the utility.
66+
* @retval -ENOBUFS if key is pressed and number of active keys exceeds the limit.
67+
*/
68+
int keys_state_key_update(struct keys_state *ks, uint16_t key_id, bool pressed, bool *ks_changed);
69+
70+
/**
71+
* @brief Clear keys state
72+
*
73+
* The function drops all of the tracked active key presses.
74+
*
75+
* @param[in] ks A keys state object.
76+
*/
77+
void keys_state_clear(struct keys_state *ks);
78+
79+
/**
80+
* @brief Get keys state
81+
*
82+
* The function fills the provided array with key IDs of all the active keys. The utility keeps key
83+
* IDs sorted in ascending order to ensure consistent results.
84+
*
85+
* The function asserts if size of the provided array is too small to handle the maximum number of
86+
* active keys.
87+
*
88+
* @param[in] ks A keys state object.
89+
* @param[out] res Array to be filled with key IDs of active keys.
90+
* @param[in] res_size Size of the provided array.
91+
*
92+
* @return Number of keys written.
93+
*/
94+
size_t keys_state_keys_get(const struct keys_state *ks, uint16_t *res, size_t res_size);
95+
96+
#ifdef __cplusplus
97+
}
98+
#endif
99+
100+
/**
101+
* @}
102+
*/
103+
104+
#endif /*_KEYS_STATE_H_ */

0 commit comments

Comments
 (0)