diff --git a/Modules/lvgl-module/CMakeLists.txt b/Modules/lvgl-module/CMakeLists.txt index cafcd03c0..b170cad1a 100644 --- a/Modules/lvgl-module/CMakeLists.txt +++ b/Modules/lvgl-module/CMakeLists.txt @@ -92,6 +92,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") tactility_add_module(lvgl-module SRCS ${SOURCE_FILES} INCLUDE_DIRS include/ + PRIV_INCLUDE_DIRS private/ REQUIRES ${REQUIRES_LIST} ) diff --git a/Modules/lvgl-module/include/lvgl/devices/keyboard.h b/Modules/lvgl-module/include/lvgl/devices/keyboard.h index e6fc7e557..e64776763 100644 --- a/Modules/lvgl-module/include/lvgl/devices/keyboard.h +++ b/Modules/lvgl-module/include/lvgl/devices/keyboard.h @@ -10,6 +10,10 @@ extern "C" { #endif +struct LvglSoftwareKeyboard { + lv_obj_t* object; +}; + /** * @brief Creates an lv_indev_t bound to the given KEYBOARD_TYPE device and registers a read callback * that polls the device through its KeyboardApi. @@ -32,6 +36,109 @@ error_t lvgl_keyboard_add(struct Device* device, lv_display_t* display, lv_indev */ void lvgl_keyboard_remove(lv_indev_t* indev); +/** + * @brief Assigns the indev to the shared keyboard input group, so it can drive focus + * navigation and input for focused widgets. + * @warning Caller must hold the LVGL lock. + */ +void lvgl_keyboard_enable(lv_indev_t* indev); + +/** + * @brief Detaches the indev from the shared keyboard input group. + * @warning Caller must hold the LVGL lock. + */ +void lvgl_keyboard_disable(lv_indev_t* indev); + +/** + * @brief Wires a textarea up to the on-screen keyboard: shows it on focus, hides it on + * defocus/ready, and adds the textarea to the keyboard's navigation group. + * + * No-op if lvgl_software_keyboard_is_enabled() is false (i.e. a hardware keyboard is present). + * + * @warning Caller must hold the LVGL lock. + * @param[in] keyboard the on-screen keyboard to associate with the textarea + * @param[in] textarea the lv_textarea_t object to wire up + */ +void lvgl_keyboard_add_textarea(struct LvglSoftwareKeyboard* keyboard, lv_obj_t* textarea); + +/** + * @brief Checks whether a ready (started) KEYBOARD_TYPE kernel device is present. + * @return true if a hardware keyboard device is available + */ +bool lvgl_hardware_keyboard_is_available(); + +/** + * @brief Assigns the shared keyboard navigation group to a keypad indev that wasn't created via + * lvgl_keyboard_add() (e.g. a USB HID keyboard managed outside the kernel device system). + * + * @warning Caller must hold the LVGL lock. Requires the keyboard navigation group to already + * exist (created during LVGL module start). + * @param[in] device the keypad indev to attach to the navigation group + */ +void lvgl_hardware_keyboard_add_custom(lv_indev_t* device); + +/** + * @brief Detaches an indev previously registered with lvgl_hardware_keyboard_add_custom() and + * frees its associated context. + * @warning Caller must hold the LVGL lock. + */ +void lvgl_hardware_keyboard_remove_custom(lv_indev_t* device); + +/** + * @brief Creates the on-screen keyboard widget as a hidden child of parent, and remembers it as + * the last constructed software keyboard (see lvgl_software_keyboard_get_last()). + * @warning Caller must hold the LVGL lock. + * @param[out] keyboard the software keyboard struct to initialize + * @param[in] parent the lv_obj_t that will own the keyboard widget + */ +void lvgl_software_keyboard_construct(struct LvglSoftwareKeyboard* keyboard, lv_obj_t* parent); + +/** + * @brief Deletes the on-screen keyboard widget created by lvgl_software_keyboard_construct(). + * @warning Caller must hold the LVGL lock. + */ +void lvgl_software_keyboard_destruct(struct LvglSoftwareKeyboard* keyboard); + +/** + * @brief Unhides the on-screen keyboard and binds it to the given textarea for input. + * @warning Caller must hold the LVGL lock. + * @param[in] keyboard the software keyboard to show + * @param[in] textarea the lv_textarea_t that receives the keyboard's input + */ +void lvgl_software_keyboard_show(struct LvglSoftwareKeyboard* keyboard, lv_obj_t* textarea); + +/** + * @brief Hides the on-screen keyboard. + * @warning Caller must hold the LVGL lock. + */ +void lvgl_software_keyboard_hide(struct LvglSoftwareKeyboard* keyboard); + +/** + * The on-screen keyboard is only shown when there is no hardware keyboard driver active. + * @return if we should show a on-screen keyboard for text input inside our apps + */ +bool lvgl_software_keyboard_is_enabled(); + +/** + * @return the most recently constructed software keyboard, or one with a NULL object if none + * has been constructed yet (or the last one was destructed) + */ +struct LvglSoftwareKeyboard* lvgl_software_keyboard_get_last(); + +/** + * @brief Attaches the shared keyboard navigation group to every currently registered keypad + * indev, so they can be used to navigate the on-screen keyboard and focused widgets. + * @warning Caller must hold the LVGL lock. + */ +void lvgl_software_keyboard_activate(struct LvglSoftwareKeyboard* keyboard); + +/** + * @brief Detaches the navigation group from every currently registered keypad indev (inverse of + * lvgl_software_keyboard_activate()). + * @warning Caller must hold the LVGL lock. + */ +void lvgl_software_keyboard_deactivate(struct LvglSoftwareKeyboard* keyboard); + #ifdef __cplusplus } #endif diff --git a/Modules/lvgl-module/private/lvgl/devices/keyboard_private.h b/Modules/lvgl-module/private/lvgl/devices/keyboard_private.h new file mode 100644 index 000000000..0f385d8b4 --- /dev/null +++ b/Modules/lvgl-module/private/lvgl/devices/keyboard_private.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + + +void lvgl_keyboard_on_start_lvgl(); +void lvgl_keyboard_on_stop_lvgl(); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/lvgl-module/source/arch/lvgl_esp32.c b/Modules/lvgl-module/source/arch/lvgl_esp32.c index ab2668d5c..63d1134ce 100644 --- a/Modules/lvgl-module/source/arch/lvgl_esp32.c +++ b/Modules/lvgl-module/source/arch/lvgl_esp32.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -51,6 +52,10 @@ error_t lvgl_arch_start() { // devices and services. The latter might start adding widgets immediately. initialized = true; + // Must exist before devices/services are attached below, since those can + // immediately try to assign an indev to this group (e.g. USB HID input). + lvgl_keyboard_on_start_lvgl(); + lvgl_devices_attach(); if (lvgl_module_config.on_start) lvgl_module_config.on_start(); @@ -63,8 +68,11 @@ error_t lvgl_arch_stop() { lvgl_devices_detach(); + lvgl_keyboard_on_stop_lvgl(); + if (lvgl_port_deinit() != ESP_OK) { - // Call on_start again to recover + // Recreate what stop() above tore down, then call on_start again to recover + lvgl_keyboard_on_start_lvgl(); if (lvgl_module_config.on_start) lvgl_module_config.on_start(); return ERROR_RESOURCE; } diff --git a/Modules/lvgl-module/source/arch/lvgl_posix.c b/Modules/lvgl-module/source/arch/lvgl_posix.c index f08abc663..119dab397 100644 --- a/Modules/lvgl-module/source/arch/lvgl_posix.c +++ b/Modules/lvgl-module/source/arch/lvgl_posix.c @@ -12,6 +12,7 @@ #include #include +#include extern struct LvglModuleConfig lvgl_module_config; extern void lvgl_devices_attach(); @@ -118,6 +119,10 @@ error_t lvgl_arch_start() { lv_init(); + // Must exist before devices/services are attached from the lvgl task below, + // since those can immediately try to assign an indev to this group. + lvgl_keyboard_on_start_lvgl(); + // Create the main app loop, like ESP-IDF BaseType_t task_result = xTaskCreate( lvgl_task, @@ -147,6 +152,8 @@ error_t lvgl_arch_stop() { } } + lvgl_keyboard_on_stop_lvgl(); + lv_deinit(); return ERROR_NONE; diff --git a/Modules/lvgl-module/source/devices/keyboard.c b/Modules/lvgl-module/source/devices/keyboard.c deleted file mode 100644 index 498450490..000000000 --- a/Modules/lvgl-module/source/devices/keyboard.c +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include - -#include - -#include - -struct LvglKeyboardCtx { - struct Device* device; -}; - -static void lvgl_keyboard_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { - struct LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)lv_indev_get_driver_data(indev); - - struct KeyboardKeyData key_data = {0}; - if (keyboard_read_key(ctx->device, &key_data) != ERROR_NONE) { - data->state = LV_INDEV_STATE_RELEASED; - data->continue_reading = false; - return; - } - - // KeyboardKeyData deliberately mirrors lv_indev_data_t's key/continue_reading fields, so no translation is needed. - data->key = key_data.key; - data->state = key_data.pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; - data->continue_reading = key_data.continue_reading; -} - -error_t lvgl_keyboard_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev) { - if (device == NULL || out_indev == NULL) { - return ERROR_INVALID_ARGUMENT; - } - if (device_get_type(device) != &KEYBOARD_TYPE) { - return ERROR_INVALID_ARGUMENT; - } - - struct LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)malloc(sizeof(struct LvglKeyboardCtx)); - if (ctx == NULL) { - return ERROR_OUT_OF_MEMORY; - } - ctx->device = device; - - lv_indev_t* indev = lv_indev_create(); - if (indev == NULL) { - free(ctx); - return ERROR_OUT_OF_MEMORY; - } - - lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD); - lv_indev_set_read_cb(indev, lvgl_keyboard_read_cb); - lv_indev_set_driver_data(indev, ctx); - if (display != NULL) { - lv_indev_set_display(indev, display); - } - - *out_indev = indev; - return ERROR_NONE; -} - -void lvgl_keyboard_remove(lv_indev_t* indev) { - if (indev == NULL) { - return; - } - - struct LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)lv_indev_get_driver_data(indev); - lv_indev_delete(indev); - free(ctx); -} diff --git a/Modules/lvgl-module/source/devices/keyboard.cpp b/Modules/lvgl-module/source/devices/keyboard.cpp new file mode 100644 index 000000000..15efa33f4 --- /dev/null +++ b/Modules/lvgl-module/source/devices/keyboard.cpp @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include + +#include +#include + +struct LvglKeyboardCtx { + Device* device; +}; + +static LvglSoftwareKeyboard last_software_keyboard = { + .object = nullptr +}; + +static lv_group_t* keyboard_group; + +extern "C" { + +void lvgl_keyboard_on_start_lvgl() { + lvgl_lock(); + keyboard_group = lv_group_create(); + check(keyboard_group); + lvgl_unlock(); +} + +void lvgl_keyboard_on_stop_lvgl() { + last_software_keyboard = { + .object = nullptr + }; + + lvgl_lock(); + lv_group_delete(keyboard_group); + lvgl_unlock(); + + keyboard_group = nullptr; +} + +static void lvgl_keyboard_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { + LvglKeyboardCtx* ctx = static_cast(lv_indev_get_driver_data(indev)); + + KeyboardKeyData key_data = {}; + if (keyboard_read_key(ctx->device, &key_data) != ERROR_NONE) { + data->state = LV_INDEV_STATE_RELEASED; + data->continue_reading = false; + return; + } + + // KeyboardKeyData deliberately mirrors lv_indev_data_t's key/continue_reading fields, so no translation is needed. + data->key = key_data.key; + data->state = key_data.pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; + data->continue_reading = key_data.continue_reading; +} + +error_t lvgl_keyboard_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev) { + if (device == NULL || out_indev == NULL) { + return ERROR_INVALID_ARGUMENT; + } + if (device_get_type(device) != &KEYBOARD_TYPE) { + return ERROR_INVALID_ARGUMENT; + } + + LvglKeyboardCtx* ctx = static_cast(malloc(sizeof(struct LvglKeyboardCtx))); + if (ctx == NULL) { + return ERROR_OUT_OF_MEMORY; + } + ctx->device = device; + + lv_indev_t* indev = lv_indev_create(); + if (indev == NULL) { + free(ctx); + return ERROR_OUT_OF_MEMORY; + } + + lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD); + lv_indev_set_read_cb(indev, lvgl_keyboard_read_cb); + lv_indev_set_driver_data(indev, ctx); + if (display != NULL) { + lv_indev_set_display(indev, display); + } + + *out_indev = indev; + return ERROR_NONE; +} + +void lvgl_keyboard_remove(lv_indev_t* indev) { + if (indev == NULL) { + return; + } + + LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)lv_indev_get_driver_data(indev); + lv_indev_delete(indev); + free(ctx); +} + +void lvgl_keyboard_enable(lv_indev_t* indev) { + check(keyboard_group != nullptr); + lv_indev_set_group(indev, keyboard_group); +} + +void lvgl_keyboard_disable(lv_indev_t* indev) { + lv_indev_set_group(indev, nullptr); +} + +bool lvgl_hardware_keyboard_is_available() { + Device* keyboard_device; + if (device_get_first_active_by_type(&KEYBOARD_TYPE, &keyboard_device) != ERROR_NONE) { + return false; + } + + device_put(keyboard_device); + return true; +} + +void lvgl_hardware_keyboard_add_custom(lv_indev_t* indev) { + LvglKeyboardCtx* ctx = static_cast(malloc(sizeof(struct LvglKeyboardCtx))); + if (ctx == nullptr) { + return; + } + + ctx->device = nullptr; + lv_indev_set_driver_data(indev, ctx); + + lvgl_keyboard_enable(indev); +} + +void lvgl_hardware_keyboard_remove_custom(lv_indev_t* indev) { + lvgl_keyboard_disable(indev); + auto* data = lv_indev_get_driver_data(indev); + lv_indev_set_driver_data(indev, nullptr); + free(data); // LvglKeyboardCtx* +} + +static void textarea_show_keyboard(lv_event_t* event) { + lv_obj_t* target = lv_event_get_current_target_obj(event); + if (last_software_keyboard.object != nullptr) { + lvgl_software_keyboard_show(&last_software_keyboard, target); + lv_obj_scroll_to_view(target, LV_ANIM_ON); + } +} + +static void textarea_hide_keyboard(lv_event_t* event) { + if (last_software_keyboard.object != nullptr) { + lvgl_software_keyboard_hide(&last_software_keyboard); + } +} + +void lvgl_software_keyboard_construct(LvglSoftwareKeyboard* keyboard, lv_obj_t* parent) { + keyboard->object = lv_keyboard_create(parent); + lv_obj_add_flag(keyboard->object, LV_OBJ_FLAG_HIDDEN); + last_software_keyboard = *keyboard; +} + +void lvgl_software_keyboard_destruct(LvglSoftwareKeyboard* keyboard) { + check(keyboard->object); + + lv_obj_delete(keyboard->object); + keyboard->object = nullptr; + + last_software_keyboard = *keyboard; +} + +void lvgl_software_keyboard_show(LvglSoftwareKeyboard* keyboard, lv_obj_t* textarea) { + assert(keyboard->object != nullptr); + lv_obj_clear_flag(keyboard->object, LV_OBJ_FLAG_HIDDEN); + lv_keyboard_set_textarea(keyboard->object, textarea); +} + +void lvgl_software_keyboard_hide(LvglSoftwareKeyboard* keyboard) { + assert(keyboard->object != nullptr); + lv_obj_add_flag(keyboard->object, LV_OBJ_FLAG_HIDDEN); +} + +bool lvgl_software_keyboard_is_enabled() { + return !lvgl_hardware_keyboard_is_available(); +} + +LvglSoftwareKeyboard* lvgl_software_keyboard_get_last() { + return &last_software_keyboard; +} + +void lvgl_keyboard_add_textarea(LvglSoftwareKeyboard* keyboard, lv_obj_t* textarea) { + if (lvgl_software_keyboard_is_enabled()) { + lv_obj_add_event_cb(textarea, textarea_show_keyboard, LV_EVENT_FOCUSED, nullptr); + lv_obj_add_event_cb(textarea, textarea_hide_keyboard, LV_EVENT_DEFOCUSED, nullptr); + lv_obj_add_event_cb(textarea, textarea_hide_keyboard, LV_EVENT_READY, nullptr); + + // lv_obj_t auto-remove themselves from the group when they are destroyed (last checked in LVGL 8.3) + lv_group_add_obj(keyboard_group, textarea); + + lvgl_software_keyboard_activate(keyboard); + } +} + +void lvgl_software_keyboard_activate(LvglSoftwareKeyboard* keyboard) { + auto* indev = lv_indev_get_next(nullptr); + check(keyboard_group); + while (indev) { + if (lv_indev_get_type(indev) == LV_INDEV_TYPE_KEYPAD) { + lv_indev_set_group(indev, keyboard_group); + } + indev = lv_indev_get_next(indev); + } +} + +void lvgl_software_keyboard_deactivate(LvglSoftwareKeyboard* keyboard) { + auto* indev = lv_indev_get_next(nullptr); + while (indev) { + if (lv_indev_get_type(indev) == LV_INDEV_TYPE_KEYPAD) { + lv_indev_set_group(indev, nullptr); + } + indev = lv_indev_get_next(indev); + } +} + +} \ No newline at end of file diff --git a/Tactility/Include/Tactility/lvgl/Keyboard.h b/Tactility/Include/Tactility/lvgl/Keyboard.h deleted file mode 100644 index 83482e7ff..000000000 --- a/Tactility/Include/Tactility/lvgl/Keyboard.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include - -namespace tt::lvgl { - -/** - * Show the on-screen keyboard. - * @param[in] textarea the textarea to focus the input for - */ -void software_keyboard_show(lv_obj_t* textarea); - -/** - * Hide the on-screen keyboard. - * Has no effect when the keyboard is not visible. - */ -void software_keyboard_hide(); - -/** - * The on-screen keyboard is only shown when both of these conditions are true: - * - there is no hardware keyboard - * - TT_CONFIG_FORCE_ONSCREEN_KEYBOARD is set to true in tactility_config.h - * @return if we should show a on-screen keyboard for text input inside our apps - */ -bool software_keyboard_is_enabled(); - -/** - * Activate the keypad for a widget group. - * @param group - */ -void software_keyboard_activate(lv_group_t* group); - -/** - * Deactivate the keypad for the current widget group (if any). - * You don't have to call this after calling _activate() because widget - * cleanup automatically removes itself from the group it belongs to. - */ -void software_keyboard_deactivate(); - -/** - * @return true if LVGL is configured with a keypad - */ -bool hardware_keyboard_is_available(); - -/** - * Set the keypad. - * @param device the keypad device - */ -void hardware_keyboard_set_indev(lv_indev_t* device); - -} \ No newline at end of file diff --git a/Tactility/Private/Tactility/service/gui/GuiService.h b/Tactility/Private/Tactility/service/gui/GuiService.h index 220eb18bc..f00b947ca 100644 --- a/Tactility/Private/Tactility/service/gui/GuiService.h +++ b/Tactility/Private/Tactility/service/gui/GuiService.h @@ -10,7 +10,7 @@ #include -#include +#include namespace tt::service::gui { @@ -47,8 +47,7 @@ class GuiService final : public Service { // App-specific std::shared_ptr appToRender = nullptr; - lv_obj_t* keyboard = nullptr; - lv_group_t* keyboardGroup = nullptr; + LvglSoftwareKeyboard software_keyboard = {}; bool isStarted = false; @@ -92,6 +91,8 @@ public: */ void softwareKeyboardHide(); + void keyboardAddTextArea(lv_obj_t* textarea); + /** * The on-screen keyboard is only shown when both of these conditions are true: * - there is no hardware keyboard @@ -99,15 +100,6 @@ public: * @return if we should show a on-screen keyboard for text input inside our apps */ bool softwareKeyboardIsEnabled(); - - /** - * Glue code for the on-screen keyboard and the hardware keyboard: - * - Attach automatic hide/show parameters for the on-screen keyboard. - * - Registers the textarea to the default lv_group_t for hardware keyboards. - * @param[in] textarea - */ - void keyboardAddTextArea(lv_obj_t* textarea); - }; std::shared_ptr findService(); diff --git a/Tactility/Source/bluetooth/BluetoothHidHost.cpp b/Tactility/Source/bluetooth/BluetoothHidHost.cpp index f900ed6f7..d6c634882 100644 --- a/Tactility/Source/bluetooth/BluetoothHidHost.cpp +++ b/Tactility/Source/bluetooth/BluetoothHidHost.cpp @@ -10,7 +10,6 @@ #include #include -#include #include @@ -22,6 +21,7 @@ #include #include +#include #include #include @@ -470,11 +470,12 @@ static void hidHostSubscribeNext(HidHostCtx& ctx) { getMainDispatcher().dispatch([] { if (!hid_host_ctx || hid_host_ctx->kbIndev != nullptr) return; if (!lvgl_try_lock(1000)) { LOG_W(TAG, "LVGL lock failed for kb indev"); return; } + auto* kb = lv_indev_create(); lv_indev_set_type(kb, LV_INDEV_TYPE_KEYPAD); lv_indev_set_read_cb(kb, hidHostKeyboardReadCb); hid_host_ctx->kbIndev = kb; - lvgl::hardware_keyboard_set_indev(kb); + lvgl_hardware_keyboard_add_custom(kb); lvgl_unlock(); LOG_I(TAG, "Keyboard indev registered"); }); @@ -707,7 +708,7 @@ static int hidHostGapCb(struct ble_gap_event* event, void* /*arg*/) { return; } if (saved_kb) { - lvgl::hardware_keyboard_set_indev(nullptr); + lvgl_hardware_keyboard_remove_custom(saved_kb); lv_indev_delete(saved_kb); } if (saved_mouse) lv_indev_delete(saved_mouse); diff --git a/Tactility/Source/lvgl/Keyboard.cpp b/Tactility/Source/lvgl/Keyboard.cpp deleted file mode 100644 index 5ed5cb0c9..000000000 --- a/Tactility/Source/lvgl/Keyboard.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "Tactility/lvgl/Keyboard.h" -#include "Tactility/service/gui/GuiService.h" - -#include -#include - -namespace tt::lvgl { - -static lv_indev_t* keyboard_device = nullptr; -static lv_group_t* pending_keyboard_group = nullptr; - -void software_keyboard_show(lv_obj_t* textarea) { - auto gui_service = service::gui::findService(); - if (gui_service != nullptr) { - gui_service->softwareKeyboardShow(textarea); - } -} - -void software_keyboard_hide() { - auto gui_service = service::gui::findService(); - if (gui_service != nullptr) { - gui_service->softwareKeyboardHide(); - } -} - -bool software_keyboard_is_enabled() { - auto gui_service = service::gui::findService(); - if (gui_service != nullptr) { - return gui_service->softwareKeyboardIsEnabled(); - } else { - return false; - } -} - -void software_keyboard_activate(lv_group_t* group) { - pending_keyboard_group = group; - if (keyboard_device != nullptr) { - lv_indev_set_group(keyboard_device, group); - } -} - -void software_keyboard_deactivate() { - pending_keyboard_group = nullptr; - if (keyboard_device != nullptr) { - lv_indev_set_group(keyboard_device, nullptr); - } -} - -bool hardware_keyboard_is_available() { - if (keyboard_device != nullptr) { - return true; - } - bool has_kernel_keyboard = false; - device_for_each_of_type(&KEYBOARD_TYPE, &has_kernel_keyboard, [](Device* device, void* context) { - if (device_is_ready(device)) { - *static_cast(context) = true; - return false; - } - return true; - }); - return has_kernel_keyboard; -} - -void hardware_keyboard_set_indev(lv_indev_t* device) { - keyboard_device = device; - // If an app already activated a keyboard group while no hardware keyboard was - // connected, apply the pending group now that the device is available. - if (device != nullptr && pending_keyboard_group != nullptr) { - lv_indev_set_group(device, pending_keyboard_group); - } -} - -} diff --git a/Tactility/Source/lvgl/UsbHidInput.cpp b/Tactility/Source/lvgl/UsbHidInput.cpp index 3f9bb09f7..731046a19 100644 --- a/Tactility/Source/lvgl/UsbHidInput.cpp +++ b/Tactility/Source/lvgl/UsbHidInput.cpp @@ -3,7 +3,6 @@ #ifdef ESP_PLATFORM #include -#include #include #include @@ -15,6 +14,7 @@ #include #include +#include #include @@ -171,6 +171,7 @@ static void usbHidInputTask(void* arg) { lv_indev_set_read_cb(ctx->kb_indev, keyboard_read_cb); lv_indev_set_user_data(ctx->kb_indev, ctx); lv_indev_set_group(ctx->kb_indev, lv_group_get_default()); + lvgl_hardware_keyboard_add_custom(ctx->kb_indev); lvgl_unlock(); @@ -230,13 +231,15 @@ static void usbHidInputTask(void* arg) { } case USB_HID_EVENT_KEYBOARD_CONNECTED: if (ctx->kb_indev && lvgl_try_lock(pdMS_TO_TICKS(200))) { - hardware_keyboard_set_indev(ctx->kb_indev); + lvgl_keyboard_enable(ctx->kb_indev); lvgl_unlock(); } break; case USB_HID_EVENT_KEYBOARD_DISCONNECTED: if (lvgl_try_lock(pdMS_TO_TICKS(200))) { - hardware_keyboard_set_indev(nullptr); + if (ctx->kb_indev) { + lvgl_keyboard_disable(ctx->kb_indev); + } lvgl_unlock(); } break; @@ -263,7 +266,7 @@ static void usbHidInputTask(void* arg) { if (ctx->mouse_indev) { lv_indev_delete(ctx->mouse_indev); ctx->mouse_indev = nullptr; } if (ctx->mouse_cursor) { lv_obj_delete(ctx->mouse_cursor); ctx->mouse_cursor = nullptr; } if (ctx->kb_indev) { - hardware_keyboard_set_indev(nullptr); + lvgl_hardware_keyboard_remove_custom(ctx->kb_indev); lv_indev_delete(ctx->kb_indev); ctx->kb_indev = nullptr; } @@ -347,7 +350,7 @@ void stopUsbHidInput() { if (ctx->mouse_indev) { lv_indev_delete(ctx->mouse_indev); ctx->mouse_indev = nullptr; } if (ctx->mouse_cursor) { lv_obj_delete(ctx->mouse_cursor); ctx->mouse_cursor = nullptr; } if (ctx->kb_indev) { - hardware_keyboard_set_indev(nullptr); + lvgl_hardware_keyboard_remove_custom(ctx->kb_indev); lv_indev_delete(ctx->kb_indev); ctx->kb_indev = nullptr; } diff --git a/Tactility/Source/lvgl/wrappers/textarea.cpp b/Tactility/Source/lvgl/wrappers/textarea.cpp index 22acafe90..f37f0f5d3 100644 --- a/Tactility/Source/lvgl/wrappers/textarea.cpp +++ b/Tactility/Source/lvgl/wrappers/textarea.cpp @@ -1,10 +1,7 @@ #ifdef ESP_PLATFORM -#include - #include - -#include +#include extern "C" { @@ -17,9 +14,9 @@ lv_obj_t* __wrap_lv_textarea_create(lv_obj_t* parent) { lv_obj_set_style_pad_all(textarea, 2, LV_STATE_DEFAULT); } - auto gui_service = tt::service::gui::findService(); - if (gui_service != nullptr) { - gui_service->keyboardAddTextArea(textarea); + auto* software_keyboard = lvgl_software_keyboard_get_last(); + if (software_keyboard != nullptr) { + lvgl_keyboard_add_textarea(software_keyboard, textarea); } if (lv_display_get_color_format(lv_obj_get_display(parent)) == LV_COLOR_FORMAT_L8) { diff --git a/Tactility/Source/service/gui/GuiService.cpp b/Tactility/Source/service/gui/GuiService.cpp index 2fe0a1896..858b3e30b 100644 --- a/Tactility/Source/service/gui/GuiService.cpp +++ b/Tactility/Source/service/gui/GuiService.cpp @@ -1,4 +1,7 @@ #include + +#include "lvgl/devices/keyboard.h" + #include #include #include @@ -113,7 +116,6 @@ int32_t GuiService::guiMain() { return 0; } - service->keyboardGroup = lv_group_create(); lv_obj_set_style_border_width(screen_root, 0, LV_STATE_DEFAULT); lv_obj_set_style_pad_all(screen_root, 0, LV_STATE_DEFAULT); @@ -157,11 +159,12 @@ lv_obj_t* GuiService::createAppViews(lv_obj_t* parent) { lv_obj_set_style_border_width(child_container, 0, LV_STATE_DEFAULT); lv_obj_set_flex_grow(child_container, 1); - if (softwareKeyboardIsEnabled()) { - keyboard = lv_keyboard_create(parent); - lv_obj_add_flag(keyboard, LV_OBJ_FLAG_HIDDEN); + if (lvgl_software_keyboard_is_enabled()) { + lvgl_software_keyboard_construct(&software_keyboard, parent); } else { - keyboard = nullptr; + software_keyboard = { + nullptr + }; } return child_container; @@ -281,9 +284,8 @@ void GuiService::onStop(ServiceContext& service) { thread->join(); lvgl_lock(); - if (keyboardGroup != nullptr) { - lv_group_delete(keyboardGroup); - keyboardGroup = nullptr; + if (software_keyboard.object != nullptr) { + lvgl_software_keyboard_destruct(&software_keyboard); } auto* default_group = lv_group_get_default(); diff --git a/Tactility/Source/service/gui/Keyboard.cpp b/Tactility/Source/service/gui/Keyboard.cpp deleted file mode 100644 index 4ba60c229..000000000 --- a/Tactility/Source/service/gui/Keyboard.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include -#include -#include -#include - -#include - -#include - -namespace tt::service::gui { - -static void show_keyboard(lv_event_t* event) { - auto service = findService(); - if (service != nullptr) { - lv_obj_t* target = lv_event_get_current_target_obj(event); - service->softwareKeyboardShow(target); - lv_obj_scroll_to_view(target, LV_ANIM_ON); - } -} - -static void hide_keyboard(lv_event_t* event) { - auto service = findService(); - if (service != nullptr) { - service->softwareKeyboardHide(); - } -} - -bool GuiService::softwareKeyboardIsEnabled() { - return !lvgl::hardware_keyboard_is_available() || TT_CONFIG_FORCE_ONSCREEN_KEYBOARD; -} - -void GuiService::softwareKeyboardShow(lv_obj_t* textarea) { - lock(); - - if (isStarted && keyboard != nullptr) { - lv_obj_clear_flag(keyboard, LV_OBJ_FLAG_HIDDEN); - lv_keyboard_set_textarea(keyboard, textarea); - } - - unlock(); -} - -void GuiService::softwareKeyboardHide() { - lock(); - - if (isStarted && keyboard != nullptr) { - lv_obj_add_flag(keyboard, LV_OBJ_FLAG_HIDDEN); - } - - unlock(); -} - -void GuiService::keyboardAddTextArea(lv_obj_t* textarea) { - lock(); - - if (isStarted) { - check(lvgl_try_lock(0), "lvgl should already be locked before calling this method"); - - if (softwareKeyboardIsEnabled()) { - lv_obj_add_event_cb(textarea, show_keyboard, LV_EVENT_FOCUSED, nullptr); - lv_obj_add_event_cb(textarea, hide_keyboard, LV_EVENT_DEFOCUSED, nullptr); - lv_obj_add_event_cb(textarea, hide_keyboard, LV_EVENT_READY, nullptr); - - // lv_obj_t auto-remove themselves from the group when they are destroyed (last checked in LVGL 8.3) - lv_group_add_obj(keyboardGroup, textarea); - - lvgl::software_keyboard_activate(keyboardGroup); - } - - lvgl_unlock(); - } - - unlock(); -} - -} // namespace diff --git a/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp b/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp index fa3d70fbc..33942156d 100644 --- a/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp +++ b/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp @@ -9,10 +9,10 @@ #include #include +#include #include #include #include -#include namespace tt::service::keyboardidle { diff --git a/TactilityKernel/source/drivers/keyboard.cpp b/TactilityKernel/source/drivers/keyboard.cpp index 34c2b101f..6bb02bf40 100644 --- a/TactilityKernel/source/drivers/keyboard.cpp +++ b/TactilityKernel/source/drivers/keyboard.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 +#include #include #include -#include #define KEYBOARD_DRIVER_API(driver) ((struct KeyboardApi*)driver->api) diff --git a/TactilityKernel/source/symbols.c b/TactilityKernel/source/symbols.c index 47875d6ca..64fe89bce 100644 --- a/TactilityKernel/source/symbols.c +++ b/TactilityKernel/source/symbols.c @@ -9,9 +9,9 @@ #include #include #include -#include -#include #include +#include +#include #include #include #include @@ -35,14 +35,14 @@ #include #include #include -#include #include +#include #include #include -#include #include #include #include +#include #ifndef ESP_PLATFORM #include