mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-18 11:25:07 +00:00
Improvements and fixes
- custom lvgl alloc - keyboard drivers now uses codepoints only (no more hardwired LV_KEY_*) - fix lvgl file lock when lvgl is stopped
This commit is contained in:
parent
4b6d2b3d8f
commit
5707c27882
@ -11,18 +11,33 @@ macro(tactility_get_module_name NAME OUT_NAME)
|
||||
endmacro()
|
||||
|
||||
macro(tactility_add_module NAME)
|
||||
set(options)
|
||||
# WHOLE_ARCHIVE: force every object file in this module into the final link unconditionally,
|
||||
# instead of only the ones some other already-scanned archive currently has a pending
|
||||
# undefined reference to. Needed when this module provides symbols a component it depends on
|
||||
# (e.g. lvgl__lvgl's custom-allocator hooks) calls back into - a reverse reference a normal
|
||||
# single-pass static-archive link can't resolve, since that component is scanned after this
|
||||
# one's archive has already been passed once. POSIX builds link everything as plain OBJECT
|
||||
# libraries (no archive-pruning to begin with), so this is a no-op there.
|
||||
set(options WHOLE_ARCHIVE)
|
||||
set(oneValueArgs)
|
||||
set(multiValueArgs SRCS INCLUDE_DIRS PRIV_INCLUDE_DIRS REQUIRES PRIV_REQUIRES)
|
||||
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
if (DEFINED ENV{ESP_IDF_VERSION})
|
||||
# idf_component_register's WHOLE_ARCHIVE is a presence-based flag (no value) - only pass
|
||||
# the token at all when requested, rather than passing ARG_WHOLE_ARCHIVE's TRUE/FALSE as
|
||||
# a value, which idf_component_register doesn't expect.
|
||||
set(whole_archive_arg)
|
||||
if (ARG_WHOLE_ARCHIVE)
|
||||
set(whole_archive_arg WHOLE_ARCHIVE)
|
||||
endif()
|
||||
idf_component_register(
|
||||
SRCS ${ARG_SRCS}
|
||||
INCLUDE_DIRS ${ARG_INCLUDE_DIRS}
|
||||
PRIV_INCLUDE_DIRS ${ARG_PRIV_INCLUDE_DIRS}
|
||||
REQUIRES ${ARG_REQUIRES}
|
||||
PRIV_REQUIRES ${ARG_PRIV_REQUIRES}
|
||||
${whole_archive_arg}
|
||||
)
|
||||
else()
|
||||
add_library(${NAME} OBJECT)
|
||||
|
||||
@ -19,7 +19,9 @@ CONFIG_LV_FS_STDIO_PATH=""
|
||||
CONFIG_LV_FS_STDIO_CACHE_SIZE=4096
|
||||
CONFIG_LV_USE_LODEPNG=y
|
||||
CONFIG_LV_USE_BUILTIN_MALLOC=n
|
||||
CONFIG_LV_USE_CLIB_MALLOC=y
|
||||
# Routes lv_malloc/realloc/free through Modules/lvgl-module/source/lv_mem_custom.c, which prefers
|
||||
# PSRAM (falls back to internal RAM automatically) instead of a fixed-size pool or plain malloc.
|
||||
CONFIG_LV_USE_CUSTOM_MALLOC=y
|
||||
CONFIG_LV_USE_MSGBOX=n
|
||||
CONFIG_LV_USE_SPINNER=n
|
||||
CONFIG_LV_USE_WIN=n
|
||||
|
||||
@ -44,31 +44,31 @@
|
||||
37 49 50 51 52 53 54 55 56 0 // % 1 2 3 4 5 6 7 8
|
||||
57 48 8 91 93 43 34 39 27 0 // 9 0 BKSP [ ] + " ' EXIT
|
||||
9 113 119 101 114 116 121 117 105 0 // TAB q w e r t y u i
|
||||
111 112 10 40 41 45 59 58 3 0 // o p ENTER ( ) - ; : STOP
|
||||
111 112 13 40 41 45 59 58 3 0 // o p ENTER ( ) - ; : STOP
|
||||
0 97 115 100 102 103 104 106 107 0 // a s d f g h j k
|
||||
108 17 35 123 125 42 44 46 2 0 // l UP # { } * , . MENU
|
||||
122 120 99 118 98 32 32 110 109 0 // z x c v b n m
|
||||
20 18 19 60 62 47 92 61 10 0 // LEFT DOWN RIGHT < > / \ = RUN
|
||||
20 18 19 60 62 47 92 61 13 0 // LEFT DOWN RIGHT < > / \ = RUN
|
||||
];
|
||||
keymap-uc = [
|
||||
37 49 50 51 52 53 54 55 56 0 // % 1 2 3 4 5 6 7 8
|
||||
57 48 8 91 93 43 34 39 27 0 // 9 0 BKSP [ ] + " ' EXIT
|
||||
9 81 87 69 82 84 89 85 73 0 // TAB Q W E R T Y U I
|
||||
79 80 10 40 41 45 59 58 3 0 // O P ENTER ( ) - ; : STOP
|
||||
79 80 13 40 41 45 59 58 3 0 // O P ENTER ( ) - ; : STOP
|
||||
0 65 83 68 70 71 72 74 75 0 // A S D F G H J K
|
||||
76 17 35 123 125 42 44 46 2 0 // L UP # { } * , . MENU
|
||||
90 88 67 86 66 32 32 78 77 0 // Z X C V B N M
|
||||
20 18 19 60 62 47 92 61 10 0 // LEFT DOWN RIGHT < > / \ = RUN
|
||||
20 18 19 60 62 47 92 61 13 0 // LEFT DOWN RIGHT < > / \ = RUN
|
||||
];
|
||||
keymap-sy = [
|
||||
37 49 50 51 52 53 54 55 56 0 // % 1 2 3 4 5 6 7 8
|
||||
57 48 8 91 93 43 34 39 27 0 // 9 0 BKSP [ ] + " ' EXIT
|
||||
9 113 119 101 114 116 121 117 105 0 // TAB q w e r t y u i
|
||||
111 112 10 40 41 45 59 58 3 0 // o p ENTER ( ) - ; : STOP
|
||||
111 112 13 40 41 45 59 58 3 0 // o p ENTER ( ) - ; : STOP
|
||||
0 97 115 100 102 103 104 106 107 0 // a s d f g h j k
|
||||
108 17 35 123 125 42 44 46 2 0 // l UP # { } * , . MENU
|
||||
122 120 99 118 98 32 32 110 109 0 // z x c v b n m
|
||||
20 18 19 60 62 47 92 61 10 0 // LEFT DOWN RIGHT < > / \ = RUN
|
||||
20 18 19 60 62 47 92 61 13 0 // LEFT DOWN RIGHT < > / \ = RUN
|
||||
];
|
||||
shift-row = <4>;
|
||||
shift-col = <0>;
|
||||
|
||||
@ -74,19 +74,19 @@
|
||||
keymap-lc = [
|
||||
113 119 101 114 116 121 117 105 111 112 // q w e r t y u i o p
|
||||
97 115 100 102 103 104 106 107 108 8 // a s d f g h j k l BACKSPACE
|
||||
0 122 120 99 118 98 110 109 36 10 // z x c v b n m $ ENTER
|
||||
0 122 120 99 118 98 110 109 36 13 // z x c v b n m $ ENTER
|
||||
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
|
||||
];
|
||||
keymap-uc = [
|
||||
81 87 69 82 84 89 85 73 79 80 // Q W E R T Y U I O P
|
||||
65 83 68 70 71 72 74 75 76 8 // A S D F G H J K L BACKSPACE
|
||||
0 90 88 67 86 66 78 77 36 10 // Z X C V B N M $ ENTER
|
||||
0 90 88 67 86 66 78 77 36 13 // Z X C V B N M $ ENTER
|
||||
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
|
||||
];
|
||||
keymap-sy = [
|
||||
49 50 51 52 53 54 55 56 57 48 // 1 2 3 4 5 6 7 8 9 0
|
||||
64 35 43 45 42 47 40 41 95 8 // @ # + - * / ( ) _ BACKSPACE
|
||||
0 33 63 59 58 39 34 44 46 10 // ! ? ; : ' " , . ENTER
|
||||
0 33 63 59 58 39 34 44 46 13 // ! ? ; : ' " , . ENTER
|
||||
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
|
||||
];
|
||||
shift-row = <2>;
|
||||
|
||||
@ -61,19 +61,19 @@
|
||||
keymap-lc = [
|
||||
113 119 101 114 116 121 117 105 111 112 // q w e r t y u i o p
|
||||
97 115 100 102 103 104 106 107 108 8 // a s d f g h j k l BACKSPACE
|
||||
0 122 120 99 118 98 110 109 36 10 // z x c v b n m $ ENTER
|
||||
0 122 120 99 118 98 110 109 36 13 // z x c v b n m $ ENTER
|
||||
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
|
||||
];
|
||||
keymap-uc = [
|
||||
81 87 69 82 84 89 85 73 79 80 // Q W E R T Y U I O P
|
||||
65 83 68 70 71 72 74 75 76 8 // A S D F G H J K L BACKSPACE
|
||||
0 90 88 67 86 66 78 77 36 10 // Z X C V B N M $ ENTER
|
||||
0 90 88 67 86 66 78 77 36 13 // Z X C V B N M $ ENTER
|
||||
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
|
||||
];
|
||||
keymap-sy = [
|
||||
49 50 51 52 53 54 55 56 57 48 // 1 2 3 4 5 6 7 8 9 0
|
||||
64 35 43 45 42 47 40 41 95 8 // @ # + - * / ( ) _ BACKSPACE
|
||||
0 33 63 59 58 39 34 44 46 10 // ! ? ; : ' " , . ENTER
|
||||
0 33 63 59 58 39 34 44 46 13 // ! ? ; : ' " , . ENTER
|
||||
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
|
||||
];
|
||||
shift-row = <2>;
|
||||
|
||||
@ -60,13 +60,13 @@
|
||||
columns = <10>;
|
||||
keymap-lc = [
|
||||
113 119 101 114 116 121 117 105 111 112 // q w e r t y u i o p
|
||||
97 115 100 102 103 104 106 107 108 10 // a s d f g h j k l ENTER
|
||||
97 115 100 102 103 104 106 107 108 13 // a s d f g h j k l ENTER
|
||||
0 122 120 99 118 98 110 109 0 8 // z x c v b n m BACKSPACE
|
||||
32 0 0 0 0 0 0 0 0 0 // SPC
|
||||
];
|
||||
keymap-uc = [
|
||||
81 87 69 82 84 89 85 73 79 80 // Q W E R T Y U I O P
|
||||
65 83 68 70 71 72 74 75 76 10 // A S D F G H J K L ENTER
|
||||
65 83 68 70 71 72 74 75 76 13 // A S D F G H J K L ENTER
|
||||
0 90 88 67 86 66 78 77 0 8 // Z X C V B N M BACKSPACE
|
||||
32 0 0 0 0 0 0 0 0 0 // SPC
|
||||
];
|
||||
|
||||
@ -17,8 +17,6 @@
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
@ -119,36 +117,38 @@ static constexpr HidMapping KEY_MATRIX_HID_SYM[70] = {
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HID usage code + modifier → LVGL key
|
||||
// Covers all codes present in the Tab5 matrix tables above. LV_KEY_* are plain uint32_t
|
||||
// constants - matching KeyboardKeyData::key's driver-defined contract and the same convention
|
||||
// m5stack-module's cardputer_keyboard.cpp kernel driver already uses.
|
||||
// HID usage code + modifier → key (Unicode codepoint)
|
||||
// Covers all codes present in the Tab5 matrix tables above. Every key returns a real Unicode
|
||||
// codepoint per KeyboardKeyData::key's contract - never an LV_KEY_* constant. Keys with no
|
||||
// ordinary character of their own (arrows, Tab/Shift+Tab and Ctrl+arrow as focus nav) use the
|
||||
// CodePoint enum's standard Unicode symbols for the concept (CODEPOINT_FOCUS_PREV/NEXT reused for
|
||||
// Ctrl+arrow's focus-move aliases). lvgl-module's keyboard.cpp translates all of these back to
|
||||
// LVGL's own sentinels (CODEPOINT_ESCAPE/BACKSPACE/DELETE already equal their LV_KEY_* counterpart
|
||||
// numerically, so no translation is needed for those).
|
||||
//
|
||||
// `ctrl` only selects the LVGL focus-navigation aliases for the arrow keys. Ctrl chords on
|
||||
// ordinary keys are NOT folded into the returned value - the C0 control codes a terminal wants
|
||||
// (Ctrl+C = 0x03, Ctrl+K = 0x0B, ...) collide with the LVGL constants returned here (LV_KEY_END = 3,
|
||||
// LV_KEY_PREV = 11, ...), so Ctrl is reported out-of-band via KeyboardKeyData::ctrl instead and
|
||||
// consumers that want control codes derive them themselves.
|
||||
// Ctrl chords on ordinary keys are NOT folded into the returned value - the C0 control codes a
|
||||
// terminal wants (Ctrl+C = 0x03, Ctrl+K = 0x0B, ...) would collide with a codepoint-based scheme,
|
||||
// so Ctrl is reported out-of-band via KeyboardKeyData::ctrl instead and consumers that want
|
||||
// control codes derive them themselves.
|
||||
// ---------------------------------------------------------------------------
|
||||
static uint32_t tab5_translate_key(uint8_t keycode, uint8_t modifier, bool ctrl) {
|
||||
const bool shift = (modifier & 0x22U) != 0U;
|
||||
|
||||
// Navigation → LVGL key constants
|
||||
// Navigation → key (Unicode codepoint)
|
||||
switch (keycode) {
|
||||
case 0x29: return LV_KEY_ESC;
|
||||
case 0x28: return LV_KEY_ENTER;
|
||||
case 0x2A: return LV_KEY_BACKSPACE;
|
||||
case 0x4C: return LV_KEY_DEL;
|
||||
case 0x2B: return '\t';
|
||||
// Arrows: Ctrl+arrow = focus navigation, plain arrow = raw cursor movement
|
||||
case 0x52: return ctrl ? (uint32_t)LV_KEY_PREV : (uint32_t)LV_KEY_UP;
|
||||
case 0x51: return ctrl ? (uint32_t)LV_KEY_NEXT : (uint32_t)LV_KEY_DOWN;
|
||||
case 0x50: return ctrl ? (uint32_t)LV_KEY_PREV : (uint32_t)LV_KEY_LEFT;
|
||||
case 0x4F: return ctrl ? (uint32_t)LV_KEY_NEXT : (uint32_t)LV_KEY_RIGHT;
|
||||
case 0x29: return CODEPOINT_ESCAPE;
|
||||
case 0x28: return CODEPOINT_ENTER;
|
||||
case 0x2A: return CODEPOINT_BACKSPACE;
|
||||
case 0x4C: return CODEPOINT_DELETE;
|
||||
case 0x2B: return CODEPOINT_TAB;
|
||||
case 0x52: return CODEPOINT_ARROW_UP;
|
||||
case 0x51: return CODEPOINT_ARROW_DOWN;
|
||||
case 0x50: return CODEPOINT_ARROW_LEFT;
|
||||
case 0x4F: return CODEPOINT_ARROW_RIGHT;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// F1-F12 (Sym layer over the number row) have no LVGL/ASCII representation - callers that
|
||||
// F1-F12 (Sym layer over the number row) have no Unicode/LVGL representation - callers that
|
||||
// want them read KeyboardKeyData::hid_keycode instead (see drain_events()), which is
|
||||
// populated for every key regardless of whether `key` itself has a meaningful value.
|
||||
|
||||
@ -378,9 +378,9 @@ static void drain_events(Device* device, Tab5KeyboardInternal* internal) {
|
||||
if (internal->ctrl_held) modifier |= 0x01U; // HID LeftCtrl
|
||||
if (internal->alt_held) modifier |= 0x04U; // HID LeftAlt
|
||||
const uint32_t lv_key = tab5_translate_key(m.keycode, modifier, internal->ctrl_held);
|
||||
// Queue whenever there's a HID keycode, even if this key has no LVGL/ASCII
|
||||
// Queue whenever there's a HID keycode, even if this key has no Unicode/LVGL
|
||||
// representation (e.g. F1-F12) - lv_key stays 0 for those, callers that only
|
||||
// care about LVGL/ASCII text ignore a 0 key the same way they always have.
|
||||
// care about the codepoint ignore a 0 key the same way they always have.
|
||||
if (pressed) {
|
||||
// A real key was pressed — this hold is a chord, not a tap
|
||||
internal->aa_tapped = false;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include "sdl_input.h"
|
||||
|
||||
#include <lvgl.h>
|
||||
#include <tactility/drivers/keyboard.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
@ -27,22 +27,27 @@ void push_key(uint32_t key) {
|
||||
key_queue_count++;
|
||||
}
|
||||
|
||||
// Mirrors LVGL's own lv_sdl_keyboard.c keycode_to_ctrl_key(): maps navigation/control keys to
|
||||
// LV_KEY_* constants. Printable characters arrive separately via SDL_TEXTINPUT.
|
||||
uint32_t keycode_to_key(SDL_Keycode sdl_key) {
|
||||
// Mirrors LVGL's own lv_sdl_keyboard.c keycode_to_ctrl_key(): every key returns a real Unicode
|
||||
// codepoint per KeyboardKeyData::key's contract - never an LV_KEY_* constant, even for keys with
|
||||
// no ordinary character of their own (arrows, Tab/Shift+Tab as focus nav, Home/End), which use the
|
||||
// CodePoint enum's standard Unicode symbols for the concept. lvgl-module's keyboard.cpp translates
|
||||
// all of these back to LVGL's own sentinels (CODEPOINT_ESCAPE/BACKSPACE/DELETE already equal their
|
||||
// LV_KEY_* counterpart numerically, so no translation is needed for those). Printable characters
|
||||
// arrive separately via SDL_TEXTINPUT.
|
||||
uint32_t keycode_to_key(SDL_Keycode sdl_key, bool shift) {
|
||||
switch (sdl_key) {
|
||||
case SDLK_RIGHT: return LV_KEY_RIGHT;
|
||||
case SDLK_LEFT: return LV_KEY_LEFT;
|
||||
case SDLK_UP: return LV_KEY_UP;
|
||||
case SDLK_DOWN: return LV_KEY_DOWN;
|
||||
case SDLK_ESCAPE: return LV_KEY_ESC;
|
||||
case SDLK_BACKSPACE: return LV_KEY_BACKSPACE;
|
||||
case SDLK_DELETE: return LV_KEY_DEL;
|
||||
case SDLK_RIGHT: return CODEPOINT_ARROW_RIGHT;
|
||||
case SDLK_LEFT: return CODEPOINT_ARROW_LEFT;
|
||||
case SDLK_UP: return CODEPOINT_ARROW_UP;
|
||||
case SDLK_DOWN: return CODEPOINT_ARROW_DOWN;
|
||||
case SDLK_ESCAPE: return CODEPOINT_ESCAPE;
|
||||
case SDLK_BACKSPACE: return CODEPOINT_BACKSPACE;
|
||||
case SDLK_DELETE: return CODEPOINT_DELETE;
|
||||
case SDLK_RETURN:
|
||||
case SDLK_KP_ENTER: return LV_KEY_ENTER;
|
||||
case SDLK_TAB: return LV_KEY_NEXT;
|
||||
case SDLK_HOME: return LV_KEY_HOME;
|
||||
case SDLK_END: return LV_KEY_END;
|
||||
case SDLK_KP_ENTER: return CODEPOINT_ENTER;
|
||||
case SDLK_TAB: return CODEPOINT_TAB;
|
||||
case SDLK_HOME: return CODEPOINT_HOME;
|
||||
case SDLK_END: return CODEPOINT_END;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
@ -75,7 +80,7 @@ void sdl_input_pump() {
|
||||
}
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
push_key(keycode_to_key(event.key.keysym.sym));
|
||||
push_key(keycode_to_key(event.key.keysym.sym, (event.key.keysym.mod & KMOD_SHIFT) != 0));
|
||||
break;
|
||||
case SDL_TEXTINPUT:
|
||||
// ASCII only (first byte of event.text.text) - sufficient for a simulator keyboard.
|
||||
|
||||
@ -12,8 +12,6 @@
|
||||
#include <tactility/log.h>
|
||||
#include <tactility/time.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#define TAG "ButtonControl"
|
||||
@ -105,8 +103,8 @@ static error_t start(Device* device) {
|
||||
|
||||
error_t error = acquire_button(
|
||||
config->pin_primary,
|
||||
two_button_mode ? LV_KEY_ENTER : LV_KEY_NEXT,
|
||||
two_button_mode ? LV_KEY_ESC : LV_KEY_ENTER,
|
||||
two_button_mode ? (uint32_t)CODEPOINT_ENTER : (uint32_t)CODEPOINT_ARROW_DOWN,
|
||||
two_button_mode ? (uint32_t)CODEPOINT_ESCAPE : (uint32_t)CODEPOINT_ENTER,
|
||||
&internal->primary
|
||||
);
|
||||
if (error != ERROR_NONE) {
|
||||
@ -114,7 +112,7 @@ static error_t start(Device* device) {
|
||||
return error;
|
||||
}
|
||||
|
||||
error = acquire_button(config->pin_secondary, LV_KEY_NEXT, LV_KEY_PREV, &internal->secondary);
|
||||
error = acquire_button(config->pin_secondary, CODEPOINT_ARROW_DOWN, CODEPOINT_ARROW_UP, &internal->secondary);
|
||||
if (error != ERROR_NONE) {
|
||||
if (internal->primary.in_use) {
|
||||
gpio_descriptor_release(internal->primary.descriptor);
|
||||
@ -176,7 +174,7 @@ static void poll_button(const ButtonControlConfig* config, ButtonControlInternal
|
||||
}
|
||||
|
||||
// Release: decide short vs. long press by elapsed hold duration, then queue a synthetic
|
||||
// key tap (press followed by release) for the LVGL key this gesture maps to.
|
||||
// key tap (press followed by release) for the key this gesture maps to.
|
||||
uint32_t held_ms = now - state->press_start_time;
|
||||
uint32_t key = held_ms < config->long_press_ms ? state->short_press_key : state->long_press_key;
|
||||
push_pending(internal, key, true);
|
||||
|
||||
@ -14,8 +14,6 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
static constexpr const char* TAG = "CardputerAdvKeyboard";
|
||||
@ -46,24 +44,24 @@ static constexpr int CARDPUTER_ADV_COLS = 14;
|
||||
// [row][col] on the 4x14 grid, matching the base Cardputer's physical layout. 0 means the cell
|
||||
// emits nothing (used for the sym/shift cells themselves, and unwired cells on this board).
|
||||
static const uint32_t cardputer_adv_keymap_lc[CARDPUTER_ADV_ROWS][CARDPUTER_ADV_COLS] = {
|
||||
{ '`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', LV_KEY_BACKSPACE },
|
||||
{ '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\' },
|
||||
{ 0, 0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', LV_KEY_ENTER },
|
||||
{ '`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', CODEPOINT_BACKSPACE },
|
||||
{ CODEPOINT_TAB, 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\' },
|
||||
{ 0, 0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', CODEPOINT_ENTER },
|
||||
{ 0, 0, 0, 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', ' ' },
|
||||
};
|
||||
|
||||
static const uint32_t cardputer_adv_keymap_uc[CARDPUTER_ADV_ROWS][CARDPUTER_ADV_COLS] = {
|
||||
{ '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', LV_KEY_DEL },
|
||||
{ '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '|' },
|
||||
{ 0, 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', LV_KEY_ENTER },
|
||||
{ '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', CODEPOINT_DELETE },
|
||||
{ CODEPOINT_FOCUS_PREV, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '|' },
|
||||
{ 0, 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', CODEPOINT_ENTER },
|
||||
{ 0, 0, 0, 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', ' ' },
|
||||
};
|
||||
|
||||
static const uint32_t cardputer_adv_keymap_sym[CARDPUTER_ADV_ROWS][CARDPUTER_ADV_COLS] = {
|
||||
{ LV_KEY_ESC, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, LV_KEY_PREV, 0, LV_KEY_ENTER },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, LV_KEY_LEFT, LV_KEY_NEXT, LV_KEY_RIGHT, 0 },
|
||||
{ CODEPOINT_ESCAPE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ CODEPOINT_TAB, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CODEPOINT_ARROW_UP, 0, CODEPOINT_ENTER },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CODEPOINT_ARROW_LEFT, CODEPOINT_ARROW_DOWN, CODEPOINT_ARROW_RIGHT, 0 },
|
||||
};
|
||||
|
||||
struct CardputerAdvActiveKey {
|
||||
|
||||
@ -14,8 +14,6 @@
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
static constexpr const char* TAG = "CardputerKeyboard";
|
||||
@ -74,8 +72,8 @@ struct CardputerKeyboardPendingEvent {
|
||||
struct CardputerKeyboardInternal {
|
||||
GpioDescriptor* output_descriptors[CARDPUTER_OUTPUT_COUNT];
|
||||
GpioDescriptor* input_descriptors[CARDPUTER_INPUT_COUNT];
|
||||
// 0 when no actionable key is currently held; otherwise the LVGL key code last reported
|
||||
// via read_key(). Only ever one actionable key at a time (matches original hardware driver:
|
||||
// 0 when no actionable key is currently held; otherwise the key (Unicode codepoint) last
|
||||
// reported via read_key(). Only ever one actionable key at a time (matches original hardware driver:
|
||||
// modifier keys are consumed internally, and only the first non-modifier key found in a
|
||||
// scan is reported).
|
||||
uint32_t active_key;
|
||||
@ -195,10 +193,12 @@ static uint8_t read_input(CardputerKeyboardInternal* internal) {
|
||||
return mask;
|
||||
}
|
||||
|
||||
// Scans the full matrix and resolves it to a single LVGL key code (0 if none), applying the
|
||||
// same priority as the original driver: enter > space > backspace > first regular character
|
||||
// found in scan order, with fn changing the interpretation of backspace/enter/punctuation.
|
||||
// Modifier keys (fn/shift/ctrl/opt/alt/tab) are never reported themselves.
|
||||
// Scans the full matrix and resolves it to a single key (Unicode codepoint, 0 if none - never an
|
||||
// LV_KEY_* constant, see KeyboardKeyData::key's contract), applying priority: enter > space >
|
||||
// backspace > tab (U+21E5 tab-to-bar, or U+21E4 with shift - the closest standard Unicode symbol
|
||||
// for focus navigation) > first regular character found in scan order, with fn changing the
|
||||
// interpretation of backspace/enter/punctuation. Modifier keys (fn/shift/ctrl/opt/alt) are never
|
||||
// reported themselves.
|
||||
static uint32_t scan_key(CardputerKeyboardInternal* internal) {
|
||||
bool fn = false, shift = false, ctrl = false;
|
||||
bool del_flag = false, enter_flag = false, space_flag = false;
|
||||
@ -223,7 +223,6 @@ static uint32_t scan_key(CardputerKeyboardInternal* internal) {
|
||||
const auto& def = cardputer_key_map[row][col];
|
||||
|
||||
switch (def.role) {
|
||||
case CARDPUTER_KEY_TAB:
|
||||
case CARDPUTER_KEY_OPT:
|
||||
case CARDPUTER_KEY_ALT:
|
||||
break; // consumed, never affects output
|
||||
@ -259,24 +258,25 @@ static uint32_t scan_key(CardputerKeyboardInternal* internal) {
|
||||
char resolved_char = has_regular ? ((ctrl || shift) ? regular_shifted : regular_normal) : 0;
|
||||
|
||||
if (!fn) {
|
||||
if (enter_flag) return LV_KEY_ENTER;
|
||||
if (enter_flag) return CODEPOINT_ENTER;
|
||||
if (space_flag) return (uint32_t)' ';
|
||||
if (del_flag) return LV_KEY_BACKSPACE;
|
||||
if (has_regular) return (uint32_t)(uint8_t)resolved_char;
|
||||
if (del_flag) return CODEPOINT_BACKSPACE;
|
||||
if (has_regular) return (uint32_t)resolved_char;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// fn combos: forward-delete, enter, and group navigation (using PREV/NEXT rather than
|
||||
// UP/DOWN so widgets like lv_switch that toggle on arrow keys aren't affected).
|
||||
if (del_flag) return LV_KEY_DEL;
|
||||
if (enter_flag) return LV_KEY_ENTER;
|
||||
// fn combos: forward-delete, enter, and group navigation (using the tab-to-bar codepoints
|
||||
// rather than arrow codepoints so widgets like lv_switch that toggle on arrow keys aren't
|
||||
// affected).
|
||||
if (del_flag) return CODEPOINT_DELETE;
|
||||
if (enter_flag) return CODEPOINT_ENTER;
|
||||
if (has_regular) {
|
||||
switch (resolved_char) {
|
||||
case '`': return LV_KEY_ESC;
|
||||
case ',': return LV_KEY_LEFT;
|
||||
case '/': return LV_KEY_RIGHT;
|
||||
case ';': return LV_KEY_PREV;
|
||||
case '.': return LV_KEY_NEXT;
|
||||
case '`': return CODEPOINT_ESCAPE;
|
||||
case ',': return CODEPOINT_ARROW_LEFT;
|
||||
case '/': return CODEPOINT_ARROW_RIGHT;
|
||||
case ';': return CODEPOINT_ARROW_UP;
|
||||
case '.': return CODEPOINT_ARROW_DOWN;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,8 +34,9 @@ properties:
|
||||
Base (lowercase) layer keymap, rows*columns bytes in row-major order (already in
|
||||
silkscreen/keymap column order - see reverse-columns). 0 = no key at this position (e.g.
|
||||
a blank matrix position, or a position handled as a modifier via shift-row/shift-col/
|
||||
sym-row/sym-col instead). Non-zero bytes are sent as-is via KeyboardKeyData::key (ASCII
|
||||
or an LVGL LV_KEY_* code).
|
||||
sym-row/sym-col instead). Non-zero bytes are sent as-is via KeyboardKeyData::key (a Unicode
|
||||
codepoint - byte range covers Latin-1 - or an LVGL LV_KEY_* code for keys with no character
|
||||
representation).
|
||||
keymap-uc:
|
||||
type: array
|
||||
element-type: uint8_t
|
||||
|
||||
@ -94,6 +94,10 @@ tactility_add_module(lvgl-module
|
||||
INCLUDE_DIRS include/
|
||||
PRIV_INCLUDE_DIRS private/
|
||||
REQUIRES ${REQUIRES_LIST}
|
||||
# lv_mem_custom.c provides lv_mem_init/lv_malloc_core/etc, which lvgl__lvgl's own lv_init.c/
|
||||
# lv_mem.c call back into (LV_STDLIB_CUSTOM) - a reverse reference into this module that a
|
||||
# normal single-pass static link can't resolve. See module.cmake's WHOLE_ARCHIVE comment.
|
||||
WHOLE_ARCHIVE
|
||||
)
|
||||
|
||||
tactility_get_module_name("lvgl-module" MODULE_NAME)
|
||||
|
||||
@ -37,6 +37,32 @@ Font sizes and symbols are configurable:
|
||||
|
||||
If you change an icon font size, ensure that a corresponding C file exists in `source-fonts/` (e.g., `material_symbols_shared_24.c`). These files are generated from TTF/OTF fonts using the LVGL font converter.
|
||||
|
||||
## Custom memory allocator
|
||||
|
||||
LVGL's malloc/realloc/free can be routed through a custom backend instead of its built-in pool or
|
||||
plain `malloc`. Three things are required:
|
||||
|
||||
**1. Select the backend.** On ESP32, via Kconfig (`sdkconfig`):
|
||||
```
|
||||
CONFIG_LV_USE_CUSTOM_MALLOC=y
|
||||
```
|
||||
On Simulator/POSIX, ESP-IDF's Kconfig doesn't apply - select it in `lv_conf.h` instead:
|
||||
```c
|
||||
#define LV_USE_STDLIB_MALLOC LV_STDLIB_CUSTOM
|
||||
```
|
||||
|
||||
**2. Force the module into the link.** LVGL's own init code calls back into the functions above
|
||||
a reverse reference a normal single-pass static-archive link can't resolve on its own:
|
||||
```cmake
|
||||
tactility_add_module(lvgl-module
|
||||
...
|
||||
WHOLE_ARCHIVE
|
||||
)
|
||||
```
|
||||
Without `WHOLE_ARCHIVE`, or without step 1 selecting the custom backend, ESP-IDF's LVGL component
|
||||
compiles its own allocator using these same symbol names - whichever one the linker happens to
|
||||
pull in first silently wins, with no error and no guarantee it's the intended one.
|
||||
|
||||
## License
|
||||
|
||||
This module is licensed under the [Apache v2.0](LICENSE-Apache-2.0.md) license.
|
||||
@ -20,19 +20,16 @@ static bool initialized = false;
|
||||
|
||||
void lvgl_lock(void) {
|
||||
if (!initialized) { return; }
|
||||
if (!lvgl_is_running()) { return; }
|
||||
lvgl_port_lock(portMAX_DELAY);
|
||||
}
|
||||
|
||||
bool lvgl_try_lock(uint32_t timeoutTicks) {
|
||||
if (!initialized) { return false; }
|
||||
if (!lvgl_is_running()) { return false; }
|
||||
// lvgl_port_lock expects milliseconds
|
||||
return lvgl_port_lock(timeoutTicks * portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
void lvgl_unlock(void) {
|
||||
if (!lvgl_is_running()) { return; }
|
||||
if (!initialized) { return; }
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
@ -41,6 +41,26 @@ void lvgl_keyboard_on_stop_lvgl() {
|
||||
keyboard_group = nullptr;
|
||||
}
|
||||
|
||||
// KeyboardKeyData::key is always a Unicode codepoint (see its doc comment / the CodePoint enum) -
|
||||
// drivers never emit LV_KEY_* directly, including for pure focus-navigation concepts that have no
|
||||
// ordinary character of their own. LVGL itself hardcodes specific sentinel values in its own
|
||||
// indev/group/textarea code that don't match the real Unicode codepoint chosen for the same key,
|
||||
// so those are translated here rather than each driver having to know about LVGL's internals.
|
||||
// CODEPOINT_BACKSPACE/TAB/ESCAPE/DELETE already equal their LV_KEY_* counterpart numerically, so
|
||||
// they need no case below - they fall through `default` unchanged.
|
||||
static uint32_t codepoint_to_lv_key(uint32_t key) {
|
||||
switch (key) {
|
||||
case CODEPOINT_ENTER: return LV_KEY_ENTER;
|
||||
case CODEPOINT_ARROW_LEFT: return LV_KEY_LEFT;
|
||||
case CODEPOINT_ARROW_UP: return LV_KEY_UP;
|
||||
case CODEPOINT_ARROW_RIGHT: return LV_KEY_RIGHT;
|
||||
case CODEPOINT_ARROW_DOWN: return LV_KEY_DOWN;
|
||||
case CODEPOINT_HOME: return LV_KEY_HOME;
|
||||
case CODEPOINT_END: return LV_KEY_END;
|
||||
default: return key;
|
||||
}
|
||||
}
|
||||
|
||||
static void lvgl_keyboard_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
|
||||
@ -51,8 +71,7 @@ static void lvgl_keyboard_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// KeyboardKeyData deliberately mirrors lv_indev_data_t's key/continue_reading fields, so no translation is needed.
|
||||
data->key = key_data.key;
|
||||
data->key = codepoint_to_lv_key(key_data.key);
|
||||
data->state = key_data.pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
||||
data->continue_reading = key_data.continue_reading;
|
||||
}
|
||||
|
||||
57
Modules/lvgl-module/source/lv_mem_custom.c
Normal file
57
Modules/lvgl-module/source/lv_mem_custom.c
Normal file
@ -0,0 +1,57 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// LVGL's custom stdlib allocator backend (LV_STDLIB_CUSTOM / CONFIG_LV_USE_CUSTOM_MALLOC) - routes
|
||||
// lv_malloc()/lv_realloc()/lv_free() through the kernel's memory_*_with_policy() functions instead
|
||||
// of a fixed-size private pool (LV_STDLIB_BUILTIN) or plain malloc (LV_STDLIB_CLIB), so LVGL grows
|
||||
// dynamically and prefers PSRAM when available instead of contending with everything else for a
|
||||
// small, fixed internal-RAM arena.
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <tactility/memory.h>
|
||||
|
||||
// PSRAM is preferred, not required: MEMORY_CAPABILITY_EXTERNAL is `desired`, so
|
||||
// memory_alloc_with_policy()/memory_realloc_with_policy() fall back to internal RAM automatically
|
||||
// on boards without PSRAM, or if PSRAM is exhausted.
|
||||
static const struct MemoryPolicy LVGL_MEMORY_POLICY = {
|
||||
.required = 0,
|
||||
.desired = MEMORY_CAPABILITY_EXTERNAL,
|
||||
.alignment = 0,
|
||||
};
|
||||
|
||||
void lv_mem_init(void) {
|
||||
}
|
||||
|
||||
void lv_mem_deinit(void) {
|
||||
}
|
||||
|
||||
lv_mem_pool_t lv_mem_add_pool(void* mem, size_t bytes) {
|
||||
// Not supported - memory_*_with_policy() owns allocation, LVGL doesn't need to manage its own
|
||||
// pools on top of it.
|
||||
(void)mem;
|
||||
(void)bytes;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void lv_mem_remove_pool(lv_mem_pool_t pool) {
|
||||
(void)pool;
|
||||
}
|
||||
|
||||
void* lv_malloc_core(size_t size) {
|
||||
return memory_alloc_with_policy(size, &LVGL_MEMORY_POLICY);
|
||||
}
|
||||
|
||||
void* lv_realloc_core(void* p, size_t new_size) {
|
||||
return memory_realloc_with_policy(p, new_size, &LVGL_MEMORY_POLICY);
|
||||
}
|
||||
|
||||
void lv_free_core(void* p) {
|
||||
memory_free(p);
|
||||
}
|
||||
|
||||
void lv_mem_monitor_core(lv_mem_monitor_t* mon_p) {
|
||||
// Not supported - memory_*_with_policy() doesn't expose LVGL-specific usage/fragmentation
|
||||
// stats (memory_print_stats() covers overall heap state instead).
|
||||
(void)mon_p;
|
||||
}
|
||||
|
||||
lv_result_t lv_mem_test_core(void) {
|
||||
return LV_RESULT_OK;
|
||||
}
|
||||
@ -85,6 +85,12 @@ static const uint8_t keycode2ascii[57][2] = {
|
||||
{'`', '~'}, {',', '<'}, {'.', '>'}, {'/', '?'},
|
||||
};
|
||||
|
||||
// Every key returns a real Unicode codepoint per KeyboardKeyData::key's contract - never an
|
||||
// LV_KEY_* (or USB_HID_KEY_*, which mirrors it) constant. Keys with no ordinary character of
|
||||
// their own use the CodePoint enum's standard Unicode symbol for the concept. lvgl-module's
|
||||
// keyboard.cpp translates all of these back to LVGL's own sentinels (CODEPOINT_ESCAPE/BACKSPACE/
|
||||
// DELETE already equal their LV_KEY_* counterpart numerically, so no translation is needed for
|
||||
// those).
|
||||
static uint32_t hid_keycode_to_key(uint8_t modifier, uint8_t key_code,
|
||||
bool caps_lock, bool num_lock) {
|
||||
bool shift = (modifier & HID_LEFT_SHIFT) || (modifier & HID_RIGHT_SHIFT);
|
||||
@ -92,33 +98,33 @@ static uint32_t hid_keycode_to_key(uint8_t modifier, uint8_t key_code,
|
||||
bool alt = (modifier & HID_LEFT_ALT) || (modifier & HID_RIGHT_ALT);
|
||||
|
||||
switch (key_code) {
|
||||
case HID_KEY_ENTER: return USB_HID_KEY_ENTER;
|
||||
case HID_KEY_ESC: return USB_HID_KEY_ESC;
|
||||
case HID_KEY_DEL: return USB_HID_KEY_BACKSPACE;
|
||||
case HID_KEY_DELETE: return USB_HID_KEY_DEL;
|
||||
case HID_KEY_TAB: return shift ? USB_HID_KEY_PREV : USB_HID_KEY_NEXT;
|
||||
case HID_KEY_UP: return USB_HID_KEY_UP;
|
||||
case HID_KEY_DOWN: return USB_HID_KEY_DOWN;
|
||||
case HID_KEY_LEFT: return USB_HID_KEY_LEFT;
|
||||
case HID_KEY_RIGHT: return USB_HID_KEY_RIGHT;
|
||||
case HID_KEY_HOME: return USB_HID_KEY_HOME;
|
||||
case HID_KEY_END: return USB_HID_KEY_END;
|
||||
case HID_KEY_KEYPAD_ENTER: return USB_HID_KEY_ENTER;
|
||||
case HID_KEY_ENTER: return CODEPOINT_ENTER;
|
||||
case HID_KEY_ESC: return CODEPOINT_ESCAPE;
|
||||
case HID_KEY_DEL: return CODEPOINT_BACKSPACE;
|
||||
case HID_KEY_DELETE: return CODEPOINT_DELETE;
|
||||
case HID_KEY_TAB: return '\t';
|
||||
case HID_KEY_UP: return CODEPOINT_ARROW_UP;
|
||||
case HID_KEY_DOWN: return CODEPOINT_ARROW_DOWN;
|
||||
case HID_KEY_LEFT: return CODEPOINT_ARROW_LEFT;
|
||||
case HID_KEY_RIGHT: return CODEPOINT_ARROW_RIGHT;
|
||||
case HID_KEY_HOME: return CODEPOINT_HOME;
|
||||
case HID_KEY_END: return CODEPOINT_END;
|
||||
case HID_KEY_KEYPAD_ENTER: return CODEPOINT_ENTER;
|
||||
case HID_KEY_KEYPAD_ADD: return '+';
|
||||
case HID_KEY_KEYPAD_SUB: return '-';
|
||||
case HID_KEY_KEYPAD_MUL: return '*';
|
||||
case HID_KEY_KEYPAD_DIV: return '/';
|
||||
case HID_KEY_KEYPAD_0: return num_lock ? (uint32_t)'0' : 0u;
|
||||
case HID_KEY_KEYPAD_1: return num_lock ? (uint32_t)'1' : (uint32_t)USB_HID_KEY_END;
|
||||
case HID_KEY_KEYPAD_2: return num_lock ? (uint32_t)'2' : (uint32_t)USB_HID_KEY_DOWN;
|
||||
case HID_KEY_KEYPAD_1: return num_lock ? (uint32_t)'1' : (uint32_t)CODEPOINT_END;
|
||||
case HID_KEY_KEYPAD_2: return num_lock ? (uint32_t)'2' : (uint32_t)CODEPOINT_ARROW_DOWN;
|
||||
case HID_KEY_KEYPAD_3: return num_lock ? (uint32_t)'3' : 0u;
|
||||
case HID_KEY_KEYPAD_4: return num_lock ? (uint32_t)'4' : (uint32_t)USB_HID_KEY_LEFT;
|
||||
case HID_KEY_KEYPAD_4: return num_lock ? (uint32_t)'4' : (uint32_t)CODEPOINT_ARROW_LEFT;
|
||||
case HID_KEY_KEYPAD_5: return num_lock ? (uint32_t)'5' : 0u;
|
||||
case HID_KEY_KEYPAD_6: return num_lock ? (uint32_t)'6' : (uint32_t)USB_HID_KEY_RIGHT;
|
||||
case HID_KEY_KEYPAD_7: return num_lock ? (uint32_t)'7' : (uint32_t)USB_HID_KEY_HOME;
|
||||
case HID_KEY_KEYPAD_8: return num_lock ? (uint32_t)'8' : (uint32_t)USB_HID_KEY_UP;
|
||||
case HID_KEY_KEYPAD_6: return num_lock ? (uint32_t)'6' : (uint32_t)CODEPOINT_ARROW_RIGHT;
|
||||
case HID_KEY_KEYPAD_7: return num_lock ? (uint32_t)'7' : (uint32_t)CODEPOINT_HOME;
|
||||
case HID_KEY_KEYPAD_8: return num_lock ? (uint32_t)'8' : (uint32_t)CODEPOINT_ARROW_UP;
|
||||
case HID_KEY_KEYPAD_9: return num_lock ? (uint32_t)'9' : 0u;
|
||||
case HID_KEY_KEYPAD_DELETE: return num_lock ? (uint32_t)'.' : (uint32_t)USB_HID_KEY_DEL;
|
||||
case HID_KEY_KEYPAD_DELETE: return num_lock ? (uint32_t)'.' : (uint32_t)CODEPOINT_DELETE;
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
||||
@ -74,6 +74,7 @@ constexpr auto* TAG = "Tactility";
|
||||
static DispatcherHandle_t mainDispatcherHandle = dispatcher_alloc();
|
||||
|
||||
void initFileMutexForLvgl();
|
||||
void deinitFileMutexForLvgl();
|
||||
|
||||
namespace {
|
||||
|
||||
@ -423,6 +424,8 @@ static void applySavedTouchCalibration() {
|
||||
#endif // CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED
|
||||
|
||||
static void onLvglStarted() {
|
||||
initFileMutexForLvgl();
|
||||
|
||||
window_manager_configure(windowManagerScreenInit);
|
||||
check(module_ensure_started(&lvgl_window_manager_module) == ERROR_NONE);
|
||||
|
||||
@ -453,6 +456,8 @@ static void onLvglStarted() {
|
||||
}
|
||||
|
||||
static void onLvglStopped() {
|
||||
deinitFileMutexForLvgl();
|
||||
|
||||
if (softwareKeyboard.object != nullptr) {
|
||||
lvgl_software_keyboard_destruct(&softwareKeyboard);
|
||||
}
|
||||
@ -510,9 +515,6 @@ void run(Module* const dtsModules[], const DtsDevice dtsDevices[]) {
|
||||
|
||||
registerAndStartServices();
|
||||
|
||||
// Must start right before LVGL
|
||||
initFileMutexForLvgl();
|
||||
|
||||
lvgl_module_configure((LvglModuleConfig) {
|
||||
.on_start = onLvglStarted,
|
||||
.on_stop = onLvglStopped,
|
||||
|
||||
@ -273,7 +273,6 @@ void runBootSequence(TickType_t startTime) {
|
||||
#endif
|
||||
|
||||
if (!setupUsbBootMode()) {
|
||||
LOG_I(TAG, "initFromBootApp");
|
||||
registerApps();
|
||||
waitForMinimalSplashDuration(startTime);
|
||||
startNextApp();
|
||||
|
||||
@ -6,12 +6,16 @@
|
||||
#include <tactility/filesystem/file_system.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
constexpr auto* TAG = "file_mutex_lvgl";
|
||||
|
||||
struct Device;
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<FileMutexId> registered_ids;
|
||||
|
||||
void wrapped_lvgl_lock() {
|
||||
if (!lvgl_is_running()) return;
|
||||
lvgl_lock();
|
||||
@ -43,7 +47,7 @@ namespace tt {
|
||||
* If the SPI controller has a display on the bus, we create an LVGL lock for the file system path.
|
||||
*/
|
||||
void initFileMutexForLvgl() {
|
||||
file_system_for_each(nullptr, [](FileSystem* fs, void* context) {
|
||||
file_system_for_each(®istered_ids, [](FileSystem* fs, void* context) {
|
||||
char mount_path[64];
|
||||
if (file_system_get_path(fs, mount_path, sizeof(mount_path)) != ERROR_NONE) {
|
||||
return true;
|
||||
@ -77,17 +81,15 @@ void initFileMutexForLvgl() {
|
||||
|
||||
struct Context {
|
||||
const char* mountPath;
|
||||
std::vector<FileMutexId>* registeredIds;
|
||||
};
|
||||
Context ctx = { .mountPath = mount_path };
|
||||
Context ctx = { .mountPath = mount_path, .registeredIds = static_cast<std::vector<FileMutexId>*>(context) };
|
||||
|
||||
device_for_each_child(parent, &ctx, [](Device* child, void* context) -> bool {
|
||||
Context* ctx = static_cast<Context*>(context);
|
||||
if (device_get_type(child) == &DISPLAY_TYPE) {
|
||||
LOG_I(TAG, "Adding file mutex for %s as it shares a bus with a display", ctx->mountPath);
|
||||
file_mutex_register(
|
||||
&lvgl_mutex,
|
||||
ctx->mountPath
|
||||
);
|
||||
ctx->registeredIds->push_back(file_mutex_add(&lvgl_mutex, ctx->mountPath));
|
||||
return false;
|
||||
} else {
|
||||
LOG_D(TAG, "child of parent, %s: not DISPLAY_TYPE", child->name);
|
||||
@ -105,7 +107,7 @@ void initFileMutexForLvgl() {
|
||||
return;
|
||||
}
|
||||
|
||||
file_system_for_each(nullptr, [](FileSystem* fs, void* context) {
|
||||
file_system_for_each(®istered_ids, [](FileSystem* fs, void* context) {
|
||||
char mount_path[64];
|
||||
if (file_system_get_path(fs, mount_path, sizeof(mount_path)) != ERROR_NONE) {
|
||||
return true;
|
||||
@ -117,9 +119,17 @@ void initFileMutexForLvgl() {
|
||||
}
|
||||
|
||||
LOG_I(TAG, "Adding file mutex for %s (SD card) - a display is present and may contend for bus/DMA resources", mount_path);
|
||||
file_mutex_register(&lvgl_mutex, mount_path);
|
||||
auto* ids = static_cast<std::vector<FileMutexId>*>(context);
|
||||
ids->push_back(file_mutex_add(&lvgl_mutex, mount_path));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void deinitFileMutexForLvgl() {
|
||||
for (FileMutexId id : registered_ids) {
|
||||
file_mutex_remove(id);
|
||||
}
|
||||
registered_ids.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -11,11 +11,43 @@ extern "C" {
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/error.h>
|
||||
|
||||
/**
|
||||
* @brief Named Unicode codepoints for KeyboardKeyData::key.
|
||||
*
|
||||
* Keys that produce an ordinary character use that character's own codepoint directly (e.g. 'a',
|
||||
* ' ') and don't need a name here. These are the standard Unicode symbol codepoints used instead
|
||||
* of LVGL's LV_KEY_* sentinels for keys with no character of their own (arrows, Home/End,
|
||||
* Tab/Shift+Tab-as-focus-navigation), plus names for the handful of C0 control keys every driver
|
||||
* needs (Enter/Escape/Backspace/Delete/Tab). Modules/lvgl-module/source/devices/keyboard.cpp
|
||||
* translates these back into LV_KEY_* internally for LVGL.
|
||||
*/
|
||||
typedef enum {
|
||||
CODEPOINT_ENTER = '\r',
|
||||
CODEPOINT_ESCAPE = '\x1B',
|
||||
CODEPOINT_BACKSPACE = '\b',
|
||||
CODEPOINT_DELETE = '\x7F',
|
||||
CODEPOINT_TAB = '\t',
|
||||
CODEPOINT_ARROW_LEFT = 0x2190,
|
||||
CODEPOINT_ARROW_UP = 0x2191,
|
||||
CODEPOINT_ARROW_RIGHT = 0x2192,
|
||||
CODEPOINT_ARROW_DOWN = 0x2193,
|
||||
CODEPOINT_HOME = 0x21F1,
|
||||
CODEPOINT_END = 0x21F2,
|
||||
} CodePoint;
|
||||
|
||||
/**
|
||||
* @brief A single key event read from a keyboard device.
|
||||
*/
|
||||
struct KeyboardKeyData {
|
||||
/** @brief The key code. Driver-defined (e.g. ASCII/UTF-8 codepoint, scan code, or LVGL key code). */
|
||||
/**
|
||||
* @brief The key. Always a Unicode codepoint - never a raw scan code.
|
||||
*
|
||||
* For a key that produces a character, this is that character's codepoint (e.g. 'a', ' '). For
|
||||
* a key with no character representation, or one of the handful of C0 control keys every
|
||||
* driver needs, this is one of the CodePoint enum values above - never an LVGL LV_KEY_*
|
||||
* constant directly. See ctrl's doc comment below for the CodePoint values that numerically
|
||||
* collide with real C0 control codes.
|
||||
*/
|
||||
uint32_t key;
|
||||
/** @brief True if the key was pressed, false if released. */
|
||||
bool pressed;
|
||||
@ -29,9 +61,9 @@ struct KeyboardKeyData {
|
||||
*
|
||||
* Reported separately rather than folded into `key` because the two encodings collide: the C0
|
||||
* control codes a terminal expects for Ctrl chords (Ctrl+C is 0x03, Ctrl+K is 0x0B, ...) overlap
|
||||
* the LVGL key constants drivers emit in the same field (LV_KEY_END is 3, LV_KEY_PREV is 11,
|
||||
* LV_KEY_UP is 17, ...), so a single uint32_t cannot express both. Consumers that want control
|
||||
* codes derive them here, e.g.
|
||||
* the four CodePoint values that remain in true C0 range (CODEPOINT_ENTER is Ctrl+M/13,
|
||||
* CODEPOINT_BACKSPACE is Ctrl+H/8, CODEPOINT_TAB is Ctrl+I/9, CODEPOINT_ESCAPE is Ctrl+[/27), so
|
||||
* a single uint32_t cannot express both. Consumers that want control codes derive them here, e.g.
|
||||
* `((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z')) ? (key & 0x1F) : key`
|
||||
* when ctrl is set.
|
||||
*
|
||||
@ -48,8 +80,8 @@ struct KeyboardKeyData {
|
||||
* or 0 if this driver doesn't compute one (most don't - `key` is the only field most consumers
|
||||
* need). Populated by drivers whose hardware layout maps cleanly onto HID usage codes, so
|
||||
* consumers that want to mirror physical key presses as real HID reports (e.g. USB HID output)
|
||||
* don't have to reverse-engineer one out of `key`'s LVGL/ASCII encoding - which is lossy for
|
||||
* keys with no ASCII/LVGL representation at all, e.g. F1-F12.
|
||||
* don't have to reverse-engineer one out of `key`'s codepoint encoding - which is lossy for
|
||||
* keys with no Unicode/LVGL representation at all, e.g. F1-F12.
|
||||
*/
|
||||
uint8_t hid_keycode;
|
||||
/**
|
||||
@ -83,7 +115,7 @@ struct KeyboardApi {
|
||||
|
||||
/**
|
||||
* @brief Optional: reports whether the keyboard is physically present right now. Only
|
||||
* meaningful for hot-pluggable/detachable keyboards (e.g. a removable accessory) whose
|
||||
* meaningful for hot-pluggable/detachable keyboarcardputer_keyboard.cppds (e.g. a removable accessory) whose
|
||||
* kernel device is constructed and started once at boot regardless of physical attachment -
|
||||
* leave NULL for a keyboard that's always physically present whenever its device is active
|
||||
* (the common case; callers must treat NULL the same as "always present").
|
||||
|
||||
@ -19,13 +19,25 @@ struct FileMutex {
|
||||
void (*unlock)();
|
||||
};
|
||||
|
||||
typedef uint32_t FileMutexId;
|
||||
|
||||
#define FILE_MUTEX_ID_INVALID ((FileMutexId)0)
|
||||
|
||||
/**
|
||||
* @brief Registers a mutex for a mount path (e.g. "/sdcard") and its descendants.
|
||||
* @param[in] mutex callbacks to associate with the path; a copy is stored
|
||||
* @param[in] path mount path this mutex serializes access to
|
||||
* @note No-op if a mutex is already registered for this exact path.
|
||||
* @return the id of the new entry, or the id of the existing entry if path is already registered
|
||||
* @note If a mutex is already registered for this exact path, no new entry is created and the
|
||||
* existing entry's id is returned; the existing callbacks are left unchanged.
|
||||
*/
|
||||
void file_mutex_register(const struct FileMutex* mutex, const char* path);
|
||||
FileMutexId file_mutex_add(const struct FileMutex* mutex, const char* path);
|
||||
|
||||
/**
|
||||
* @brief Removes a previously added mutex registration.
|
||||
* @param[in] id id returned by file_mutex_add(); a stale or unknown id is a no-op
|
||||
*/
|
||||
void file_mutex_remove(FileMutexId id);
|
||||
|
||||
/**
|
||||
* @brief Looks up the mutex registered for path or one of its ancestor mount paths.
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/filesystem/file_mutex.h>
|
||||
#include <tactility/concurrent/mutex.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -12,41 +14,89 @@ static const FileMutex no_mutex = {
|
||||
};
|
||||
|
||||
struct FileMutexEntry {
|
||||
FileMutexId id;
|
||||
std::string path;
|
||||
FileMutex mutex;
|
||||
};
|
||||
|
||||
static std::vector<FileMutexEntry> mutex_entries;
|
||||
// Guards mutex_entries against concurrent add/get/remove; unrelated to whether a FileMutex's own
|
||||
// lock/unlock is currently held (file_mutex_get() hands out a copy that stays valid regardless of
|
||||
// later registry changes - see file_mutex_remove()).
|
||||
struct FileMutexLedger {
|
||||
std::vector<FileMutexEntry> entries;
|
||||
FileMutexId next_id = 1;
|
||||
Mutex mutex {};
|
||||
|
||||
FileMutexLedger() { mutex_construct(&mutex); }
|
||||
~FileMutexLedger() { mutex_destruct(&mutex); }
|
||||
|
||||
void lock() { mutex_lock(&mutex); }
|
||||
void unlock() { mutex_unlock(&mutex); }
|
||||
};
|
||||
|
||||
static FileMutexLedger& get_ledger() {
|
||||
static FileMutexLedger ledger;
|
||||
return ledger;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void file_mutex_register(const FileMutex* mutex, const char* path) {
|
||||
// Skip if entry for path exists
|
||||
for (auto& entry : mutex_entries) {
|
||||
FileMutexId file_mutex_add(const FileMutex* mutex, const char* path) {
|
||||
auto& ledger = get_ledger();
|
||||
ledger.lock();
|
||||
|
||||
for (auto& entry : ledger.entries) {
|
||||
if (entry.path == path) {
|
||||
return;
|
||||
FileMutexId existing_id = entry.id;
|
||||
ledger.unlock();
|
||||
return existing_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Store a copy of the entry
|
||||
mutex_entries.push_back({
|
||||
FileMutexId new_id = ledger.next_id++;
|
||||
ledger.entries.push_back({
|
||||
.id = new_id,
|
||||
.path = path,
|
||||
.mutex = *mutex
|
||||
});
|
||||
|
||||
ledger.unlock();
|
||||
return new_id;
|
||||
}
|
||||
|
||||
void file_mutex_remove(FileMutexId id) {
|
||||
auto& ledger = get_ledger();
|
||||
ledger.lock();
|
||||
|
||||
const auto iterator = std::ranges::find_if(ledger.entries, [id](const FileMutexEntry& entry) {
|
||||
return entry.id == id;
|
||||
});
|
||||
if (iterator != ledger.entries.end()) {
|
||||
// Plain erase, not swap-and-pop: file_mutex_get() matches first-registered-wins, so
|
||||
// removal must preserve the relative order of the remaining entries.
|
||||
ledger.entries.erase(iterator);
|
||||
}
|
||||
|
||||
ledger.unlock();
|
||||
}
|
||||
|
||||
void file_mutex_get(FileMutex* mutex, const char* path) {
|
||||
auto& ledger = get_ledger();
|
||||
std::string path_string = path;
|
||||
for (auto& entry : mutex_entries) {
|
||||
|
||||
ledger.lock();
|
||||
for (auto& entry : ledger.entries) {
|
||||
// Match the mount path itself, or a descendant (e.g. "/sdcard" registered, "/sdcard/config.json" requested).
|
||||
bool is_match = path_string == entry.path ||
|
||||
(entry.path == "/" && !path_string.empty() && path_string[0] == '/') ||
|
||||
(path_string.rfind(entry.path, 0) == 0 && path_string[entry.path.size()] == '/');
|
||||
if (is_match) {
|
||||
memcpy(mutex, &entry.mutex, sizeof(FileMutex));
|
||||
ledger.unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
ledger.unlock();
|
||||
|
||||
*mutex = no_mutex;
|
||||
}
|
||||
|
||||
@ -25,18 +25,22 @@ void* memory_alloc_with_policy(size_t size, const struct MemoryPolicy* policy) {
|
||||
uint32_t required_caps = toHeapCaps(policy->required);
|
||||
uint32_t desired_caps = toHeapCaps(policy->desired);
|
||||
|
||||
// heap_caps matches heaps via (heap->caps[prio] & caps) != 0 - a caps value of 0 (e.g.
|
||||
// required_caps when policy->required wasn't set) can never match any heap, so the fallback
|
||||
// must OR in MALLOC_CAP_DEFAULT to actually reach a general-purpose heap, same as ESP-IDF's
|
||||
// own heap_caps_malloc_default() does.
|
||||
void* ptr;
|
||||
if (policy->alignment > 0) {
|
||||
ptr = heap_caps_aligned_alloc(policy->alignment, size, required_caps | desired_caps);
|
||||
if (ptr == nullptr && desired_caps != 0) {
|
||||
// Desired caps couldn't be satisfied alongside the required ones - retry with
|
||||
// required only, since desired is explicitly optional.
|
||||
ptr = heap_caps_aligned_alloc(policy->alignment, size, required_caps);
|
||||
ptr = heap_caps_aligned_alloc(policy->alignment, size, required_caps | MALLOC_CAP_DEFAULT);
|
||||
}
|
||||
} else {
|
||||
ptr = heap_caps_malloc(size, required_caps | desired_caps);
|
||||
if (ptr == nullptr && desired_caps != 0) {
|
||||
ptr = heap_caps_malloc(size, required_caps);
|
||||
ptr = heap_caps_malloc(size, required_caps | MALLOC_CAP_DEFAULT);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
@ -50,7 +54,7 @@ void* memory_realloc_with_policy(void* ptr, size_t size, const struct MemoryPoli
|
||||
// on fresh allocations (memory_alloc_with_policy/memory_calloc_with_policy).
|
||||
void* result = heap_caps_realloc(ptr, size, required_caps | desired_caps);
|
||||
if (result == nullptr && desired_caps != 0) {
|
||||
result = heap_caps_realloc(ptr, size, required_caps);
|
||||
result = heap_caps_realloc(ptr, size, required_caps | MALLOC_CAP_DEFAULT);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -63,12 +67,12 @@ void* memory_calloc_with_policy(size_t count, size_t size, const struct MemoryPo
|
||||
if (policy->alignment > 0) {
|
||||
ptr = heap_caps_aligned_calloc(policy->alignment, count, size, required_caps | desired_caps);
|
||||
if (ptr == nullptr && desired_caps != 0) {
|
||||
ptr = heap_caps_aligned_calloc(policy->alignment, count, size, required_caps);
|
||||
ptr = heap_caps_aligned_calloc(policy->alignment, count, size, required_caps | MALLOC_CAP_DEFAULT);
|
||||
}
|
||||
} else {
|
||||
ptr = heap_caps_calloc(count, size, required_caps | desired_caps);
|
||||
if (ptr == nullptr && desired_caps != 0) {
|
||||
ptr = heap_caps_calloc(count, size, required_caps);
|
||||
ptr = heap_caps_calloc(count, size, required_caps | MALLOC_CAP_DEFAULT);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
|
||||
@ -169,7 +169,8 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
||||
DEFINE_MODULE_SYMBOL(display_get_backlight),
|
||||
DEFINE_MODULE_SYMBOL(DISPLAY_TYPE),
|
||||
// file_mutex
|
||||
DEFINE_MODULE_SYMBOL(file_mutex_register),
|
||||
DEFINE_MODULE_SYMBOL(file_mutex_add),
|
||||
DEFINE_MODULE_SYMBOL(file_mutex_remove),
|
||||
DEFINE_MODULE_SYMBOL(file_mutex_get),
|
||||
DEFINE_MODULE_SYMBOL(file_mutex_lock),
|
||||
DEFINE_MODULE_SYMBOL(file_mutex_try_lock),
|
||||
|
||||
@ -48,10 +48,10 @@ TEST_CASE("file_mutex_get with zero registrations returns a no-op mutex") {
|
||||
file_mutex_unlock(&mutex);
|
||||
}
|
||||
|
||||
TEST_CASE("file_mutex_register/get with a single registration") {
|
||||
TEST_CASE("file_mutex_add/get with a single registration") {
|
||||
reset_mocks();
|
||||
FileMutex registered = { .lock = mock_lock, .try_lock = mock_try_lock, .unlock = mock_unlock };
|
||||
file_mutex_register(®istered, "/mock1");
|
||||
file_mutex_add(®istered, "/mock1");
|
||||
|
||||
FileMutex mutex;
|
||||
|
||||
@ -87,19 +87,19 @@ TEST_CASE("file_mutex_register/get with a single registration") {
|
||||
|
||||
// Re-registering the same path is a no-op: original callbacks remain in place.
|
||||
FileMutex replacement = { .lock = nullptr, .try_lock = nullptr, .unlock = nullptr };
|
||||
file_mutex_register(&replacement, "/mock1");
|
||||
file_mutex_add(&replacement, "/mock1");
|
||||
file_mutex_get(&mutex, "/mock1");
|
||||
CHECK_EQ(mutex.lock, mock_lock);
|
||||
}
|
||||
|
||||
TEST_CASE("file_mutex_register/get with two registrations resolves to the matching path") {
|
||||
TEST_CASE("file_mutex_add/get with two registrations resolves to the matching path") {
|
||||
reset_mocks();
|
||||
|
||||
FileMutex mutex_a = { .lock = mock_lock_a, .try_lock = nullptr, .unlock = nullptr };
|
||||
FileMutex mutex_b = { .lock = mock_lock_b, .try_lock = nullptr, .unlock = nullptr };
|
||||
|
||||
file_mutex_register(&mutex_a, "/mock2a");
|
||||
file_mutex_register(&mutex_b, "/mock2b");
|
||||
file_mutex_add(&mutex_a, "/mock2a");
|
||||
file_mutex_add(&mutex_b, "/mock2b");
|
||||
|
||||
FileMutex resolved;
|
||||
|
||||
@ -116,7 +116,75 @@ TEST_CASE("file_mutex_register/get with two registrations resolves to the matchi
|
||||
// Registration order matters: the first matching entry wins, not the longest
|
||||
// prefix. A mount nested under an earlier one is shadowed by it.
|
||||
FileMutex mutex_nested = { .lock = nullptr, .try_lock = nullptr, .unlock = nullptr };
|
||||
file_mutex_register(&mutex_nested, "/mock2a/nested");
|
||||
file_mutex_add(&mutex_nested, "/mock2a/nested");
|
||||
file_mutex_get(&resolved, "/mock2a/nested/file.txt");
|
||||
CHECK_EQ(resolved.lock, mock_lock_a); // still /mock2a, registered first
|
||||
}
|
||||
|
||||
TEST_CASE("file_mutex_add returns a valid id") {
|
||||
reset_mocks();
|
||||
FileMutex registered = { .lock = mock_lock, .try_lock = nullptr, .unlock = nullptr };
|
||||
FileMutexId id = file_mutex_add(®istered, "/mockA");
|
||||
CHECK_NE(id, FILE_MUTEX_ID_INVALID);
|
||||
}
|
||||
|
||||
TEST_CASE("file_mutex_add with a duplicate path returns the existing id") {
|
||||
reset_mocks();
|
||||
FileMutex mutex_1 = { .lock = mock_lock, .try_lock = nullptr, .unlock = nullptr };
|
||||
FileMutex mutex_2 = { .lock = mock_lock_a, .try_lock = nullptr, .unlock = nullptr };
|
||||
|
||||
FileMutexId id_1 = file_mutex_add(&mutex_1, "/mockB");
|
||||
FileMutexId id_2 = file_mutex_add(&mutex_2, "/mockB");
|
||||
CHECK_EQ(id_1, id_2);
|
||||
|
||||
FileMutex resolved;
|
||||
file_mutex_get(&resolved, "/mockB");
|
||||
CHECK_EQ(resolved.lock, mock_lock); // first registration's callbacks win, unchanged
|
||||
}
|
||||
|
||||
TEST_CASE("file_mutex_remove removes a registration") {
|
||||
reset_mocks();
|
||||
FileMutex registered = { .lock = mock_lock, .try_lock = nullptr, .unlock = nullptr };
|
||||
FileMutexId id = file_mutex_add(®istered, "/mockC");
|
||||
|
||||
FileMutex before;
|
||||
file_mutex_get(&before, "/mockC");
|
||||
CHECK_EQ(before.lock, mock_lock);
|
||||
|
||||
file_mutex_remove(id);
|
||||
|
||||
FileMutex after;
|
||||
file_mutex_get(&after, "/mockC");
|
||||
CHECK_EQ(after.lock, nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("file_mutex_remove with an unknown id is a safe no-op") {
|
||||
reset_mocks();
|
||||
FileMutex registered = { .lock = mock_lock, .try_lock = nullptr, .unlock = nullptr };
|
||||
file_mutex_add(®istered, "/mockD");
|
||||
|
||||
file_mutex_remove(999999); // never issued
|
||||
file_mutex_remove(FILE_MUTEX_ID_INVALID);
|
||||
|
||||
FileMutex resolved;
|
||||
file_mutex_get(&resolved, "/mockD");
|
||||
CHECK_EQ(resolved.lock, mock_lock); // untouched
|
||||
}
|
||||
|
||||
TEST_CASE("file_mutex_remove of one registration leaves others intact") {
|
||||
reset_mocks();
|
||||
FileMutex mutex_a = { .lock = mock_lock_a, .try_lock = nullptr, .unlock = nullptr };
|
||||
FileMutex mutex_b = { .lock = mock_lock_b, .try_lock = nullptr, .unlock = nullptr };
|
||||
FileMutexId id_a = file_mutex_add(&mutex_a, "/mockE1");
|
||||
file_mutex_add(&mutex_b, "/mockE2");
|
||||
|
||||
file_mutex_remove(id_a);
|
||||
|
||||
FileMutex resolved_a;
|
||||
file_mutex_get(&resolved_a, "/mockE1");
|
||||
CHECK_EQ(resolved_a.lock, nullptr);
|
||||
|
||||
FileMutex resolved_b;
|
||||
file_mutex_get(&resolved_b, "/mockE2");
|
||||
CHECK_EQ(resolved_b.lock, mock_lock_b);
|
||||
}
|
||||
|
||||
@ -38,14 +38,14 @@
|
||||
* - LV_STDLIB_RTTHREAD: RT-Thread implementation
|
||||
* - LV_STDLIB_CUSTOM: Implement the functions externally
|
||||
*/
|
||||
#define LV_USE_STDLIB_MALLOC LV_STDLIB_CLIB
|
||||
#define LV_USE_STDLIB_MALLOC LV_STDLIB_CUSTOM
|
||||
#define LV_USE_STDLIB_STRING LV_STDLIB_BUILTIN
|
||||
#define LV_USE_STDLIB_SPRINTF LV_STDLIB_BUILTIN
|
||||
|
||||
|
||||
#if LV_USE_STDLIB_MALLOC == LV_STDLIB_BUILTIN
|
||||
/*Size of the memory available for `lv_malloc()` in bytes (>= 2kB)*/
|
||||
#define LV_MEM_SIZE (64 * 1024U) /*[bytes]*/
|
||||
#define LV_MEM_SIZE (128 * 1024U) /*[bytes]*/
|
||||
|
||||
/*Size of the memory expand for `lv_malloc()` in bytes*/
|
||||
#define LV_MEM_POOL_EXPAND_SIZE 0
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user