From cf38b6f873a8068f27b9a2df9002f12159bbc8cd Mon Sep 17 00:00:00 2001 From: Jean THOMAS <25333063+jeanthom@users.noreply.github.com> Date: Fri, 31 Oct 2025 07:47:45 +0100 Subject: [PATCH] feat(indev): implement key remapping (#8455) (#8981) Co-authored-by: Jean THOMAS --- .../src/details/main-modules/indev/keypad.rst | 38 +++++++ src/indev/lv_indev.c | 16 +++ src/indev/lv_indev.h | 10 ++ src/indev/lv_indev_private.h | 3 + tests/src/test_cases/test_indev_key_remap.c | 99 +++++++++++++++++++ 5 files changed, 166 insertions(+) create mode 100644 tests/src/test_cases/test_indev_key_remap.c diff --git a/docs/src/details/main-modules/indev/keypad.rst b/docs/src/details/main-modules/indev/keypad.rst index bb8b4d78d0..05f6da9ebc 100644 --- a/docs/src/details/main-modules/indev/keypad.rst +++ b/docs/src/details/main-modules/indev/keypad.rst @@ -87,3 +87,41 @@ The most important special keys in your :cpp:func:`read_cb` function are: You should translate some of the read keys to these special keys to support navigation in a group and interact with selected widgets. + +Key remapping +************* + +Some applications require assigning physical buttons to multiple key events depending +on the context: arrow keys may be used in a screen to navigate across widgets, and +used to interact with one widget in another screen. Key remapping functionality of +indev can be used to implement this behavior. + +You must first define a remapping callback, then invoke +:cpp:func:`lv_indev_set_key_remap_cb` to enable it. + +.. code-block:: c + + lv_key_t remap_left_right_into_prev_next(lv_indev_t * indev, lv_key_t key) + { + LV_UNUSED(indev); + + switch (key) { + case LV_KEY_LEFT: + return LV_KEY_PREV; + case LV_KEY_RIGHT: + return LV_KEY_NEXT; + default: + return key; + } + } + +.. code-block:: c + + /* in your setup code */ + lv_indev_set_key_remap_cb(indev, remap_left_right_into_prev_next); + + /* later, to disable remapping */ + lv_indev_set_key_remap_cb(indev, NULL); + +.. note:: + The remap callback is invoked only for keypad input devices. It will not remap keys from other input device types. diff --git a/src/indev/lv_indev.c b/src/indev/lv_indev.c index 9cac153d1b..a6dfa82d33 100644 --- a/src/indev/lv_indev.c +++ b/src/indev/lv_indev.c @@ -141,6 +141,7 @@ lv_indev_t * lv_indev_create(void) indev->gesture_limit = LV_INDEV_DEF_GESTURE_LIMIT; indev->gesture_min_velocity = LV_INDEV_DEF_GESTURE_MIN_VELOCITY; indev->rotary_sensitivity = LV_INDEV_DEF_ROTARY_SENSITIVITY; + indev->key_remap_cb = NULL; #if LV_USE_GESTURE_RECOGNITION lv_indev_gesture_init(indev); @@ -674,6 +675,16 @@ lv_result_t lv_indev_send_event(lv_indev_t * indev, lv_event_code_t code, void * return lv_event_push_and_send(&indev->event_list, code, indev, param); } +void lv_indev_set_key_remap_cb(lv_indev_t * indev, lv_indev_key_remap_cb_t remap_cb) +{ + if(!indev) { + LV_LOG_WARN("Can't remap key on a NULL indev"); + return; + } + + indev->key_remap_cb = remap_cb; +} + /********************** * STATIC FUNCTIONS **********************/ @@ -762,6 +773,11 @@ static void indev_keypad_proc(lv_indev_t * i, lv_indev_data_t * data) i->keypad.last_state = LV_INDEV_STATE_RELEASED; /*To skip the processing of release*/ } + /* Remap key using callback */ + if(i->key_remap_cb) { + data->key = i->key_remap_cb(i, data->key); + } + /*Save the last key. *It must be done here else `lv_indev_get_key` will return the last key in events*/ uint32_t prev_key = i->keypad.last_key; i->keypad.last_key = data->key; diff --git a/src/indev/lv_indev.h b/src/indev/lv_indev.h index b59b0cd4a4..2da2a837d8 100644 --- a/src/indev/lv_indev.h +++ b/src/indev/lv_indev.h @@ -77,6 +77,9 @@ typedef struct { typedef void (*lv_indev_read_cb_t)(lv_indev_t * indev, lv_indev_data_t * data); +/** Indev key remapping callback */ +typedef lv_key_t (*lv_indev_key_remap_cb_t)(lv_indev_t * indev, lv_key_t key); + /********************** * GLOBAL PROTOTYPES **********************/ @@ -442,6 +445,13 @@ uint32_t lv_indev_remove_event_cb_with_user_data(lv_indev_t * indev, lv_event_cb */ lv_result_t lv_indev_send_event(lv_indev_t * indev, lv_event_code_t code, void * param); +/** + * Set key remapping callback (LV_INDEV_TYPE_KEYPAD) + * @param indev pointer to an indev + * @param remap_cb remapping function callback. Use NULL to disable callback. + */ +void lv_indev_set_key_remap_cb(lv_indev_t * indev, lv_indev_key_remap_cb_t remap_cb); + /********************** * MACROS **********************/ diff --git a/src/indev/lv_indev_private.h b/src/indev/lv_indev_private.h index 6c8f5c2466..781f6a2bc1 100644 --- a/src/indev/lv_indev_private.h +++ b/src/indev/lv_indev_private.h @@ -123,6 +123,9 @@ struct _lv_indev_t { lv_event_list_t event_list; lv_anim_t * scroll_throw_anim; + /**< Key remapping callback */ + lv_indev_key_remap_cb_t key_remap_cb; + #if LV_USE_GESTURE_RECOGNITION lv_indev_gesture_recognizer_t recognizers[LV_INDEV_GESTURE_CNT]; lv_indev_gesture_type_t cur_gesture; diff --git a/tests/src/test_cases/test_indev_key_remap.c b/tests/src/test_cases/test_indev_key_remap.c new file mode 100644 index 0000000000..76ebaa5a52 --- /dev/null +++ b/tests/src/test_cases/test_indev_key_remap.c @@ -0,0 +1,99 @@ +#if LV_BUILD_TEST +#include "../lvgl.h" +#include "unity/unity.h" +#include + +#define MAX_KEYS 6 + +struct key_buffer { + size_t count; + lv_key_t keys[MAX_KEYS]; +}; + +static void key_buffer_add(struct key_buffer * key_buffer, lv_key_t key) +{ + assert(key_buffer->count + 1 <= MAX_KEYS); + + key_buffer->keys[key_buffer->count++] = key; +} + +void setUp(void) +{ + /* Function run before every test */ +} + +void tearDown(void) +{ + /* Function run after every test */ + lv_obj_clean(lv_screen_active()); +} + +static void event_cb(lv_event_t * e) +{ + struct key_buffer * key_buffer = lv_event_get_user_data(e); + + switch(lv_event_get_code(e)) { + case LV_EVENT_KEY: + key_buffer_add(key_buffer, lv_indev_get_key(lv_indev_active())); + break; + default: + break; + } +} + +static lv_key_t remap_cb(lv_indev_t * i, lv_key_t key) +{ + LV_UNUSED(i); + + switch((int)key) { + case 'a': + return LV_KEY_LEFT; + case LV_KEY_ESC: + return 'Z'; + default: + return key; + } +} + +void test_indev_key_remap(void) +{ + struct key_buffer key_buffer = { + .count = 0, + }; + + lv_group_t * group = lv_group_create(); + lv_indev_t * indev = lv_test_indev_get_indev(LV_INDEV_TYPE_KEYPAD); + lv_indev_set_group(indev, group); + + lv_obj_t * btn = lv_button_create(lv_screen_active()); + lv_obj_set_size(btn, 100, 100); + lv_obj_add_event_cb(btn, event_cb, LV_EVENT_ALL, &key_buffer); + lv_group_add_obj(group, btn); + + lv_test_key_hit('a'); + lv_test_key_hit(LV_KEY_ESC); + + /* Remap 'a' -> LV_KEY_LEFT, LV_KEY_ESC -> 'Z' */ + + lv_indev_set_key_remap_cb(indev, remap_cb); + + lv_test_key_hit('a'); + lv_test_key_hit(LV_KEY_ESC); + + /* Disable remapping */ + + lv_indev_set_key_remap_cb(indev, NULL); + + lv_test_key_hit('a'); + lv_test_key_hit(LV_KEY_ESC); + + TEST_ASSERT_EQUAL_UINT32(6, key_buffer.count); + TEST_ASSERT_EQUAL_UINT32('a', key_buffer.keys[0]); + TEST_ASSERT_EQUAL_UINT32(LV_KEY_ESC, key_buffer.keys[1]); + TEST_ASSERT_EQUAL_UINT32(LV_KEY_LEFT, key_buffer.keys[2]); + TEST_ASSERT_EQUAL_UINT32('Z', key_buffer.keys[3]); + TEST_ASSERT_EQUAL_UINT32('a', key_buffer.keys[4]); + TEST_ASSERT_EQUAL_UINT32(LV_KEY_ESC, key_buffer.keys[5]); +} + +#endif