Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .claude/rules/lvgl.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,21 @@ Two kernel modules cover LVGL:

- **`lvgl-module`** (`Modules/lvgl-module/`, `<lvgl/*.h>`) owns LVGL's lifecycle: init/deinit, the LVGL task loop, and `lvgl_lock()`/`lvgl_try_lock()`/`lvgl_unlock()` mutex-based locking that any task must hold before touching LVGL objects. It bridges Tactility's device model to LVGL indevs (`lvgl/devices/*.h`: `display`, `pointer`, `keyboard`, `trackball`), and provides shared fonts (`lvgl/fonts.h`: Montserrat text sizes, Material Symbols icon sets for statusbar/launcher/shared use) and a few shared widgets (`lvgl/widgets/*.h`: `toolbar`, `spinner`, `sliderbox`).
- **`lvgl-window-manager-module`** (`Modules/lvgl-window-manager-module/`, `<lvgl_window_manager/*.h>`) manages a single stacked window per app instance on top of `lvgl-module`. `window_manager_start()`/`window_manager_stop()` create/tear down the root widget (plus optional chrome from a configured `WindowManagerScreenInitFn`); `window_manager_create()`/`window_manager_remove()` push/pop an app's window and (re)populate it via a `WindowCreateWidgetsFn`. Only the topmost window ever has live widgets - burying and resurfacing a window deletes and rebuilds its widget tree rather than hiding/showing it. That rebuild-on-remove path can run `create_widgets` on a *different* app's thread (whichever app's `window_manager_remove()` call caused this window to resurface), so `create_widgets` must only rebuild already-committed state, never decide what happens next - state transitions belong in the app's own `main()` event loop, driven by real `APP_EVENT_RESULT`s.

## Keyboard navigation

- Put groups of related focusable widgets (pills/chips, rows of buttons, toggle tiles) in a grid navigation container with `lvgl_grid_navigation_add()` (`lvgl/grid_navigation.h`). Only the container joins the input group: left/right moves within the group and up/down moves between groups.
- Never make checkable widgets (switches, checkable buttons) direct input group members: LVGL toggles them on arrow keys. Grid navigation forwards only Enter to them.

## Theming (LSS)

The themes in `Modules/lvgl-module/source/themes/lv_theme_material*.c/.h` are generated: never edit them directly. They're compiled from the LVGL Style Sheets in `Modules/lvgl-module/themes/*.lss` by the LSS tool, a git submodule at `Modules/lvgl-module/lss/` (see its `README.md` for the syntax).

- Styles are defined once and attached to widget types with `apply(<widget>) { <style>: <state/part>; }` rules. Custom widget classes of `lvgl-module` (e.g. `lvgl_card_class`) are declared with `@widget <name>: <class>;`.
- Change `material.lss` and `material_mono.lss` together, then regenerate (requires `pip install lark`):

```bash
cd Modules/lvgl-module
python lss/compile.py themes/material.lss --output source/themes
python lss/compile.py themes/material_mono.lss --output source/themes
```
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
.idea/
.DS_Store

build*/
!.github/actions/build*/
Expand All @@ -8,6 +7,8 @@ CMakeCache.txt
*.cbp
CMakeFiles

Data/data

release/

sdkconfig
Expand All @@ -28,4 +29,6 @@ sdkconfig.board.*.dev
.ai/mcp
.clinerules/

__pycache__
__pycache__
.DS_Store
.cache
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
[submodule "Libraries/cJSON/cJSON"]
path = Libraries/cJSON/cJSON
url = https://github.com/DaveGamble/cJSON.git
[submodule "Modules/lvgl-module/lss"]
path = Modules/lvgl-module/lss
url = https://github.com/ByteWelder/LSS.git
1 change: 1 addition & 0 deletions Buildscripts/release-sdk-modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ lvgl-window-manager-module
service-module
graphics-module
wifi-module
binfont-module
1 change: 1 addition & 0 deletions Data/system/fonts/MaterialSymbolsRounded.codepoints
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,4 @@ terminal eb8e
toolbar e9f7
usb e1e0
wifi e63e
wifi_off e648
Binary file modified Data/system/fonts/MaterialSymbolsRounded.ttf
Binary file not shown.
15 changes: 14 additions & 1 deletion Devices/cl32-v02-v03/cl32-v02-v03.dts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/dts-v1/;

#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c_master.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_usbdevice.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/esp32_wifi.h>

Expand All @@ -22,7 +24,18 @@

wifi0 {
compatible = "espressif,esp32-wifi";
status = "disabled";
};

usbdevice0 {
compatible = "espressif,esp32-usbdevice";

usbdevicemsc0 {
compatible = "espressif,esp32-usbdevice-msc";
};
};

ble0 {
compatible = "espressif,esp32-ble";
};

// Top Stemma/Qwiic port 1 shares this bus with the RTC (SDA: pin 1 <-> SCL: pin 2). External
Expand Down
1 change: 1 addition & 0 deletions Devices/cl32-v02-v03/device.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ hardware.spiRamSpeed=80M
hardware.spiRamXipDisabled=true
hardware.esptoolFlashFreq=80M
hardware.bluetooth=true
hardware.tinyUsbMsc=true

storage.userDataLocation=SD

Expand Down
15 changes: 14 additions & 1 deletion Devices/cl32-v04/cl32-v04.dts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/dts-v1/;

#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c_master.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_usbdevice.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/esp32_wifi.h>

Expand All @@ -25,7 +27,18 @@

wifi0 {
compatible = "espressif,esp32-wifi";
status = "disabled";
};

usbdevice0 {
compatible = "espressif,esp32-usbdevice";

usbdevicemsc0 {
compatible = "espressif,esp32-usbdevice-msc";
};
};

ble0 {
compatible = "espressif,esp32-ble";
};

// Top Stemma/Qwiic port 1 shares this bus with the RTC (SDA: pin 1 <-> SCL: pin 2). External
Expand Down
1 change: 1 addition & 0 deletions Devices/cl32-v04/device.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ hardware.spiRamSpeed=80M
hardware.spiRamXipDisabled=true
hardware.esptoolFlashFreq=80M
hardware.bluetooth=true
hardware.tinyUsbMsc=true

storage.userDataLocation=SD

Expand Down
1 change: 0 additions & 1 deletion Devices/guition-jc1060p470ciwy/guition,jc1060p470ciwy.dts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@

wifi0 {
compatible = "espressif,esp32-wifi";
status = "disabled";
};

ble0 {
Expand Down
1 change: 0 additions & 1 deletion Devices/lilygo-tlora-pager/device.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,4 @@ display.dpi=227

lvgl.colorDepth=16
lvgl.dpi=150
lvgl.uiDensity=compact
lvgl.fontSize=16
3 changes: 3 additions & 0 deletions Devices/m5stack-cardputer-adv/device.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ display.dpi=139

lvgl.colorDepth=16
lvgl.uiDensity=compact

ui.systemBarsPortrait=split
ui.systemBarsLandscape=split
3 changes: 3 additions & 0 deletions Devices/m5stack-cardputer/device.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ display.dpi=139
lvgl.colorDepth=16
lvgl.uiDensity=compact

ui.systemBarsPortrait=split
ui.systemBarsLandscape=split

sdkconfig.CONFIG_CODEC_DUMMY_SUPPORT=y
3 changes: 3 additions & 0 deletions Devices/m5stack-stickc-plus2/device.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ display.dpi=242

lvgl.colorDepth=16
lvgl.uiDensity=compact

ui.systemBarsPortrait=split
ui.systemBarsLandscape=split
3 changes: 3 additions & 0 deletions Devices/m5stack-sticks3/device.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ display.dpi=242

lvgl.colorDepth=16
lvgl.uiDensity=compact

ui.systemBarsPortrait=split
ui.systemBarsLandscape=split
1 change: 0 additions & 1 deletion Devices/m5stack-tab5/m5stack,tab5.dts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@

wifi0 {
compatible = "espressif,esp32-wifi";
status = "disabled";
};

ble0 {
Expand Down
2 changes: 1 addition & 1 deletion Devices/simulator/Source/module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ static Driver* const simulator_drivers[] = {
// These devices have no real bus to attach to (SDL has no notion of one), but every non-root
// device is still expected to have a parent (see Device::parent) - they're parented to root once
// it's available below.
static const SdlDisplayConfig sdl_display_config = { 640, 480 };
static const SdlDisplayConfig sdl_display_config = { 640, 320 };
static Device sdl_display_device {};
static Device sdl_pointer_device {};
static Device sdl_keyboard_device {};
Expand Down
3 changes: 3 additions & 0 deletions Devices/waveshare-esp32-s3-geek/device.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ display.dpi=143

lvgl.colorDepth=16
lvgl.uiDensity=compact

ui.systemBarsPortrait=split
ui.systemBarsLandscape=split
3 changes: 3 additions & 0 deletions Devices/waveshare-s3-lcd-13/device.properties
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ display.dpi=261

lvgl.colorDepth=16
lvgl.uiDensity=compact

ui.systemBarsPortrait=split
ui.systemBarsLandscape=split
3 changes: 3 additions & 0 deletions Devices/waveshare-s3-touch-lcd-128/device.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ display.dpi=265

lvgl.colorDepth=16
lvgl.uiDensity=compact

ui.systemBarsPortrait=split
ui.systemBarsLandscape=split
3 changes: 3 additions & 0 deletions Devices/waveshare-s3-touch-lcd-147/device.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ display.dpi=247

lvgl.colorDepth=16
lvgl.uiDensity=compact

ui.systemBarsPortrait=split
ui.systemBarsLandscape=split
9 changes: 2 additions & 7 deletions Documentation/ideas.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@

## Higher Priority

- esp_lvgl_port settings has a large stack size (~9kB) to fix stackoverflow when LVGL events (e.g. button click) do actions like file operations do actions like file operations. Can we reduce the callstack?
- Improve posix support: use user home path and system /tmp folder. implement uart/i2c drivers properly.
- Improve Setup: Add keyboard/keypad/encoder navigation explanation.
e.g. encoder long press on t-lora pager to get out of textarea
- NimBLE looses pairing key after reboot
- Pick different font to improve monochrome screen readability (or change the weight).
- Drivers/audio-codec-module is not a module. Move it somewhere else. Or make it an actual module.
Expand All @@ -31,17 +30,14 @@
- netutils-module: ping, wget, ifconfig, nslookup, etc.
- Core2: support power off via software
- Improve Setup: Show "Step done" screen
- Make it more clear to end-users that an SD card is required to run Tactility
- Make it clearer to end-users that an SD card is required to run Tactility
- Warn if file operations are done from prohibited tasks (e.g. lvgl task)
- stopAppFromToolbar() in lvgl/Lvgl.cpp stops the top-most app. Change it so the toolbar knows for which app id it is created, so it can rely on that.
- Consider not unpacking `.app` files and executing them directly. Might want to cache file offsets. Cache file must be pinned to app version.
- lvgl-module's spinner relies on hard-coded spinner asset from Tactility main project.
- esp_lvgl_port settings has a large stack size (~9kB) to fix stackoverflow when LVGL events (e.g. button click) do actions like file operations do actions like file operations. Can we reduce the callstack?
- `struct Driver` has an `.owner`, but it's not always set. Either validate on Module construct that it matches, or otherwise set it during module start. The problem: NULL parent currently means that driver is not removable. This clashes with setting it dynamically. Consider some kind of flag to determine removability.
- Consider moving certain drivers into separate modules: audio, bt, wifi, etc
- Consider using https://github.com/Graphify-Labs/graphify
- Consider implementing LVGL gridnav in apps https://lvgl.io/docs/open/9.3/details/auxiliary-modules/gridnav.html
- TactilityTool: Make API compatibility table (and check for compatibility in the tool itself)
- Refactor HttpServer into C code and move implementation to http-module
- Use GPS time to set/update the current time
- Make a URL handler. Use it for handling local files. Match file types with apps.
Expand Down Expand Up @@ -69,7 +65,6 @@
- Show a warning screen if firmware encryption or secure boot are off when saving WiFi credentials.
- Calculator app should show regular text input field on non-touch devices that have a keyboard (Cardputer, T-Lora Pager)
- Allow for WSAD keys to navigate LVGL (this is extra nice for cardputer, but just handy in general)
- Create a "How to" app for a device. It could explain things like keyboard navigation on first start.
- Make WiFi setup app that starts an access point and hosts a webpage to set up the device.
This will be useful for devices without a screen, a small screen or a non-touch screen.

Expand Down
1 change: 1 addition & 0 deletions Drivers/m5stack-module/source/cardputer_keyboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ static uint32_t scan_key(CardputerKeyboardInternal* internal) {
if (has_regular) {
switch (resolved_char) {
case '`': return CODEPOINT_ESCAPE;
case '\n': return CODEPOINT_ENTER;
case ',': return CODEPOINT_ARROW_LEFT;
case '/': return CODEPOINT_ARROW_RIGHT;
case ';': return CODEPOINT_ARROW_UP;
Expand Down
2 changes: 1 addition & 1 deletion Drivers/st7789-i8080-module/source/st7789_i8080.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ struct LcdInitCmd {
uint8_t len;
};

// No MADCTL (0x36): esp_lcd_panel_init() already sets it from rgb_ele_order, and esp_lcd's own swap_xy/mirror rewrite it from that value
constexpr LcdInitCmd ST7789_INIT_CMDS[] = {
{0x36, {0x08}, 1},
{0x3A, {0x05}, 1},
{0x20, {0}, 0},
{0xB2, {0x0B, 0x0B, 0x00, 0x33, 0x33}, 5},
Expand Down
5 changes: 5 additions & 0 deletions Modules/lvgl-module/generate-icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ def generate_icon_names(codepoint_map: dict, codepoint_names: list, variable_nam
"usb", # Power (settings) app
"wifi", # WiFi (settings) app
"bluetooth", # Bluetooth (settings) app
# System menu toggles
"wifi_off",
"bluetooth_disabled",
"location_on",
"location_off",
"terminal",
"logo_dev",
"camera",
Expand Down
18 changes: 16 additions & 2 deletions Modules/lvgl-module/include/lvgl/devices/keyboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ void lvgl_keyboard_remove(lv_indev_t* indev);
lv_indev_t* lvgl_keyboard_find_by_device(struct Device* device);

/**
* @brief Assigns the indev to the shared keyboard input group, so it can drive focus
* @brief Assigns the indev to the active keyboard input group, so it can drive focus
* navigation and input for focused widgets.
* @warning Caller must hold the LVGL lock.
*/
Expand All @@ -60,6 +60,20 @@ void lvgl_keyboard_enable(lv_indev_t* indev);
*/
void lvgl_keyboard_disable(lv_indev_t* indev);

/**
* @brief Makes @a group the group that receives keypad input: it becomes the default group (new
* widgets join it), and every keypad/encoder indev that is attached to a group is moved to it.
* Indevs enabled later are attached to it too.
* @warning Caller must hold the LVGL lock.
* @param[in] group the group to activate, or NULL to activate the shared keyboard input group
*/
void lvgl_keyboard_set_active_group(lv_group_t* group);

/**
* @return the shared keyboard input group, which is the default when no other group is active
*/
lv_group_t* lvgl_keyboard_get_app_group(void);

/**
* @brief Adds the textarea to the shared keyboard navigation group (so any keypad indev -
* hardware or on-screen - can type into it once it's focused), and, only when
Expand Down Expand Up @@ -136,7 +150,7 @@ bool lvgl_software_keyboard_is_enabled();
struct LvglSoftwareKeyboard* lvgl_software_keyboard_get_last();

/**
* @brief Attaches the shared keyboard navigation group to every currently registered keypad
* @brief Attaches the active 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.
*/
Expand Down
2 changes: 1 addition & 1 deletion Modules/lvgl-module/include/lvgl/icons/names.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extern "C" {
#endif

/** Changes whenever the icon TTF subset, its codepoints file or the icon name lists change */
#define LVGL_ICON_FONT_VERSION 0xD5578751u
#define LVGL_ICON_FONT_VERSION 0x3ED2CAAAu

// Icon names per icon font, resolved through the .codepoints file next to the TTF
extern const char* const lvgl_icon_shared_names[];
Expand Down
4 changes: 4 additions & 0 deletions Modules/lvgl-module/include/lvgl/icons/shared.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
#define LVGL_ICON_SHARED_USB "\xEE\x87\xA0"
#define LVGL_ICON_SHARED_WIFI "\xEE\x98\xBE"
#define LVGL_ICON_SHARED_BLUETOOTH "\xEE\x86\xA7"
#define LVGL_ICON_SHARED_WIFI_OFF "\xEE\x99\x88"
#define LVGL_ICON_SHARED_BLUETOOTH_DISABLED "\xEE\x86\xA9"
#define LVGL_ICON_SHARED_LOCATION_ON "\xEF\x87\x9B"
#define LVGL_ICON_SHARED_LOCATION_OFF "\xEE\x83\x87"
#define LVGL_ICON_SHARED_TERMINAL "\xEE\xAE\x8E"
#define LVGL_ICON_SHARED_LOGO_DEV "\xEE\xAB\x96"
#define LVGL_ICON_SHARED_CAMERA "\xEE\x8E\xAF"
Expand Down
20 changes: 20 additions & 0 deletions Modules/lvgl-module/include/lvgl/widgets/focus_container.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <lvgl.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Creates a focus container: a transparent container without padding that the theme only styles with a focus ring.
* Use it to make a group of widgets selectable as one, e.g. with a click handler on the container.
* @param[in] parent the parent object for the new container
* @return the created container
*/
lv_obj_t* lvgl_focus_container_create(lv_obj_t* parent);

#ifdef __cplusplus
}
#endif
1 change: 1 addition & 0 deletions Modules/lvgl-module/lss
Submodule lss added at e9c58a
Loading
Loading