Compare commits

...

18 Commits

Author SHA1 Message Date
Ken Van Hoeylandt f9acf04dcb Update tactility.py and tactility.properties for new release (#289) 2025-06-09 21:05:21 +02:00
Ken Van Hoeylandt a091923353 Fixed & improved build scripts (#288)
* Fixed&improved build scripts

* Fixes and updates

* Fix for esp-idf-ci-action

* Build fixes
2025-06-09 21:03:18 +02:00
Ken Van Hoeylandt 1593eb80ce TactilityC additions (#287)
New TactilityC implementations for:
- WiFi
- GPS
- Preferences
- Timezone

Also includes:
- Some fixes to enums/naming
- Cleanup elsewhere
2025-06-09 13:46:08 +02:00
Ken Van Hoeylandt 869a56125f App build system (#285)
from https://github.com/ByteWelder/TactilityTool
2025-06-08 10:38:20 +02:00
Ken Van Hoeylandt 6116521556 Update launcher icon sizes (#286)
This is a missing commit from a previous PR
2025-06-08 10:37:57 +02:00
Ken Van Hoeylandt 3dfc27e93e Fixes for colours and margins in GPIO app and more (#284)
- Fixes for colours and margins in GPIO app
- Removed unused imports
2025-06-01 17:52:09 +02:00
Ken Van Hoeylandt e4ecec64c9 Reduced desktop icon sizes (#283)
This improves loading performance (and uses less memory on ESP32 devices
without PSRAM)
2025-06-01 17:11:29 +02:00
Shadowtrance ce96474d84 Cleanup of Notes.cpp (#282) 2025-05-27 08:08:04 +02:00
Ken Van Hoeylandt 2691dbb014 Implemented LaunchId and FileSelection, updated Notes (#281)
- Implemented `LaunchId` to keep track of the apps that are started
- Implemented `FileSelection` app to select existing and/or new files.
- Moved some re-usable file functionality to `tt::file::`
- Renamed `Files` app to `FileBrowser`
- Updated `Notes` app to use new `FileSelection` functionality, and cleaned it up a bit.
- General code cleanliness improvements
2025-05-25 22:11:50 +02:00
Ken Van Hoeylandt 74eb830870 Buildscript and kernel improvements (#280)
- Implemented `constexpr long int getMicros()` in `Kernel.h`
- Changed `secondsToTicks()`, `minutesToTicks()` and `bool isIsr()` to `constexpr`
- Added more relevant build info
2025-05-24 17:27:37 +02:00
Shadowtrance 870924229a Switch to dark theme (#278) 2025-05-06 19:52:55 +02:00
Ken Van Hoeylandt b2647f46bb Cleanup (#277) 2025-04-11 21:56:16 +02:00
Shadowtrance d1c1a3a369 Add Notes app (#276) 2025-04-11 21:35:29 +02:00
Ken Van Hoeylandt a5090ec194 Refactor display app (#275)
- Moved more code into class
- Refactored some logic
- UI is more dynamic: hide elements that are not supported by the hardware (gamma and/or brightness slider)
2025-04-08 22:38:03 +02:00
Ken Van Hoeylandt f67cb241b7 SDK fixes (#272)
- Fix for `ExternalApps/HelloWorld/build.sh`
- Fix portability issue with regards to the SDK release script
2025-04-03 23:38:47 +02:00
Ken Van Hoeylandt b2d4dc5ecb Simplify AppList (#271)
Remove categories, disable HelloWorld app
2025-04-03 23:06:54 +02:00
Shadowtrance a7c5d6a2ee Add Calculator app (#270)
Add Calculator app

Originally created by @maxbrito500 and tweaked/updated by me.
2025-04-03 18:28:15 +02:00
Ken Van Hoeylandt 08029a84dd Various fixes and improvements (#269)
- Bump version for next release
- Fix default gamma for CYD 2432S032C
- Remember gamma curve setting from Display settings app
- Add UART to Core2 (still has no voltage on Grove port, though)
- LVGL performance improvements: pin to second core and set task priority to "critical"
- Fix build warnings, including deprecations
- Removed deprecated `Thread` constructor
- Fix WaveShare S3 display: Some displays would show a white screen at 12MHz, so I'm putting it back to the
official config values.
2025-04-01 23:42:56 +02:00
171 changed files with 2856 additions and 654 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ runs:
shell: bash shell: bash
run: cp sdkconfig.board.${{ inputs.board_id }} sdkconfig run: cp sdkconfig.board.${{ inputs.board_id }} sdkconfig
- name: 'Build' - name: 'Build'
uses: espressif/esp-idf-ci-action@main uses: espressif/esp-idf-ci-action@v1
with: with:
esp_idf_version: v5.4 esp_idf_version: v5.4
target: ${{ inputs.arch }} target: ${{ inputs.arch }}
+5 -1
View File
@@ -18,13 +18,17 @@ runs:
shell: bash shell: bash
run: cp sdkconfig.board.${{ inputs.board_id }} sdkconfig run: cp sdkconfig.board.${{ inputs.board_id }} sdkconfig
- name: 'Build' - name: 'Build'
uses: espressif/esp-idf-ci-action@main uses: espressif/esp-idf-ci-action@v1
with: with:
# NOTE: Update with ESP-IDF!
esp_idf_version: v5.4 esp_idf_version: v5.4
target: ${{ inputs.arch }} target: ${{ inputs.arch }}
path: './' path: './'
- name: 'Release' - name: 'Release'
shell: bash shell: bash
env:
# NOTE: Update with ESP-IDF!
ESP_IDF_VERSION: '5.4'
run: Buildscripts/release-sdk.sh release/TactilitySDK run: Buildscripts/release-sdk.sh release/TactilitySDK
- name: 'Upload Artifact' - name: 'Upload Artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
+1
View File
@@ -1,4 +1,5 @@
#pragma once #pragma once
#include <Tactility/hal/Configuration.h>
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
#include <sdkconfig.h> #include <sdkconfig.h>
+2 -2
View File
@@ -5,7 +5,7 @@
#include "tt_init.h" #include "tt_init.h"
#endif #endif
extern const tt::app::AppManifest hello_world_app; // extern const tt::app::AppManifest hello_world_app;
extern "C" { extern "C" {
@@ -17,7 +17,7 @@ void app_main() {
*/ */
.hardware = TT_BOARD_HARDWARE, .hardware = TT_BOARD_HARDWARE,
.apps = { .apps = {
&hello_world_app, // &hello_world_app,
} }
}; };
+17 -3
View File
@@ -3,12 +3,26 @@
#include "hal/CydDisplay.h" #include "hal/CydDisplay.h"
#include "hal/CydSdCard.h" #include "hal/CydSdCard.h"
#include <Tactility/kernel/SystemEvents.h>
#include <PwmBacklight.h> #include <PwmBacklight.h>
#define CYD_SPI_TRANSFER_SIZE_LIMIT (TWODOTFOUR_LCD_DRAW_BUFFER_SIZE * LV_COLOR_DEPTH / 8)
bool initBoot() { bool initBoot() {
return driver::pwmbacklight::init(GPIO_NUM_27); if (!driver::pwmbacklight::init(GPIO_NUM_27)) {
return false;
}
// This display has a weird glitch with gamma during boot, which results in uneven dark gray colours.
// Setting gamma curve index to 0 doesn't work at boot for an unknown reason, so we set the curve index to 1:
tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootInitLvglEnd, [](auto event) {
auto display = tt::hal::findFirstDevice<tt::hal::display::DisplayDevice>(tt::hal::Device::Type::Display);
assert(display != nullptr);
tt::lvgl::lock(portMAX_DELAY);
display->setGammaCurve(1U);
tt::lvgl::unlock();
});
return true;
} }
const tt::hal::Configuration cyd_2432S032c_config = { const tt::hal::Configuration cyd_2432S032c_config = {
@@ -9,6 +9,8 @@
#define CORE2_SPI_TRANSFER_SIZE_LIMIT (CORE2_LCD_DRAW_BUFFER_SIZE * LV_COLOR_DEPTH / 8) #define CORE2_SPI_TRANSFER_SIZE_LIMIT (CORE2_LCD_DRAW_BUFFER_SIZE * LV_COLOR_DEPTH / 8)
using namespace tt::hal;
extern const tt::hal::Configuration m5stack_core2 = { extern const tt::hal::Configuration m5stack_core2 = {
.initBoot = initBoot, .initBoot = initBoot,
.createDisplay = createDisplay, .createDisplay = createDisplay,
@@ -74,5 +76,30 @@ extern const tt::hal::Configuration m5stack_core2 = {
.isMutable = false, .isMutable = false,
.lock = tt::lvgl::getSyncLock() // esp_lvgl_port owns the lock for the display .lock = tt::lvgl::getSyncLock() // esp_lvgl_port owns the lock for the display
} }
},
.uart {
uart::Configuration {
.name = "Grove",
.port = UART_NUM_1,
.rxPin = GPIO_NUM_32,
.txPin = GPIO_NUM_33,
.rtsPin = GPIO_NUM_NC,
.ctsPin = GPIO_NUM_NC,
.rxBufferSize = 1024,
.txBufferSize = 1024,
.config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.rx_flow_ctrl_thresh = 0,
.source_clk = UART_SCLK_DEFAULT,
.flags = {
.allow_pd = 0,
.backup_before_sleep = 0,
}
}
},
} }
}; };
-1
View File
@@ -1,7 +1,6 @@
#pragma once #pragma once
#include "Main.h" #include "Main.h"
#include <Tactility/hal/Configuration.h>
namespace simulator { namespace simulator {
/** Set the function pointer of the real app_main() */ /** Set the function pointer of the real app_main() */
+2 -3
View File
@@ -137,7 +137,7 @@ static void updatePowerSwitch() {
} }
} }
static int32_t powerSwitchMain(void*) { // check power switch every 10th of sec static int32_t powerSwitchMain() { // check power switch every 10th of sec
while (true) { while (true) {
updatePowerSwitch(); updatePowerSwitch();
tt::kernel::delayMillis(200); tt::kernel::delayMillis(200);
@@ -148,8 +148,7 @@ static void startPowerSwitchThread() {
powerThread = std::make_unique<tt::Thread>( powerThread = std::make_unique<tt::Thread>(
"unphone_power_switch", "unphone_power_switch",
4096, 4096,
powerSwitchMain, []() { return powerSwitchMain(); }
nullptr
); );
powerThread->start(); powerThread->start();
} }
@@ -23,15 +23,15 @@ std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
esp_lcd_rgb_panel_config_t rgb_panel_config = { esp_lcd_rgb_panel_config_t rgb_panel_config = {
.clk_src = LCD_CLK_SRC_DEFAULT, .clk_src = LCD_CLK_SRC_DEFAULT,
.timings = { .timings = {
.pclk_hz = 12'000'000, // NOTE: original was 14MHz, but we had to slow it down with PSRAM frame buffer, .pclk_hz = 16'000'000,
.h_res = 800, .h_res = 800,
.v_res = 480, .v_res = 480,
.hsync_pulse_width = 10, .hsync_pulse_width = 4,
.hsync_back_porch = 10, .hsync_back_porch = 8,
.hsync_front_porch = 20, .hsync_front_porch = 8,
.vsync_pulse_width = 10, .vsync_pulse_width = 4,
.vsync_back_porch = 10, .vsync_back_porch = 8,
.vsync_front_porch = 10, .vsync_front_porch = 8,
.flags = { .flags = {
.hsync_idle_low = false, .hsync_idle_low = false,
.vsync_idle_low = false, .vsync_idle_low = false,
+11 -2
View File
@@ -1,3 +1,12 @@
if (NOT WIN32)
string(ASCII 27 Esc)
set(ColorReset "${Esc}[m")
set(Cyan "${Esc}[36m")
else ()
set(ColorReset "")
set(Cyan "")
endif ()
function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE) function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE)
get_filename_component(SDKCONFIG_FILE_ABS ${SDKCONFIG_FILE} ABSOLUTE) get_filename_component(SDKCONFIG_FILE_ABS ${SDKCONFIG_FILE} ABSOLUTE)
# Find the board identifier in the sdkconfig file # Find the board identifier in the sdkconfig file
@@ -10,7 +19,7 @@ function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE)
set(id_length 0) set(id_length 0)
math(EXPR id_length "${sdkconfig_board_id_length} - 21") math(EXPR id_length "${sdkconfig_board_id_length} - 21")
string(SUBSTRING ${sdkconfig_board_id} 20 ${id_length} board_id) string(SUBSTRING ${sdkconfig_board_id} 20 ${id_length} board_id)
message("Building board ${board_id}") message("Board name: ${Cyan}${board_id}${ColorReset}")
if (board_id STREQUAL "cyd-2432s024c") if (board_id STREQUAL "cyd-2432s024c")
set(TACTILITY_BOARD_PROJECT CYD-2432S024C) set(TACTILITY_BOARD_PROJECT CYD-2432S024C)
@@ -53,7 +62,7 @@ function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE)
if (TACTILITY_BOARD_PROJECT STREQUAL "") if (TACTILITY_BOARD_PROJECT STREQUAL "")
message(FATAL_ERROR "No subproject mapped to \"${TACTILITY_BOARD_ID}\" in root Buildscripts/board.cmake") message(FATAL_ERROR "No subproject mapped to \"${TACTILITY_BOARD_ID}\" in root Buildscripts/board.cmake")
else () else ()
message("Board project: Boards/${TACTILITY_BOARD_PROJECT}") message("Board path: ${Cyan}Boards/${TACTILITY_BOARD_PROJECT}${ColorReset}\n")
set_property(GLOBAL PROPERTY TACTILITY_BOARD_PROJECT ${TACTILITY_BOARD_PROJECT}) set_property(GLOBAL PROPERTY TACTILITY_BOARD_PROJECT ${TACTILITY_BOARD_PROJECT})
set_property(GLOBAL PROPERTY TACTILITY_BOARD_ID ${board_id}) set_property(GLOBAL PROPERTY TACTILITY_BOARD_ID ${board_id})
endif () endif ()
+3 -2
View File
@@ -27,12 +27,13 @@ fi
echoNewPhase "Cleaning build folder" echoNewPhase "Cleaning build folder"
rm -rf build #rm -rf build
echoNewPhase "Building $sdkconfig_file" echoNewPhase "Building $sdkconfig_file"
cp $sdkconfig_file sdkconfig cp $sdkconfig_file sdkconfig
if not idf.py build; then idf.py build
if [[ $? != 0 ]]; then
fatalError "Failed to build esp32s3 SDK" fatalError "Failed to build esp32s3 SDK"
fi fi
+14
View File
@@ -0,0 +1,14 @@
if (NOT WIN32)
string(ASCII 27 Esc)
set(ColorReset "${Esc}[m")
set(Cyan "${Esc}[36m")
set(Grey "${Esc}[37m")
set(LightPurple "${Esc}[1;35m")
set(White "${Esc}[1;37m")
else ()
set(ColorReset "")
set(Cyan "")
set(Grey "")
set(LightPurple "")
set(White "")
endif ()
+3 -3
View File
@@ -10,13 +10,13 @@ endif()
if (NOT WIN32) if (NOT WIN32)
string(ASCII 27 Esc) string(ASCII 27 Esc)
set(ColourReset "${Esc}[m") set(ColorReset "${Esc}[m")
set(Cyan "${Esc}[36m") set(Cyan "${Esc}[36m")
set(Grey "${Esc}[37m") set(Grey "${Esc}[37m")
set(LightPurple "${Esc}[1;35m") set(LightPurple "${Esc}[1;35m")
set(White "${Esc}[1;37m") set(White "${Esc}[1;37m")
else () else ()
set(ColourReset "") set(ColorReset "")
set(Cyan "") set(Cyan "")
set(Grey "") set(Grey "")
set(LightPurple "") set(LightPurple "")
@@ -41,4 +41,4 @@ message("\n\n\
${Cyan}@@@\n\ ${Cyan}@@@\n\
${Cyan}@@@\n\ ${Cyan}@@@\n\
${Cyan}@@@\n\ ${Cyan}@@@\n\
${Cyan}@@\n\n${ColourReset}") ${Cyan}@@\n\n${ColorReset}")
+1 -1
View File
@@ -46,4 +46,4 @@ cp Libraries/elf_loader/license.txt $elf_loader_library_path/
cp Buildscripts/CMake/TactilitySDK.cmake $target_path/ cp Buildscripts/CMake/TactilitySDK.cmake $target_path/
cp Buildscripts/CMake/CMakeLists.txt $target_path/ cp Buildscripts/CMake/CMakeLists.txt $target_path/
echo -n $ESP_IDF_VERSION >> $target_path/idf-version.txt printf '%s' "$ESP_IDF_VERSION" >> $target_path/idf-version.txt
+11 -1
View File
@@ -8,14 +8,24 @@ set(CMAKE_ASM_COMPILE_OBJECT "${CMAKE_CXX_COMPILER_TARGET}")
include("Buildscripts/logo.cmake") include("Buildscripts/logo.cmake")
if (NOT WIN32)
string(ASCII 27 Esc)
set(ColorReset "${Esc}[m")
set(Cyan "${Esc}[36m")
else ()
set(ColorReset "")
set(Cyan "")
endif ()
file(READ version.txt TACTILITY_VERSION) file(READ version.txt TACTILITY_VERSION)
add_compile_definitions(TT_VERSION="${TACTILITY_VERSION}") add_compile_definitions(TT_VERSION="${TACTILITY_VERSION}")
if (DEFINED ENV{ESP_IDF_VERSION}) if (DEFINED ENV{ESP_IDF_VERSION})
message("Building with ESP-IDF v$ENV{ESP_IDF_VERSION}") message("Using ESP-IDF ${Cyan}v$ENV{ESP_IDF_VERSION}${ColorReset}")
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
include("Buildscripts/board.cmake") include("Buildscripts/board.cmake")
init_tactility_globals("sdkconfig") init_tactility_globals("sdkconfig")
get_property(TACTILITY_BOARD_PROJECT GLOBAL PROPERTY TACTILITY_BOARD_PROJECT) get_property(TACTILITY_BOARD_PROJECT GLOBAL PROPERTY TACTILITY_BOARD_PROJECT)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 724 B

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

Before

Width:  |  Height:  |  Size: 333 B

After

Width:  |  Height:  |  Size: 333 B

Before

Width:  |  Height:  |  Size: 240 B

After

Width:  |  Height:  |  Size: 240 B

Before

Width:  |  Height:  |  Size: 286 B

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

Before

Width:  |  Height:  |  Size: 429 B

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Before

Width:  |  Height:  |  Size: 299 B

After

Width:  |  Height:  |  Size: 299 B

Before

Width:  |  Height:  |  Size: 535 B

After

Width:  |  Height:  |  Size: 535 B

Before

Width:  |  Height:  |  Size: 436 B

After

Width:  |  Height:  |  Size: 436 B

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 392 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M240-160q-33 0-56.5-23.5T160-240q0-33 23.5-56.5T240-320q33 0 56.5 23.5T320-240q0 33-23.5 56.5T240-160Zm240 0q-33 0-56.5-23.5T400-240q0-33 23.5-56.5T480-320q33 0 56.5 23.5T560-240q0 33-23.5 56.5T480-160Zm240 0q-33 0-56.5-23.5T640-240q0-33 23.5-56.5T720-320q33 0 56.5 23.5T800-240q0 33-23.5 56.5T720-160ZM240-400q-33 0-56.5-23.5T160-480q0-33 23.5-56.5T240-560q33 0 56.5 23.5T320-480q0 33-23.5 56.5T240-400Zm240 0q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm240 0q-33 0-56.5-23.5T640-480q0-33 23.5-56.5T720-560q33 0 56.5 23.5T800-480q0 33-23.5 56.5T720-400ZM240-640q-33 0-56.5-23.5T160-720q0-33 23.5-56.5T240-800q33 0 56.5 23.5T320-720q0 33-23.5 56.5T240-640Zm240 0q-33 0-56.5-23.5T400-720q0-33 23.5-56.5T480-800q33 0 56.5 23.5T560-720q0 33-23.5 56.5T480-640Zm240 0q-33 0-56.5-23.5T640-720q0-33 23.5-56.5T720-800q33 0 56.5 23.5T800-720q0 33-23.5 56.5T720-640Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640v400q0 33-23.5 56.5T800-160H160Zm0-80h640v-400H447l-80-80H160v480Zm0 0v-480 480Z"/></svg>

Before

Width:  |  Height:  |  Size: 301 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"/></svg>

Before

Width:  |  Height:  |  Size: 771 B

+42
View File
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="24px"
viewBox="0 -960 960 960"
width="24px"
fill="#e8eaed"
version="1.1"
id="svg1"
sodipodi:docname="apps.svg"
inkscape:export-filename="icon_apps.png"
inkscape:export-xdpi="160"
inkscape:export-ydpi="160"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="17.088414"
inkscape:cx="14.600536"
inkscape:cy="18.960215"
inkscape:window-width="1503"
inkscape:window-height="933"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="m 122.37644,-3.1685927 q -49.173234,0 -84.190541,-35.0173063 -35.0173063,-35.017307 -35.0173063,-84.190541 0,-49.17324 35.0173063,-84.19055 35.017307,-35.01731 84.190541,-35.01731 49.17324,0 84.19055,35.01731 35.01731,35.01731 35.01731,84.19055 0,49.173234 -35.01731,84.190541 -35.01731,35.0173063 -84.19055,35.0173063 z m 357.62356,0 q -49.17324,0 -84.19055,-35.0173063 -35.0173,-35.017307 -35.0173,-84.190541 0,-49.17324 35.0173,-84.19055 35.01731,-35.01731 84.19055,-35.01731 49.17324,0 84.19055,35.01731 35.0173,35.01731 35.0173,84.19055 0,49.173234 -35.0173,84.190541 Q 529.17324,-3.1685927 480,-3.1685927 Z m 357.62356,0 q -49.17324,0 -84.19055,-35.0173063 -35.01731,-35.017307 -35.01731,-84.190541 0,-49.17324 35.01731,-84.19055 35.01731,-35.01731 84.19055,-35.01731 49.17323,0 84.19054,35.01731 35.01731,35.01731 35.01731,84.19055 0,49.173234 -35.01731,84.190541 -35.01731,35.0173063 -84.19054,35.0173063 z M 122.37644,-360.79215 q -49.173234,0 -84.190541,-35.0173 Q 3.1685927,-430.82676 3.1685927,-480 q 0,-49.17324 35.0173063,-84.19055 35.017307,-35.0173 84.190541,-35.0173 49.17324,0 84.19055,35.0173 35.01731,35.01731 35.01731,84.19055 0,49.17324 -35.01731,84.19055 -35.01731,35.0173 -84.19055,35.0173 z m 357.62356,0 q -49.17324,0 -84.19055,-35.0173 -35.0173,-35.01731 -35.0173,-84.19055 0,-49.17324 35.0173,-84.19055 35.01731,-35.0173 84.19055,-35.0173 49.17324,0 84.19055,35.0173 35.0173,35.01731 35.0173,84.19055 0,49.17324 -35.0173,84.19055 -35.01731,35.0173 -84.19055,35.0173 z m 357.62356,0 q -49.17324,0 -84.19055,-35.0173 Q 718.4157,-430.82676 718.4157,-480 q 0,-49.17324 35.01731,-84.19055 35.01731,-35.0173 84.19055,-35.0173 49.17323,0 84.19054,35.0173 35.01731,35.01731 35.01731,84.19055 0,49.17324 -35.01731,84.19055 -35.01731,35.0173 -84.19054,35.0173 z M 122.37644,-718.4157 q -49.173234,0 -84.190541,-35.01731 -35.0173063,-35.01731 -35.0173063,-84.19055 0,-49.17323 35.0173063,-84.19054 35.017307,-35.01731 84.190541,-35.01731 49.17324,0 84.19055,35.01731 35.01731,35.01731 35.01731,84.19054 0,49.17324 -35.01731,84.19055 -35.01731,35.01731 -84.19055,35.01731 z m 357.62356,0 q -49.17324,0 -84.19055,-35.01731 -35.0173,-35.01731 -35.0173,-84.19055 0,-49.17323 35.0173,-84.19054 35.01731,-35.01731 84.19055,-35.01731 49.17324,0 84.19055,35.01731 35.0173,35.01731 35.0173,84.19054 0,49.17324 -35.0173,84.19055 Q 529.17324,-718.4157 480,-718.4157 Z m 357.62356,0 q -49.17324,0 -84.19055,-35.01731 -35.01731,-35.01731 -35.01731,-84.19055 0,-49.17323 35.01731,-84.19054 35.01731,-35.01731 84.19055,-35.01731 49.17323,0 84.19054,35.01731 35.01731,35.01731 35.01731,84.19054 0,49.17324 -35.01731,84.19055 -35.01731,35.01731 -84.19054,35.01731 z"
id="path1"
style="stroke-width:1.4901" />
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="24px"
viewBox="0 -960 960 960"
width="24px"
fill="#e8eaed"
version="1.1"
id="svg1"
sodipodi:docname="folder.svg"
inkscape:export-filename="icon_files.png"
inkscape:export-xdpi="160"
inkscape:export-ydpi="160"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="48.333333"
inkscape:cx="11.989655"
inkscape:cy="12"
inkscape:window-width="1503"
inkscape:window-height="933"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="m 96.441379,-96.441379 q -39.554482,0 -67.722069,-28.167591 -28.16758586,-28.16758 -28.16758586,-67.72206 v -575.33794 q 0,-39.55448 28.16758586,-67.72206 28.167587,-28.16759 67.722069,-28.16759 H 384.11034 L 480,-767.66897 h 383.55862 q 39.55448,0 67.72207,28.16759 28.16759,28.16759 28.16759,67.72207 v 479.44828 q 0,39.55448 -28.16759,67.72206 -28.16759,28.167591 -67.72207,28.167591 z m 0,-95.889651 H 863.55862 v -479.44828 h -423.1131 l -95.88966,-95.88966 H 96.441379 Z m 0,0 v -575.33794 z"
id="path1"
style="stroke-width:1.19862" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="24px"
viewBox="0 -960 960 960"
width="24px"
fill="#e8eaed"
version="1.1"
id="svg1"
sodipodi:docname="settings.svg"
inkscape:export-filename="icon_settings.png"
inkscape:export-xdpi="160"
inkscape:export-ydpi="160"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="48.333333"
inkscape:cx="11.989655"
inkscape:cy="12"
inkscape:window-width="1503"
inkscape:window-height="933"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="M 347.80751,0.69994853 328.57952,-153.12403 q -15.62275,-6.00875 -29.44288,-14.421 -13.82012,-8.41225 -27.03937,-18.02625 L 129.08904,-125.48379 -3.1034483,-353.81626 120.67679,-447.55275 q -1.20175,-8.41225 -1.20175,-16.22363 v -32.44724 q 0,-7.81138 1.20175,-16.22363 L -3.1034483,-606.18374 129.08904,-834.51621 l 143.00823,60.08749 q 13.21925,-9.614 27.64025,-18.02625 14.421,-8.41225 28.842,-14.421 l 19.22799,-153.82398 h 264.38498 l 19.22799,153.82398 q 15.62275,6.00875 29.44288,14.421 13.82012,8.41225 27.03937,18.02625 l 143.00823,-60.08749 132.19249,228.33247 -123.78024,93.73649 q 1.20175,8.41225 1.20175,16.22363 v 32.44724 q 0,7.81138 -2.4035,16.22363 l 123.78024,93.73649 -132.19249,228.33247 -141.80648,-60.08749 q -13.21925,9.614 -27.64025,18.02625 -14.421,8.41225 -28.842,14.421 L 612.19249,0.69994853 Z m 84.1225,-96.13998953 h 94.93823 l 16.8245,-127.385489 q 37.25425,-9.614 69.10062,-28.24112 31.84637,-18.62712 58.28487,-45.06562 l 118.97324,49.27175 46.86824,-81.719 -103.35049,-78.11374 q 6.00875,-16.8245 8.41225,-35.45162 2.4035,-18.62712 2.4035,-37.85512 0,-19.228 -2.4035,-37.85512 -2.4035,-18.62712 -8.41225,-35.45162 l 103.35049,-78.11374 -46.86824,-81.719 -118.97324,50.4735 q -26.4385,-27.64025 -58.28487,-46.26737 -31.84637,-18.62712 -69.10062,-28.24112 l -15.62275,-127.38549 h -94.93823 l -16.8245,127.38549 q -37.25425,9.614 -69.10062,28.24112 -31.84637,18.62712 -58.28487,45.06562 l -118.97324,-49.27175 -46.86824,81.719 103.35049,76.91199 q -6.00875,18.02625 -8.41225,36.05249 -2.4035,18.02625 -2.4035,38.456 0,19.228 2.4035,37.25425 2.4035,18.02624 8.41225,36.05249 l -103.35049,78.11374 46.86824,81.719 118.97324,-50.4735 q 26.4385,27.64025 58.28487,46.26737 31.84637,18.62712 69.10062,28.24112 z M 482.4035,-311.75502 q 69.70149,0 118.97324,-49.27174 49.27174,-49.27175 49.27174,-118.97324 0,-69.70149 -49.27174,-118.97324 -49.27175,-49.27174 -118.97324,-49.27174 -70.90324,0 -119.57411,49.27174 -48.67087,49.27175 -48.67087,118.97324 0,69.70149 48.67087,118.97324 48.67087,49.27174 119.57411,49.27174 z M 480,-480 Z"
id="path1"
style="stroke-width:1.20175" />
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- License: Apache. Made by Iconscout: https://github.com/Iconscout/unicons -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="M12.71,17.29a1,1,0,0,0-.16-.12.56.56,0,0,0-.17-.09.6.6,0,0,0-.19-.06.93.93,0,0,0-.57.06.9.9,0,0,0-.54.54A.84.84,0,0,0,11,18a1,1,0,0,0,.07.38,1.46,1.46,0,0,0,.22.33A1,1,0,0,0,12,19a.84.84,0,0,0,.38-.08,1.15,1.15,0,0,0,.33-.21A1,1,0,0,0,13,18a1,1,0,0,0-.08-.38A1,1,0,0,0,12.71,17.29ZM8.55,13.17a.56.56,0,0,0-.17-.09A.6.6,0,0,0,8.19,13a.86.86,0,0,0-.39,0l-.18.06-.18.09-.15.12A1.05,1.05,0,0,0,7,14a1,1,0,0,0,.29.71,1.15,1.15,0,0,0,.33.21A1,1,0,0,0,9,14a1.05,1.05,0,0,0-.29-.71Zm.16,4.12a1,1,0,0,0-.33-.21A1,1,0,0,0,7.8,17l-.18.06a.76.76,0,0,0-.18.09,1.58,1.58,0,0,0-.15.12,1,1,0,0,0-.21.33.94.94,0,0,0,0,.76,1.15,1.15,0,0,0,.21.33A1,1,0,0,0,8,19a.84.84,0,0,0,.38-.08,1.15,1.15,0,0,0,.33-.21,1.15,1.15,0,0,0,.21-.33.94.94,0,0,0,0-.76A1,1,0,0,0,8.71,17.29Zm2.91-4.21a1,1,0,0,0-.33.21A1.05,1.05,0,0,0,11,14a1,1,0,0,0,1.38.92,1.15,1.15,0,0,0,.33-.21A1,1,0,0,0,13,14a1.05,1.05,0,0,0-.29-.71A1,1,0,0,0,11.62,13.08Zm5.09,4.21a1.15,1.15,0,0,0-.33-.21,1,1,0,0,0-1.09.21,1,1,0,0,0-.21.33.94.94,0,0,0,0,.76,1.15,1.15,0,0,0,.21.33A1,1,0,0,0,16,19a.84.84,0,0,0,.38-.08,1.15,1.15,0,0,0,.33-.21,1,1,0,0,0,.21-1.09A1,1,0,0,0,16.71,17.29ZM16,5H8A1,1,0,0,0,7,6v4a1,1,0,0,0,1,1h8a1,1,0,0,0,1-1V6A1,1,0,0,0,16,5ZM15,9H9V7h6Zm3-8H6A3,3,0,0,0,3,4V20a3,3,0,0,0,3,3H18a3,3,0,0,0,3-3V4A3,3,0,0,0,18,1Zm1,19a1,1,0,0,1-1,1H6a1,1,0,0,1-1-1V4A1,1,0,0,1,6,3H18a1,1,0,0,1,1,1Zm-2.45-6.83a.56.56,0,0,0-.17-.09.6.6,0,0,0-.19-.06.86.86,0,0,0-.39,0l-.18.06-.18.09-.15.12A1.05,1.05,0,0,0,15,14a1,1,0,0,0,1.38.92,1.15,1.15,0,0,0,.33-.21A1,1,0,0,0,17,14a1.05,1.05,0,0,0-.29-.71Z"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- License: PD. Made by Mary Akveo: https://maryakveo.com/ -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" id="chat" data-name="Line Color" xmlns="http://www.w3.org/2000/svg" class="icon line-color"><path id="primary" d="M18.81,16.23,20,21l-4.95-2.48A9.84,9.84,0,0,1,12,19c-5,0-9-3.58-9-8s4-8,9-8,9,3.58,9,8A7.49,7.49,0,0,1,18.81,16.23Z" style="fill: none; stroke: #000000; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path></svg>

After

Width:  |  Height:  |  Size: 518 B

+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- License: PD. Made by Mary Akveo: https://maryakveo.com/ -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" id="chip" data-name="Flat Line" xmlns="http://www.w3.org/2000/svg" class="icon flat-line"><rect id="secondary" x="6" y="6" width="12" height="12" rx="1" style="fill: none; stroke-width: 2;"></rect><path id="primary" d="M12,6V3m4,3V4M8,6V4m10,8h3m-3,4h2M18,8h2M12,18v3M8,18v2m8-2v2M6,12H3M6,8H4m2,8H4m14,1V7a1,1,0,0,0-1-1H7A1,1,0,0,0,6,7V17a1,1,0,0,0,1,1H17A1,1,0,0,0,18,17Z" style="fill: none; stroke: #333333; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path></svg>

After

Width:  |  Height:  |  Size: 662 B

+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- License: MIT. Made by vmware: https://github.com/vmware/clarity-assets -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 36 36" version="1.1" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>note-line</title>
<path d="M28,30H6V8H19.22l2-2H6A2,2,0,0,0,4,8V30a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V15l-2,2Z" class="clr-i-outline clr-i-outline-path-1"></path><path d="M33.53,5.84,30.16,2.47a1.61,1.61,0,0,0-2.28,0L14.17,16.26l-1.11,4.81A1.61,1.61,0,0,0,14.63,23,1.69,1.69,0,0,0,15,23l4.85-1.07L33.53,8.12A1.61,1.61,0,0,0,33.53,5.84ZM18.81,20.08l-3.66.81L16,17.26,26.32,6.87l2.82,2.82ZM30.27,8.56,27.45,5.74,29,4.16,31.84,7Z" class="clr-i-outline clr-i-outline-path-2"></path>
<rect x="0" y="0" width="36" height="36" fill-opacity="0"/>
</svg>

After

Width:  |  Height:  |  Size: 878 B

+5 -12
View File
@@ -1,21 +1,17 @@
# TODOs # TODOs
- Split up boot stages, so the last stage can be done from the splash screen
- Add a Keyboard setting app to override the behaviour of soft keyboard hiding (e.g. keyboard hardware is present, but user wants soft keyboard)
- HAL for display touch calibration
- Start using non_null (either via MS GSL, or custom) - Start using non_null (either via MS GSL, or custom)
- `hal/Configuration.h` defines C function types: Use C++ std::function instead - `hal/Configuration.h` defines C function types: Use C++ std::function instead
- Fix system time to not be 1980 (use build year as minimum) - Fix system time to not be 1980 (use build year as minimum)
- Use std::span or string_view in StringUtils https://youtu.be/FRkJCvHWdwQ?t=2754 - Use std::span or string_view in StringUtils https://youtu.be/FRkJCvHWdwQ?t=2754
- Fix bug in T-Deck/etc: esp_lvgl_port settings has a large stack size (~9kB) to fix an issue where the T-Deck would get a stackoverflow. This sometimes happens when WiFi is auto-enabled and you open the app while it is still connecting. - Fix bug in T-Deck/etc: esp_lvgl_port settings has a large stack size (~9kB) to fix an issue where the T-Deck would get a stackoverflow. This sometimes happens when WiFi is auto-enabled and you open the app while it is still connecting.
- Clean up static_cast when casting to base class.
- Mutex: Implement give/take from ISR support (works only for non-recursive ones) - Mutex: Implement give/take from ISR support (works only for non-recursive ones)
- Extend unPhone power driver: add charging status, usb connection status, etc. - Extend unPhone power driver: add charging status, usb connection status, etc.
- Expose app::Paths to TactilityC - Expose app::Paths to TactilityC
- Experiment with what happens when using C++ code in an external app (without using standard library!) - Experiment with what happens when using C++ code in an external app (without using standard library!)
- Boards' CMakeLists.txt manually declare each source folder. Update them all to do a recursive search of all folders.
- We currently build all boards for a given platform (e.g. ESP32S3), but it's better to filter all irrelevant ones based on the Kconfig board settings:
Projects will load and compile faster as it won't compile all the dependencies of all these other boards
- Make a ledger for setting CPU affinity of various services and tasks - Make a ledger for setting CPU affinity of various services and tasks
- Boot hooks instead of a single boot method in config. Define different boot phases/levels in enum.
- Add toggle to Display app for sysmon overlay: https://docs.lvgl.io/master/API/others/sysmon/index.html
- CrashHandler: use "corrupted" flag - CrashHandler: use "corrupted" flag
- CrashHandler: process other types of crashes (WDT?) - CrashHandler: process other types of crashes (WDT?)
- Call tt::lvgl::isSyncSet after HAL init and show error (and crash?) when it is not set. - Call tt::lvgl::isSyncSet after HAL init and show error (and crash?) when it is not set.
@@ -38,6 +34,7 @@
- Remove flex_flow from app_container in Gui.cpp - Remove flex_flow from app_container in Gui.cpp
# Nice-to-haves # Nice-to-haves
- Give external app a different icon. Allow an external app update their id, icon, type and name once they are running(and persist that info?). Loader will need to be able to find app by (external) location. - Give external app a different icon. Allow an external app update their id, icon, type and name once they are running(and persist that info?). Loader will need to be able to find app by (external) location.
- Audio player app - Audio player app
- Audio recording app - Audio recording app
@@ -48,20 +45,18 @@
- Capacity based on voltage: estimation for various devices uses linear voltage curve, but it should use some sort of battery discharge curve. - Capacity based on voltage: estimation for various devices uses linear voltage curve, but it should use some sort of battery discharge curve.
- Statusbar widget to show how much memory is in use? - Statusbar widget to show how much memory is in use?
- Wrapper for Slider that shows "+" and "-" buttons, and also the value in a label. - Wrapper for Slider that shows "+" and "-" buttons, and also the value in a label.
- Display app: Add toggle to display performance measurement overlay (consider showing FPS in statusbar!)
- Files app: copy/paste actions - Files app: copy/paste actions
- On crash, try to save current log to flash or SD card? (this is risky, though, so ask in Discord first) - On crash, try to save current log to flash or SD card? (this is risky, though, so ask in Discord first)
- Support more than 1 hardware keyboard (see lvgl::hardware_keyboard_set_indev()). LVGL init currently calls keyboard init, but that part should probably be done from the KeyboardDevice base class. - Support more than 1 hardware keyboard (see lvgl::hardware_keyboard_set_indev()). LVGL init currently calls keyboard init, but that part should probably be done from the KeyboardDevice base class.
# App Ideas # App Ideas
- Map widget: - Map widget:
https://github.com/portapack-mayhem/mayhem-firmware/blob/b66d8b1aa178d8a9cd06436fea788d5d58cb4c8d/firmware/application/ui/ui_geomap.cpp https://github.com/portapack-mayhem/mayhem-firmware/blob/b66d8b1aa178d8a9cd06436fea788d5d58cb4c8d/firmware/application/ui/ui_geomap.cpp
https://github.com/portapack-mayhem/mayhem-firmware/blob/b66d8b1aa178d8a9cd06436fea788d5d58cb4c8d/firmware/tools/generate_world_map.bin.py https://github.com/portapack-mayhem/mayhem-firmware/blob/b66d8b1aa178d8a9cd06436fea788d5d58cb4c8d/firmware/tools/generate_world_map.bin.py
https://github.com/portapack-mayhem/mayhem-firmware/releases https://github.com/portapack-mayhem/mayhem-firmware/releases
- Weather app: https://lab.flipper.net/apps/flip_weather - Weather app: https://lab.flipper.net/apps/flip_weather
- wget app: https://lab.flipper.net/apps/web_crawler (add profiles for known public APIs?) - wget app: https://lab.flipper.net/apps/web_crawler (add profiles for known public APIs?)
- USB implementation to make device act as mass storage device.
- System logger
- BlueTooth keyboard app - BlueTooth keyboard app
- Chip 8 emulator - Chip 8 emulator
- BadUSB (in December 2024, TinyUSB has a bug where uninstalling and re-installing the driver fails) - BadUSB (in December 2024, TinyUSB has a bug where uninstalling and re-installing the driver fails)
@@ -70,7 +65,5 @@
- GPS app - GPS app
- Investigate CSI https://stevenmhernandez.github.io/ESP32-CSI-Tool/ - Investigate CSI https://stevenmhernandez.github.io/ESP32-CSI-Tool/
- Compile unix tools to ELF apps? - Compile unix tools to ELF apps?
- Calculator
- Text editor
- Todo list - Todo list
- Calendar - Calendar
+22
View File
@@ -0,0 +1,22 @@
# Releasing Tactility
1. Test the latest version on several devices
2. Build the SDK locally and test it with `ExternalApps/HelloWorld`
3. Test the latest SDK build from GitHub with the CDN:
1. Download it from the [main branch](https://github.com/ByteWelder/Tactility/actions/workflows/build-sdk.yml)
2. Upload it to the [CDN](https://dash.cloudflare.com)
3. Update `sdk.json` from [TactilityTool](https://github.com/ByteWelder/TactilityTool) and upload it to [CDN](https://dash.cloudflare.com)
4. Test it with `ExternalApps/HelloWorld` (clear all its cache and update the SDK version)
4. Download the latest firmwares [main branch](https://github.com/ByteWelder/Tactility/actions/workflows/build-firmware.yml)
5. Prepare a new version of [TactilityWebInstaller](https://github.com/ByteWelder/TactilityWebInstaller) locally
6. Test the firmwares on all devices with the local web installer
7. If all went well: release the web installer
8. Test web installer in production (use popular devices)
9. Make a new version of the docs available at [TactilityDocs](https://github.com/ByteWelder/TactilityDocs)
10. Make a new [GitHub release](https://github.com/ByteWelder/Tactility/releases/new)
### Post-release
1. Mention on Discord
2. Consider notifying vendors/stakeholders
3. Remove dev versions in `sdk.json`from [TactilityTool](https://github.com/ByteWelder/TactilityTool) and upload it to [CDN](https://dash.cloudflare.com)
+4 -7
View File
@@ -12,15 +12,12 @@ static void touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data) {
touch->readLast(data); touch->readLast(data);
} }
static int32_t threadCallback(void* context) {
auto* touch = (Ft6x36Touch*)context;
touch->driverThreadMain();
return 0;
}
Ft6x36Touch::Ft6x36Touch(std::unique_ptr<Configuration> inConfiguration) : Ft6x36Touch::Ft6x36Touch(std::unique_ptr<Configuration> inConfiguration) :
configuration(std::move(inConfiguration)), configuration(std::move(inConfiguration)),
driverThread(tt::Thread("ft6x36", 4096, threadCallback, this)) driverThread(tt::Thread("ft6x36", 4096, [this]() {
driverThreadMain();
return 0;
}))
{} {}
Ft6x36Touch::~Ft6x36Touch() { Ft6x36Touch::~Ft6x36Touch() {
+2
View File
@@ -0,0 +1,2 @@
build*/
.tactility/
-4
View File
@@ -1,4 +0,0 @@
rm sdkconfig
cp ../../sdkconfig sdkconfig
cat sdkconfig.override >> sdkconfig
idf.py elf_app
@@ -1,2 +0,0 @@
CONFIG_PARTITION_TABLE_SINGLE_APP=y
CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=n
@@ -0,0 +1,2 @@
[sdk]
version = 0.4.0
+410
View File
@@ -0,0 +1,410 @@
import configparser
import json
import os
import re
import shutil
import sys
import subprocess
import time
import urllib.request
import zipfile
# Targetable platforms that represent a specific hardware target
platform_targets = ["esp32", "esp32s3"]
# All valid platform commandline arguments
platform_arguments = platform_targets.copy()
platform_arguments.append("all")
ttbuild_path = ".tactility"
ttbuild_version = "1.0.0"
ttbuild_properties_file = "tactility.properties"
ttbuild_cdn = "https://cdn.tactility.one"
ttbuild_sdk_json_validity = 3600 # seconds
verbose = False
use_local_sdk = False
spinner_pattern = [
"⠋",
"⠙",
"⠹",
"⠸",
"⠼",
"⠴",
"⠦",
"⠧",
"⠇",
"⠏"
]
if sys.platform == "win32":
shell_color_red = ""
shell_color_orange = ""
shell_color_green = ""
shell_color_purple = ""
shell_color_cyan = ""
shell_color_reset = ""
else:
shell_color_red = "\033[91m"
shell_color_orange = "\033[93m"
shell_color_green = "\033[32m"
shell_color_purple = "\033[35m"
shell_color_cyan = "\033[36m"
shell_color_reset = "\033[m"
def print_help():
print("Usage: python tactility.py [action] [options]")
print("")
print("Actions:")
print(" build [esp32,esp32s3,all,local] Build the app for the specified platform")
print(" esp32: ESP32")
print(" esp32s3: ESP32 S3")
print(" all: all supported ESP platforms")
print(" clean Clean the build folders")
print(" clearcache Clear the SDK cache")
print(" updateself Update this tool")
print("")
print("Options:")
print(" --help Show this commandline info")
print(" --local-sdk Use SDK specifiedc by environment variable TACTILITY_SDK_PATH")
print(" --skip-build Run everything except the idf.py/CMake commands")
print(" --verbose Show extra console output")
def download_file(url, filepath):
global verbose
if verbose:
print(f"Downloading from {url} to {filepath}")
request = urllib.request.Request(
url,
data=None,
headers={
"User-Agent": f"Tactility Build Tool {ttbuild_version}"
}
)
try:
response = urllib.request.urlopen(request)
file = open(filepath, mode="wb")
file.write(response.read())
file.close()
return True
except OSError as error:
if verbose:
print_error(f"Failed to fetch URL {url}\n{error}")
return False
def print_warning(message):
print(f"{shell_color_orange}WARNING: {message}{shell_color_reset}")
def print_error(message):
print(f"{shell_color_red}ERROR: {message}{shell_color_reset}")
def exit_with_error(message):
print_error(message)
sys.exit(1)
def is_valid_platform_name(name):
global platform_arguments
return name in platform_arguments
def validate_environment():
global ttbuild_properties_file, use_local_sdk
if os.environ.get("IDF_PATH") is None:
exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh")
if not os.path.exists(ttbuild_properties_file):
exit_with_error(f"{ttbuild_properties_file} file not found")
if use_local_sdk == False and os.environ.get("TACTILITY_SDK_PATH") is not None:
print_warning("TACTILITY_SDK_PATH is set, but will be ignored by this command.")
print_warning("If you want to use it, use the 'build local' parameters.")
elif use_local_sdk == True and os.environ.get("TACTILITY_SDK_PATH") is None:
exit_with_error("local build was requested, but TACTILITY_SDK_PATH environment variable is not set.")
def setup_environment():
global ttbuild_path
os.makedirs(ttbuild_path, exist_ok=True)
def get_sdk_dir(version, platform):
global use_local_sdk
if use_local_sdk:
return os.environ.get("TACTILITY_SDK_PATH")
else:
global ttbuild_cdn
return os.path.join(ttbuild_path, f"{version}-{platform}", "TactilitySDK")
def get_sdk_version():
global ttbuild_properties_file
parser = configparser.RawConfigParser()
parser.read(ttbuild_properties_file)
sdk_dict = dict(parser.items("sdk"))
if not "version" in sdk_dict:
exit_with_error(f"Could not find 'version' in [sdk] section in {ttbuild_properties_file}")
return sdk_dict["version"]
def get_sdk_root_dir(version, platform):
global ttbuild_cdn
return os.path.join(ttbuild_path, f"{version}-{platform}")
def get_sdk_url(version, platform):
global ttbuild_cdn
return f"{ttbuild_cdn}/TactilitySDK-{version}-{platform}.zip"
def sdk_exists(version, platform):
sdk_dir = get_sdk_dir(version, platform)
return os.path.isdir(sdk_dir)
def should_update_sdk_json():
global ttbuild_cdn
json_filepath = os.path.join(ttbuild_path, "sdk.json")
if os.path.exists(json_filepath):
json_modification_time = os.path.getmtime(json_filepath)
now = time.time()
global ttbuild_sdk_json_validity
minimum_seconds_difference = ttbuild_sdk_json_validity
return (now - json_modification_time) > minimum_seconds_difference
else:
return True
def update_sdk_json():
global ttbuild_cdn, ttbuild_path
json_url = f"{ttbuild_cdn}/sdk.json"
json_filepath = os.path.join(ttbuild_path, "sdk.json")
return download_file(json_url, json_filepath)
def should_fetch_sdkconfig_files():
for platform in platform_targets:
sdkconfig_filename = f"sdkconfig.app.{platform}"
if not os.path.exists(os.path.join(ttbuild_path, sdkconfig_filename)):
return True
return False
def fetch_sdkconfig_files():
for platform in platform_targets:
sdkconfig_filename = f"sdkconfig.app.{platform}"
target_path = os.path.join(ttbuild_path, sdkconfig_filename)
if not download_file(f"{ttbuild_cdn}/{sdkconfig_filename}", target_path):
exit_with_error(f"Failed to download sdkconfig file for {platform}")
def validate_version_and_platforms(sdk_json, sdk_version, platforms_to_build):
version_map = sdk_json["versions"]
if not sdk_version in version_map:
exit_with_error(f"Version not found: {sdk_version}")
version_data = version_map[sdk_version]
available_platforms = version_data["platforms"]
for desired_platform in platforms_to_build:
if not desired_platform in available_platforms:
exit_with_error(f"Platform {desired_platform} is not available. Available ones: {available_platforms}")
def validate_self(sdk_json):
if not "toolVersion" in sdk_json:
exit_with_error("Server returned invalid SDK data format (toolVersion not found)")
if not "toolCompatibility" in sdk_json:
exit_with_error("Server returned invalid SDK data format (toolCompatibility not found)")
if not "toolDownloadUrl" in sdk_json:
exit_with_error("Server returned invalid SDK data format (toolDownloadUrl not found)")
tool_version = sdk_json["toolVersion"]
tool_compatibility = sdk_json["toolCompatibility"]
if tool_version != ttbuild_version:
print_warning(f"New version available: {tool_version} (currently using {ttbuild_version})")
print_warning(f"Run 'tactility.py updateself' to update.")
if re.search(tool_compatibility, ttbuild_version) is None:
print_error("The tool is not compatible anymore.")
print_error("Run 'tactility.py updateself' to update.")
sys.exit(1)
def sdk_download(version, platform):
sdk_root_dir = get_sdk_root_dir(version, platform)
os.makedirs(sdk_root_dir, exist_ok=True)
sdk_url = get_sdk_url(version, platform)
filepath = os.path.join(sdk_root_dir, f"{version}-{platform}.zip")
print(f"Downloading SDK version {version} for {platform}")
if download_file(sdk_url, filepath):
with zipfile.ZipFile(filepath, "r") as zip_ref:
zip_ref.extractall(os.path.join(sdk_root_dir, "TactilitySDK"))
return True
else:
return False
def sdk_download_all(version, platforms):
for platform in platforms:
if not sdk_exists(version, platform):
if not sdk_download(version, platform):
return False
else:
if verbose:
print(f"Using cached download for SDK version {version} and platform {platform}")
return True
def find_elf_file(platform):
build_dir = f"build-{platform}"
if os.path.exists(build_dir):
for file in os.listdir(build_dir):
if file.endswith(".app.elf"):
return os.path.join(build_dir, file)
return None
def build_all(version, platforms, skip_build):
for platform in platforms:
# First build command must be "idf.py build", otherwise it fails to execute "idf.py elf"
# We check if the ELF file exists and run the correct command
# This can lead to code caching issues, so sometimes a clean build is required
if find_elf_file(platform) is None:
if not build_first(version, platform, skip_build):
break
else:
if not build_consecutively(version, platform, skip_build):
break
def wait_for_build(process, platform):
buffer = []
os.set_blocking(process.stdout.fileno(), False)
while process.poll() is None:
for i in spinner_pattern:
time.sleep(0.1)
progress_text = f"Building for {platform} {shell_color_cyan}" + str(i) + shell_color_reset
sys.stdout.write(progress_text + "\r")
while True:
line = process.stdout.readline()
decoded_line = line.decode("UTF-8")
if decoded_line != "":
buffer.append(decoded_line)
else:
break
return buffer
# The first build must call "idf.py build" and consecutive builds must call "idf.py elf" as it finishes faster.
# The problem is that the "idf.py build" always results in an error, even though the elf file is created.
# The solution is to suppress the error if we find that the elf file was created.
def build_first(version, platform, skip_build):
sdk_dir = get_sdk_dir(version, platform)
if verbose:
print(f"Using SDK at {sdk_dir}")
os.environ["TACTILITY_SDK_PATH"] = sdk_dir
sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}")
os.system(f"cp {sdkconfig_path} sdkconfig")
elf_path = find_elf_file(platform)
# Remove previous elf file: re-creation of the file is used to measure if the build succeeded,
# as the actual build job will always fail due to technical issues with the elf cmake script
if elf_path is not None:
os.remove(elf_path)
if skip_build:
return True
print("Building first build")
with subprocess.Popen(["idf.py", "-B", f"build-{platform}", "build"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process:
build_output = wait_for_build(process, platform)
# The return code is never expected to be 0 due to a bug in the elf cmake script, but we keep it just in case
if process.returncode == 0:
print(f"{shell_color_green}Building for {platform} ✅{shell_color_reset}")
return True
else:
if find_elf_file(platform) is None:
for line in build_output:
print(line, end="")
print(f"{shell_color_red}Building for {platform} failed ❌{shell_color_reset}")
return False
else:
print(f"{shell_color_green}Building for {platform} ✅{shell_color_reset}")
return True
def build_consecutively(version, platform, skip_build):
sdk_dir = get_sdk_dir(version, platform)
if verbose:
print(f"Using SDK at {sdk_dir}")
os.environ["TACTILITY_SDK_PATH"] = sdk_dir
sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}")
os.system(f"cp {sdkconfig_path} sdkconfig")
if skip_build:
return True
with subprocess.Popen(["idf.py", "-B", f"build-{platform}", "elf"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as process:
build_output = wait_for_build(process, platform)
if process.returncode == 0:
print(f"{shell_color_green}Building for {platform} ✅{shell_color_reset}")
return True
else:
for line in build_output:
print(line, end="")
print(f"{shell_color_red}Building for {platform} failed ❌{shell_color_reset}")
return False
def read_sdk_json():
json_file_path = os.path.join(ttbuild_path, "sdk.json")
json_file = open(json_file_path)
return json.load(json_file)
def build_action(platform_arg):
# Environment validation
validate_environment()
# Environment setup
setup_environment()
platforms_to_build = platform_targets if platform_arg == "all" else [platform_arg]
if not is_valid_platform_name(platform_arg):
print_help()
exit_with_error("Invalid platform name")
if not use_local_sdk:
if should_fetch_sdkconfig_files():
fetch_sdkconfig_files()
# Update SDK cache
if should_update_sdk_json() and not update_sdk_json():
exit_with_error("Failed to retrieve SDK info")
sdk_json = read_sdk_json()
validate_self(sdk_json)
if not "versions" in sdk_json:
exit_with_error("Version data not found in sdk.json")
# Build
sdk_version = get_sdk_version()
if not use_local_sdk:
validate_version_and_platforms(sdk_json, sdk_version, platforms_to_build)
if not sdk_download_all(sdk_version, platforms_to_build):
exit_with_error("Failed to download one or more SDKs")
build_all(sdk_version, platforms_to_build, skip_build) # Environment validation
def clean_action():
count = 0
for path in os.listdir("."):
if path.startswith("build-"):
print(f"Removing {path}/")
shutil.rmtree(path)
count = count + 1
if count == 0:
print("Nothing to clean")
def clear_cache_action():
if os.path.exists(ttbuild_path):
print(f"Removing {ttbuild_path}/")
shutil.rmtree(ttbuild_path)
else:
print("Nothing to clear")
def update_self_action():
sdk_json = read_sdk_json()
tool_download_url = sdk_json["toolDownloadUrl"]
if download_file(tool_download_url, "tactility.py"):
print("Updated")
else:
exit_with_error("Update failed")
if __name__ == "__main__":
print(f"Tactility Build System v{ttbuild_version}")
if "--help" in sys.argv:
print_help()
sys.exit()
# Argument validation
if len(sys.argv) == 1:
print_help()
sys.exit()
action_arg = sys.argv[1]
verbose = "--verbose" in sys.argv
skip_build = "--skip-build" in sys.argv
use_local_sdk = "--local-sdk" in sys.argv
# Actions
if action_arg == "build":
if len(sys.argv) < 3:
print_help()
exit_with_error("Commandline parameter missing")
build_action(sys.argv[2])
elif action_arg == "clean":
clean_action()
elif action_arg == "clearcache":
clear_cache_action()
elif action_arg == "updateself":
update_self_action()
else:
print_help()
exit_with_error("Unknown commandline parameter")
+1 -1
View File
@@ -558,7 +558,7 @@
#if LV_USE_THEME_DEFAULT #if LV_USE_THEME_DEFAULT
/*0: Light mode; 1: Dark mode*/ /*0: Light mode; 1: Dark mode*/
#define LV_THEME_DEFAULT_DARK 0 #define LV_THEME_DEFAULT_DARK 1
/*1: Enable grow on press*/ /*1: Enable grow on press*/
#define LV_THEME_DEFAULT_GROW 1 #define LV_THEME_DEFAULT_GROW 1
+12 -8
View File
@@ -7,11 +7,15 @@
#define TT_ASSETS_UI_SPINNER TT_ASSET("spinner.png") #define TT_ASSETS_UI_SPINNER TT_ASSET("spinner.png")
// App icons // App icons
#define TT_ASSETS_APP_ICON_FALLBACK TT_ASSET("app_icon_fallback.png") #define TT_ASSETS_APP_ICON_FALLBACK TT_ASSET("app_icon_fallback_dark_mode.png")
#define TT_ASSETS_APP_ICON_FILES TT_ASSET("app_icon_files.png") #define TT_ASSETS_APP_ICON_FILES TT_ASSET("app_icon_files_dark_mode.png")
#define TT_ASSETS_APP_ICON_DISPLAY_SETTINGS TT_ASSET("app_icon_display_settings.png") #define TT_ASSETS_APP_ICON_DISPLAY_SETTINGS TT_ASSET("app_icon_display_settings_dark_mode.png")
#define TT_ASSETS_APP_ICON_POWER_SETTINGS TT_ASSET("app_icon_power_settings.png") #define TT_ASSETS_APP_ICON_POWER_SETTINGS TT_ASSET("app_icon_power_settings_dark_mode.png")
#define TT_ASSETS_APP_ICON_I2C_SETTINGS TT_ASSET("app_icon_i2c.png") #define TT_ASSETS_APP_ICON_I2C_SETTINGS TT_ASSET("app_icon_i2c_dark_mode.png")
#define TT_ASSETS_APP_ICON_SETTINGS TT_ASSET("app_icon_settings.png") #define TT_ASSETS_APP_ICON_SETTINGS TT_ASSET("app_icon_settings_dark_mode.png")
#define TT_ASSETS_APP_ICON_SYSTEM_INFO TT_ASSET("app_icon_system_info.png") #define TT_ASSETS_APP_ICON_SYSTEM_INFO TT_ASSET("app_icon_system_info_dark_mode.png")
#define TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS TT_ASSET("app_icon_time_date_settings.png") #define TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS TT_ASSET("app_icon_time_date_settings_dark_mode.png")
#define TT_ASSETS_APP_ICON_CALCULATOR TT_ASSET("app_icon_calculator_dark_mode.png")
#define TT_ASSETS_APP_ICON_NOTES TT_ASSET("app_icon_notes_dark_mode.png")
#define TT_ASSETS_APP_ICON_CHAT TT_ASSET("app_icon_chat_dark_mode.png")
#define TT_ASSETS_APP_ICON_GPIO TT_ASSET("app_icon_gpio_dark_mode.png")
+5 -1
View File
@@ -8,9 +8,13 @@ namespace tt {
/** /**
* Settings that persist on NVS flash for ESP32. * Settings that persist on NVS flash for ESP32.
* On simulator, the settings are only in-memory. * On simulator, the settings are only in-memory.
*
* Note that on ESP32, there are limitations:
* - namespace name is limited by NVS_NS_NAME_MAX_SIZE (generally 16 characters)
* - key is limited by NVS_KEY_NAME_MAX_SIZE (generally 16 characters)
*/ */
class Preferences { class Preferences {
private:
const char* namespace_; const char* namespace_;
public: public:
+6 -6
View File
@@ -16,9 +16,9 @@ namespace tt::app {
class AppContext; class AppContext;
enum class Result; enum class Result;
class App { typedef unsigned int LaunchId;
private: class App {
Mutex mutex; Mutex mutex;
@@ -44,7 +44,7 @@ public:
virtual void onDestroy(AppContext& appContext) {} virtual void onDestroy(AppContext& appContext) {}
virtual void onShow(AppContext& appContext, lv_obj_t* parent) {} virtual void onShow(AppContext& appContext, lv_obj_t* parent) {}
virtual void onHide(AppContext& appContext) {} virtual void onHide(AppContext& appContext) {}
virtual void onResult(AppContext& appContext, Result result, std::unique_ptr<Bundle> _Nullable resultData) {} virtual void onResult(AppContext& appContext, LaunchId launchId, Result result, std::unique_ptr<Bundle> _Nullable resultData) {}
Mutex& getMutex() { return mutex; } Mutex& getMutex() { return mutex; }
@@ -83,15 +83,15 @@ std::shared_ptr<App> create() { return std::shared_ptr<T>(new T); }
* @param[in] id application name or id * @param[in] id application name or id
* @param[in] parameters optional parameters to pass onto the application * @param[in] parameters optional parameters to pass onto the application
*/ */
void start(const std::string& id, std::shared_ptr<const Bundle> _Nullable parameters = nullptr); LaunchId start(const std::string& id, std::shared_ptr<const Bundle> _Nullable parameters = nullptr);
/** @brief Stop the currently showing app. Show the previous app if any app was still running. */ /** @brief Stop the currently showing app. Show the previous app if any app was still running. */
void stop(); void stop();
/** @return the currently running app context (it is only ever null before the splash screen is shown) */ /** @return the currently running app context (it is only ever null before the splash screen is shown) */
std::shared_ptr<app::AppContext> _Nullable getCurrentAppContext(); std::shared_ptr<AppContext> _Nullable getCurrentAppContext();
/** @return the currently running app (it is only ever null before the splash screen is shown) */ /** @return the currently running app (it is only ever null before the splash screen is shown) */
std::shared_ptr<app::App> _Nullable getCurrentApp(); std::shared_ptr<App> _Nullable getCurrentApp();
} }
+1 -1
View File
@@ -12,7 +12,7 @@ typedef void (*OnCreate)(void* appContext, void* _Nullable data);
typedef void (*OnDestroy)(void* appContext, void* _Nullable data); typedef void (*OnDestroy)(void* appContext, void* _Nullable data);
typedef void (*OnShow)(void* appContext, void* _Nullable data, lv_obj_t* parent); typedef void (*OnShow)(void* appContext, void* _Nullable data, lv_obj_t* parent);
typedef void (*OnHide)(void* appContext, void* _Nullable data); typedef void (*OnHide)(void* appContext, void* _Nullable data);
typedef void (*OnResult)(void* appContext, void* _Nullable data, Result result, Bundle* resultData); typedef void (*OnResult)(void* appContext, void* _Nullable data, LaunchId launchId, Result result, Bundle* resultData);
void setElfAppManifest( void setElfAppManifest(
const char* name, const char* name,
@@ -5,8 +5,15 @@
namespace tt::app::display { namespace tt::app::display {
void setBacklightDuty(uint8_t value); void setBacklightDuty(uint8_t value);
uint8_t getBacklightDuty();
bool getBacklightDuty(uint8_t& duty);
void setGammaCurve(uint8_t curveIndex);
bool getGammaCurve(uint8_t& curveIndex);
void setRotation(lv_display_rotation_t rotation); void setRotation(lv_display_rotation_t rotation);
lv_display_rotation_t getRotation(); lv_display_rotation_t getRotation();
} // namespace } // namespace
@@ -0,0 +1,7 @@
#pragma once
namespace tt::app::filebrowser {
void start();
} // namespace
@@ -1,15 +0,0 @@
#pragma once
#include "app/files/View.h"
#include "app/files/State.h"
#include "app/AppManifest.h"
#include <lvgl.h>
#include <dirent.h>
#include <memory>
namespace tt::app::files {
void start();
} // namespace
@@ -0,0 +1,23 @@
#pragma once
namespace tt::app::fileselection {
/**
* Show a file selection dialog that allows the user to select an existing file.
* This app returns the absolute file path as a result.
*/
LaunchId startForExistingFile();
/**
* Show a file selection dialog that allows the user to select a new or existing file.
* This app returns the absolute file path as a result.
*/
LaunchId startForExistingOrNewFile();
/**
* @param bundle the result bundle of an app
* @return the path from the bundle, or empty string if none is present
*/
std::string getResultPath(const Bundle& bundle);
} // namespace
@@ -98,6 +98,8 @@ public:
lv_dropdown_set_options(busDropdown, bus_options.c_str()); lv_dropdown_set_options(busDropdown, bus_options.c_str());
lv_obj_align(busDropdown, LV_ALIGN_TOP_RIGHT, 0, 0); lv_obj_align(busDropdown, LV_ALIGN_TOP_RIGHT, 0, 0);
lv_obj_set_width(busDropdown, LV_PCT(50)); lv_obj_set_width(busDropdown, LV_PCT(50));
lv_obj_set_style_border_color(busDropdown, lv_color_hex(0xFAFAFA), LV_PART_MAIN);
lv_obj_set_style_border_width(busDropdown, 1, LV_PART_MAIN);
int32_t bus_index = 0; int32_t bus_index = 0;
preferences.optInt32("bus", bus_index); preferences.optInt32("bus", bus_index);
if (bus_index < uartNames.size()) { if (bus_index < uartNames.size()) {
@@ -1,6 +1,5 @@
#pragma once #pragma once
#include "Tactility/hal/gps/GpsDevice.h"
#include "Tactility/hal/sdcard/SdCardDevice.h" #include "Tactility/hal/sdcard/SdCardDevice.h"
#include "Tactility/hal/spi/Spi.h" #include "Tactility/hal/spi/Spi.h"
#include "Tactility/hal/uart/Uart.h" #include "Tactility/hal/uart/Uart.h"
@@ -53,7 +53,7 @@ std::shared_ptr<SdCardDevice> _Nullable find(const std::string& path);
* Always calls the function, but doesn't lock if the path is not an SD card path. * Always calls the function, but doesn't lock if the path is not an SD card path.
*/ */
template<typename ReturnType> template<typename ReturnType>
inline ReturnType withSdCardLock(const std::string& path, std::function<ReturnType()> fn) { ReturnType withSdCardLock(const std::string& path, std::function<ReturnType()> fn) {
auto sdcard = find(path); auto sdcard = find(path);
if (sdcard != nullptr) { if (sdcard != nullptr) {
auto scoped_lockable = sdcard->getLock().asScopedLock(); auto scoped_lockable = sdcard->getLock().asScopedLock();
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include <lvgl.h>
lv_color_t lv_color_foreground();
lv_color_t lv_color_background();
lv_color_t lv_color_background_darkest();
@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "lvgl.h" #include <lvgl.h>
namespace tt::lvgl { namespace tt::lvgl {
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <lvgl.h>
#include "./Colors.h"
+1 -1
View File
@@ -1,4 +1,4 @@
#include "lvgl.h" #include <lvgl.h>
namespace tt::lvgl { namespace tt::lvgl {
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "lvgl.h" #include <lvgl.h>
namespace tt::lvgl { namespace tt::lvgl {
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once #pragma once
#include "lvgl.h"
#include "../app/AppContext.h" #include "../app/AppContext.h"
#include <lvgl.h>
namespace tt::lvgl { namespace tt::lvgl {
@@ -4,7 +4,6 @@
#include <Tactility/Bundle.h> #include <Tactility/Bundle.h>
#include <Tactility/PubSub.h> #include <Tactility/PubSub.h>
#include <Tactility/service/ServiceManifest.h>
#include <memory> #include <memory>
@@ -30,7 +29,7 @@ struct LoaderEvent {
* @param[in] id application name or id * @param[in] id application name or id
* @param[in] parameters optional parameters to pass onto the application * @param[in] parameters optional parameters to pass onto the application
*/ */
void startApp(const std::string& id, std::shared_ptr<const Bundle> _Nullable parameters = nullptr); app::LaunchId startApp(const std::string& id, std::shared_ptr<const Bundle> _Nullable parameters = nullptr);
/** @brief Stop the currently showing app. Show the previous app if any app was still running. */ /** @brief Stop the currently showing app. Show the previous app if any app was still running. */
void stopApp(); void stopApp();
+13 -9
View File
@@ -25,22 +25,21 @@ enum class State {
*/ */
class AppInstance : public AppContext { class AppInstance : public AppContext {
private:
Mutex mutex = Mutex(Mutex::Type::Normal); Mutex mutex = Mutex(Mutex::Type::Normal);
const std::shared_ptr<AppManifest> manifest; const std::shared_ptr<AppManifest> manifest;
State state = State::Initial; State state = State::Initial;
LaunchId launchId;
Flags flags = { .showStatusbar = true }; Flags flags = { .showStatusbar = true };
/** @brief Optional parameters to start the app with /** @brief Optional parameters to start the app with
* When these are stored in the app struct, the struct takes ownership. * When these are stored in the app struct, the struct takes ownership.
* Do not mutate after app creation. * Do not mutate after app creation.
*/ */
std::shared_ptr<const tt::Bundle> _Nullable parameters; std::shared_ptr<const Bundle> _Nullable parameters;
std::shared_ptr<App> app; std::shared_ptr<App> app;
static std::shared_ptr<app::App> createApp( static std::shared_ptr<App> createApp(
const std::shared_ptr<app::AppManifest>& manifest const std::shared_ptr<AppManifest>& manifest
) { ) {
if (manifest->location.isInternal()) { if (manifest->location.isInternal()) {
assert(manifest->createApp != nullptr); assert(manifest->createApp != nullptr);
@@ -50,7 +49,7 @@ private:
TT_LOG_W("", "Manifest specifies createApp, but this is not used with external apps"); TT_LOG_W("", "Manifest specifies createApp, but this is not used with external apps");
} }
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
return app::createElfApp(manifest); return createElfApp(manifest);
#else #else
tt_crash("not supported"); tt_crash("not supported");
#endif #endif
@@ -61,18 +60,23 @@ private:
public: public:
explicit AppInstance(const std::shared_ptr<AppManifest>& manifest) : explicit AppInstance(const std::shared_ptr<AppManifest>& manifest, LaunchId launchId) :
manifest(manifest), manifest(manifest),
launchId(launchId),
app(createApp(manifest)) app(createApp(manifest))
{} {}
AppInstance(const std::shared_ptr<AppManifest>& manifest, std::shared_ptr<const Bundle> parameters) : AppInstance(const std::shared_ptr<AppManifest>& manifest, LaunchId launchId, std::shared_ptr<const Bundle> parameters) :
manifest(manifest), manifest(manifest),
launchId(launchId),
parameters(std::move(parameters)), parameters(std::move(parameters)),
app(createApp(manifest)) {} app(createApp(manifest))
{}
~AppInstance() override = default; ~AppInstance() override = default;
LaunchId getLaunchId() const { return launchId; }
void setState(State state); void setState(State state);
State getState() const; State getState() const;
@@ -6,7 +6,7 @@
#include <vector> #include <vector>
#include <dirent.h> #include <dirent.h>
namespace tt::app::files { namespace tt::app::filebrowser {
class State { class State {
@@ -0,0 +1,11 @@
#pragma once
#include <string>
namespace tt::app::filebrowser {
bool isSupportedExecutableFile(const std::string& filename);
bool isSupportedImageFile(const std::string& filename);
bool isSupportedTextFile(const std::string& filename);
} // namespace
@@ -7,7 +7,7 @@
#include <lvgl.h> #include <lvgl.h>
#include <memory> #include <memory>
namespace tt::app::files { namespace tt::app::filebrowser {
class View { class View {
std::shared_ptr<State> state; std::shared_ptr<State> state;
@@ -1,66 +0,0 @@
#pragma once
#include <dirent.h>
#include <string>
#include <sys/stat.h>
#include <vector>
namespace tt::app::files {
/** File types for `dirent`'s `d_type`. */
enum {
TT_DT_UNKNOWN = 0,
#define TT_DT_UNKNOWN TT_DT_UNKNOWN // Unknown type
TT_DT_FIFO = 1,
#define TT_DT_FIFO TT_DT_FIFO // Named pipe or FIFO
TT_DT_CHR = 2,
#define TT_DT_CHR TT_DT_CHR // Character device
TT_DT_DIR = 4,
#define TT_DT_DIR TT_DT_DIR // Directory
TT_DT_BLK = 6,
#define TT_DT_BLK TT_DT_BLK // Block device
TT_DT_REG = 8,
#define TT_DT_REG TT_DT_REG // Regular file
TT_DT_LNK = 10,
#define TT_DT_LNK TT_DT_LNK // Symbolic link
TT_DT_SOCK = 12,
#define TT_DT_SOCK TT_DT_SOCK // Local-domain socket
TT_DT_WHT = 14
#define TT_DT_WHT TT_DT_WHT // Whiteout inodes
};
std::string getChildPath(const std::string& basePath, const std::string& childPath);
typedef int (*ScandirFilter)(const struct dirent*);
typedef bool (*ScandirSort)(const struct dirent&, const struct dirent&);
bool dirent_sort_alpha_and_type(const struct dirent& left, const struct dirent& right);
int dirent_filter_dot_entries(const struct dirent* entry);
/**
* A scandir()-like implementation that works on ESP32.
* It does not return "." and ".." items but otherwise functions the same.
* It returns an allocated output array with allocated dirent instances.
* The caller is responsible for free-ing the memory of these.
*
* @param[in] path path the scan for files and directories
* @param[out] outList a pointer to vector of dirent
* @param[in] filter an optional filter to filter out specific items
* @param[in] sort an optional sorting function
* @return the amount of items that were stored in "output" or -1 when an error occurred
*/
int scandir(
const std::string& path,
std::vector<dirent>& outList,
ScandirFilter _Nullable filter,
ScandirSort _Nullable sort
);
bool isSupportedExecutableFile(const std::string& filename);
bool isSupportedImageFile(const std::string& filename);
bool isSupportedTextFile(const std::string& filename);
} // namespace
@@ -0,0 +1,14 @@
#pragma once
#include <Tactility/Bundle.h>
namespace tt::app::fileselection {
enum class Mode {
Existing = 0,
ExistingOrNew = 1
};
Mode getMode(const Bundle& bundle);
}
@@ -0,0 +1,59 @@
#pragma once
#include <Tactility/Mutex.h>
#include <string>
#include <vector>
#include <dirent.h>
namespace tt::app::fileselection {
class State {
Mutex mutex = Mutex(Mutex::Type::Recursive);
std::vector<dirent> dir_entries;
std::string current_path;
std::string selected_child_entry;
public:
State();
void freeEntries() {
dir_entries.clear();
}
~State() {
freeEntries();
}
bool setEntriesForChildPath(const std::string& child_path);
bool setEntriesForPath(const std::string& path);
template <std::invocable<const std::vector<dirent> &> Func>
void withEntries(Func&& onEntries) const {
mutex.withLock([&]() {
std::invoke(std::forward<Func>(onEntries), dir_entries);
});
}
bool getDirent(uint32_t index, dirent& dirent);
void setSelectedChildEntry(const std::string& newFile) {
selected_child_entry = newFile;
}
std::string getSelectedChildEntry() const { return selected_child_entry; }
std::string getCurrentPath() const { return current_path; }
std::string getCurrentPathWithTrailingSlash() const {
if (current_path.length() > 1) {
return current_path + "/";
} else {
return current_path;
}
}
std::string getSelectedChildPath() const;
};
}
@@ -0,0 +1,44 @@
#pragma once
#include "./State.h"
#include "./FileSelectionPrivate.h"
#include "Tactility/app/AppManifest.h"
#include <lvgl.h>
#include <memory>
namespace tt::app::fileselection {
class View {
std::shared_ptr<State> state;
lv_obj_t* dir_entry_list = nullptr;
lv_obj_t* navigate_up_button = nullptr;
lv_obj_t* path_textarea = nullptr;
lv_obj_t* select_button = nullptr;
std::function<void(std::string path)> on_file_selected;
void onTapFile(const std::string&path, const std::string&filename);
static void onSelectButtonPressed(lv_event_t* event);
static void onPathTextChanged(lv_event_t* event);
void createDirEntryWidget(lv_obj_t* parent, dirent& dir_entry);
public:
explicit View(const std::shared_ptr<State>& state, std::function<void(const std::string& path)> onFileSelected) :
state(state),
on_file_selected(std::move(onFileSelected))
{}
void init(lv_obj_t* parent, Mode mode);
void update();
void onNavigateUpPressed();
void onDirEntryPressed(uint32_t index);
void onFileSelected(const std::string& path) const {
on_file_selected(path);
}
};
}
@@ -1,13 +1,5 @@
#pragma once #pragma once
#include <TactilityCore.h>
#include <Mutex.h>
#include <Thread.h>
#include "lvgl.h"
#include <Tactility/hal/i2c/I2c.h>
#include "Timer.h"
#include <memory>
namespace tt::app::i2cscanner { namespace tt::app::i2cscanner {
void start(); void start();
+8 -2
View File
@@ -33,10 +33,12 @@ namespace app {
namespace addgps { extern const AppManifest manifest; } namespace addgps { extern const AppManifest manifest; }
namespace alertdialog { extern const AppManifest manifest; } namespace alertdialog { extern const AppManifest manifest; }
namespace applist { extern const AppManifest manifest; } namespace applist { extern const AppManifest manifest; }
namespace calculator { extern const AppManifest manifest; }
namespace chat { extern const AppManifest manifest; } namespace chat { extern const AppManifest manifest; }
namespace boot { extern const AppManifest manifest; } namespace boot { extern const AppManifest manifest; }
namespace display { extern const AppManifest manifest; } namespace display { extern const AppManifest manifest; }
namespace files { extern const AppManifest manifest; } namespace filebrowser { extern const AppManifest manifest; }
namespace fileselection { extern const AppManifest manifest; }
namespace gpio { extern const AppManifest manifest; } namespace gpio { extern const AppManifest manifest; }
namespace gpssettings { extern const AppManifest manifest; } namespace gpssettings { extern const AppManifest manifest; }
namespace i2cscanner { extern const AppManifest manifest; } namespace i2cscanner { extern const AppManifest manifest; }
@@ -45,6 +47,7 @@ namespace app {
namespace inputdialog { extern const AppManifest manifest; } namespace inputdialog { extern const AppManifest manifest; }
namespace launcher { extern const AppManifest manifest; } namespace launcher { extern const AppManifest manifest; }
namespace log { extern const AppManifest manifest; } namespace log { extern const AppManifest manifest; }
namespace notes { extern const AppManifest manifest; }
namespace power { extern const AppManifest manifest; } namespace power { extern const AppManifest manifest; }
namespace selectiondialog { extern const AppManifest manifest; } namespace selectiondialog { extern const AppManifest manifest; }
namespace serialconsole { extern const AppManifest manifest; } namespace serialconsole { extern const AppManifest manifest; }
@@ -74,8 +77,10 @@ static void registerSystemApps() {
addApp(app::addgps::manifest); addApp(app::addgps::manifest);
addApp(app::alertdialog::manifest); addApp(app::alertdialog::manifest);
addApp(app::applist::manifest); addApp(app::applist::manifest);
addApp(app::calculator::manifest);
addApp(app::display::manifest); addApp(app::display::manifest);
addApp(app::files::manifest); addApp(app::filebrowser::manifest);
addApp(app::fileselection::manifest);
addApp(app::gpio::manifest); addApp(app::gpio::manifest);
addApp(app::gpssettings::manifest); addApp(app::gpssettings::manifest);
addApp(app::i2cscanner::manifest); addApp(app::i2cscanner::manifest);
@@ -84,6 +89,7 @@ static void registerSystemApps() {
addApp(app::inputdialog::manifest); addApp(app::inputdialog::manifest);
addApp(app::launcher::manifest); addApp(app::launcher::manifest);
addApp(app::log::manifest); addApp(app::log::manifest);
addApp(app::notes::manifest);
addApp(app::serialconsole::manifest); addApp(app::serialconsole::manifest);
addApp(app::settings::manifest); addApp(app::settings::manifest);
addApp(app::selectiondialog::manifest); addApp(app::selectiondialog::manifest);
+4 -4
View File
@@ -4,19 +4,19 @@
namespace tt::app { namespace tt::app {
void start(const std::string& id, std::shared_ptr<const Bundle> _Nullable parameters) { LaunchId start(const std::string& id, std::shared_ptr<const Bundle> _Nullable parameters) {
service::loader::startApp(id, std::move(parameters)); return service::loader::startApp(id, std::move(parameters));
} }
void stop() { void stop() {
service::loader::stopApp(); service::loader::stopApp();
} }
std::shared_ptr<app::AppContext> _Nullable getCurrentAppContext() { std::shared_ptr<AppContext> _Nullable getCurrentAppContext() {
return service::loader::getCurrentAppContext(); return service::loader::getCurrentAppContext();
} }
std::shared_ptr<app::App> _Nullable getCurrentApp() { std::shared_ptr<App> _Nullable getCurrentApp() {
return service::loader::getCurrentApp(); return service::loader::getCurrentApp();
} }
+2 -4
View File
@@ -36,8 +36,6 @@ static ElfManifest elfManifest;
class ElfApp : public App { class ElfApp : public App {
private:
const std::string filePath; const std::string filePath;
std::unique_ptr<uint8_t[]> elfFileData; std::unique_ptr<uint8_t[]> elfFileData;
esp_elf_t elf; esp_elf_t elf;
@@ -143,9 +141,9 @@ public:
} }
} }
void onResult(AppContext& appContext, Result result, std::unique_ptr<Bundle> resultBundle) override { void onResult(AppContext& appContext, LaunchId launchId, Result result, std::unique_ptr<Bundle> resultBundle) override {
if (manifest != nullptr && manifest->onResult != nullptr) { if (manifest != nullptr && manifest->onResult != nullptr) {
manifest->onResult(&appContext, data, result, resultBundle.get()); manifest->onResult(&appContext, data, launchId, result, resultBundle.get());
} }
} }
}; };
+6
View File
@@ -101,6 +101,8 @@ public:
lv_dropdown_set_options(uartDropdown, uart_options.c_str()); lv_dropdown_set_options(uartDropdown, uart_options.c_str());
lv_obj_align(uartDropdown, LV_ALIGN_TOP_RIGHT, 0, 0); lv_obj_align(uartDropdown, LV_ALIGN_TOP_RIGHT, 0, 0);
lv_obj_set_width(uartDropdown, LV_PCT(50)); lv_obj_set_width(uartDropdown, LV_PCT(50));
lv_obj_set_style_border_color(uartDropdown, lv_color_hex(0xFAFAFA), LV_PART_MAIN);
lv_obj_set_style_border_width(uartDropdown, 1, LV_PART_MAIN);
auto* uart_label = lv_label_create(uart_wrapper); auto* uart_label = lv_label_create(uart_wrapper);
lv_obj_align(uart_label, LV_ALIGN_TOP_LEFT, 0, 10); lv_obj_align(uart_label, LV_ALIGN_TOP_LEFT, 0, 10);
@@ -121,6 +123,8 @@ public:
lv_dropdown_set_options(modelDropdown, model_options.c_str()); lv_dropdown_set_options(modelDropdown, model_options.c_str());
lv_obj_align(modelDropdown, LV_ALIGN_TOP_RIGHT, 0, 0); lv_obj_align(modelDropdown, LV_ALIGN_TOP_RIGHT, 0, 0);
lv_obj_set_width(modelDropdown, LV_PCT(50)); lv_obj_set_width(modelDropdown, LV_PCT(50));
lv_obj_set_style_border_color(modelDropdown, lv_color_hex(0xFAFAFA), LV_PART_MAIN);
lv_obj_set_style_border_width(modelDropdown, 1, LV_PART_MAIN);
auto* model_label = lv_label_create(model_wrapper); auto* model_label = lv_label_create(model_wrapper);
lv_obj_align(model_label, LV_ALIGN_TOP_LEFT, 0, 10); lv_obj_align(model_label, LV_ALIGN_TOP_LEFT, 0, 10);
@@ -139,6 +143,8 @@ public:
lv_dropdown_set_options(baudDropdown, baudRatesDropdownValues); lv_dropdown_set_options(baudDropdown, baudRatesDropdownValues);
lv_obj_align(baudDropdown, LV_ALIGN_TOP_RIGHT, 0, 0); lv_obj_align(baudDropdown, LV_ALIGN_TOP_RIGHT, 0, 0);
lv_obj_set_width(baudDropdown, LV_PCT(50)); lv_obj_set_width(baudDropdown, LV_PCT(50));
lv_obj_set_style_border_color(baudDropdown, lv_color_hex(0xFAFAFA), LV_PART_MAIN);
lv_obj_set_style_border_width(baudDropdown, 1, LV_PART_MAIN);
auto* baud_rate_label = lv_label_create(baud_wrapper); auto* baud_rate_label = lv_label_create(baud_wrapper);
lv_obj_align(baud_rate_label, LV_ALIGN_TOP_LEFT, 0, 10); lv_obj_align(baud_rate_label, LV_ALIGN_TOP_LEFT, 0, 10);
+1 -9
View File
@@ -43,16 +43,8 @@ public:
auto manifests = getApps(); auto manifests = getApps();
std::sort(manifests.begin(), manifests.end(), SortAppManifestByName); std::sort(manifests.begin(), manifests.end(), SortAppManifestByName);
lv_list_add_text(list, "User");
for (const auto& manifest: manifests) { for (const auto& manifest: manifests) {
if (manifest->type == Type::User) { if (manifest->type == Type::User || manifest->type == Type::System) {
createAppWidget(manifest, list);
}
}
lv_list_add_text(list, "System");
for (const auto& manifest: manifests) {
if (manifest->type == Type::System) {
createAppWidget(manifest, list); createAppWidget(manifest, list);
} }
} }
+13 -5
View File
@@ -32,9 +32,9 @@ class BootApp : public App {
private: private:
Thread thread = Thread("boot", 4096, bootThreadCallback, this); Thread thread = Thread("boot", 4096, [this]() { return bootThreadCallback(); });
static int32_t bootThreadCallback(TT_UNUSED void* context) { int32_t bootThreadCallback() {
TickType_t start_time = kernel::getTicks(); TickType_t start_time = kernel::getTicks();
kernel::publishSystemEvent(kernel::SystemEvent::BootSplash); kernel::publishSystemEvent(kernel::SystemEvent::BootSplash);
@@ -42,14 +42,22 @@ private:
auto hal_display = getHalDisplay(); auto hal_display = getHalDisplay();
assert(hal_display != nullptr); assert(hal_display != nullptr);
if (hal_display->supportsBacklightDuty()) { if (hal_display->supportsBacklightDuty()) {
int32_t backlight_duty = app::display::getBacklightDuty(); uint8_t backlight_duty = 200;
TT_LOG_I(TAG, "backlight %ld", backlight_duty); app::display::getBacklightDuty(backlight_duty);
TT_LOG_I(TAG, "backlight %du", backlight_duty);
hal_display->setBacklightDuty(backlight_duty); hal_display->setBacklightDuty(backlight_duty);
} else { } else {
TT_LOG_I(TAG, "no backlight"); TT_LOG_I(TAG, "no backlight");
} }
if (hal_display->getGammaCurveCount() > 0) {
uint8_t gamma_curve;
if (app::display::getGammaCurve(gamma_curve)) {
hal_display->setGammaCurve(gamma_curve);
TT_LOG_I(TAG, "gamma %du", gamma_curve);
}
}
if (hal::usb::isUsbBootMode()) { if (hal::usb::isUsbBootMode()) {
TT_LOG_I(TAG, "Rebooting into mass storage device mode"); TT_LOG_I(TAG, "Rebooting into mass storage device mode");
hal::usb::resetUsbBootMode(); hal::usb::resetUsbBootMode();
@@ -0,0 +1,233 @@
#include <Tactility/app/AppManifest.h>
#include <Tactility/lvgl/Toolbar.h>
#include <Tactility/Assets.h>
#include <cstdlib>
#include <cstring>
#include <lvgl.h>
#include <queue>
#include <stack>
namespace tt::app::calculator {
constexpr const char* TAG = "Calculator";
class CalculatorApp : public App {
lv_obj_t* displayLabel;
lv_obj_t* resultLabel;
char formulaBuffer[128] = {0}; // Stores the full input expression
bool newInput = true;
static void button_event_cb(lv_event_t* e) {
CalculatorApp* self = static_cast<CalculatorApp*>(lv_event_get_user_data(e));
lv_obj_t* btnm = lv_event_get_current_target_obj(e);
lv_event_code_t code = lv_event_get_code(e);
uint32_t btn_id = lv_buttonmatrix_get_selected_button(btnm);
const char* txt = lv_buttonmatrix_get_button_text(btnm, btn_id);
if (code == LV_EVENT_VALUE_CHANGED) {
self->handleInput(txt);
}
}
void handleInput(const char* txt) {
if (std::strcmp(txt, "C") == 0) {
resetCalculator();
return;
}
if (std::strcmp(txt, "=") == 0) {
evaluateExpression();
return;
}
if (std::strlen(formulaBuffer) + std::strlen(txt) < sizeof(formulaBuffer) - 1) {
if (newInput) {
std::memset(formulaBuffer, 0, sizeof(formulaBuffer));
newInput = false;
}
std::strcat(formulaBuffer, txt);
lv_label_set_text(displayLabel, formulaBuffer);
}
}
void evaluateExpression() {
double result = computeFormula();
size_t formulaLen = std::strlen(formulaBuffer);
size_t maxAvailable = sizeof(formulaBuffer) - formulaLen - 1;
if (maxAvailable > 10) {
char resultBuffer[32];
snprintf(resultBuffer, sizeof(resultBuffer), " = %.8g", result);
strncat(formulaBuffer, resultBuffer, maxAvailable);
} else {
snprintf(formulaBuffer, sizeof(formulaBuffer), "%.8g", result);
}
lv_label_set_text(displayLabel, "0");
lv_label_set_text(resultLabel, formulaBuffer);
newInput = true;
}
double computeFormula() {
return evaluateRPN(infixToRPN(std::string(formulaBuffer)));
}
int precedence(char op) {
if (op == '+' || op == '-') return 1;
if (op == '*' || op == '/') return 2;
return 0;
}
std::queue<std::string> infixToRPN(const std::string& infix) {
std::stack<char> opStack;
std::queue<std::string> output;
std::string token;
size_t i = 0;
while (i < infix.size()) {
char ch = infix[i];
if (isdigit(ch)) {
token.clear();
while (i < infix.size() && (isdigit(infix[i]) || infix[i] == '.')) {
token += infix[i++];
}
output.push(token);
continue;
}
if (ch == '(') {
opStack.push(ch);
} else if (ch == ')') {
while (!opStack.empty() && opStack.top() != '(') {
output.push(std::string(1, opStack.top()));
opStack.pop();
}
opStack.pop();
} else if (strchr("+-*/", ch)) {
while (!opStack.empty() && precedence(opStack.top()) >= precedence(ch)) {
output.push(std::string(1, opStack.top()));
opStack.pop();
}
opStack.push(ch);
}
i++;
}
while (!opStack.empty()) {
output.push(std::string(1, opStack.top()));
opStack.pop();
}
return output;
}
double evaluateRPN(std::queue<std::string> rpnQueue) {
std::stack<double> values;
while (!rpnQueue.empty()) {
std::string token = rpnQueue.front();
rpnQueue.pop();
if (isdigit(token[0])) {
values.push(std::stod(token));
} else if (strchr("+-*/", token[0])) {
if (values.size() < 2) return 0;
double b = values.top();
values.pop();
double a = values.top();
values.pop();
if (token[0] == '+') values.push(a + b);
else if (token[0] == '-')
values.push(a - b);
else if (token[0] == '*')
values.push(a * b);
else if (token[0] == '/' && b != 0)
values.push(a / b);
}
}
return values.empty() ? 0 : values.top();
}
void resetCalculator() {
std::memset(formulaBuffer, 0, sizeof(formulaBuffer));
lv_label_set_text(displayLabel, "0");
lv_label_set_text(resultLabel, "");
newInput = true;
}
void onShow(AppContext& context, lv_obj_t* parent) override {
lv_obj_remove_flag(parent, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, context);
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t* wrapper = lv_obj_create(parent);
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(wrapper, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START);
lv_obj_set_width(wrapper, LV_PCT(100));
lv_obj_set_height(wrapper, LV_SIZE_CONTENT);
lv_obj_set_flex_grow(wrapper, 0);
lv_obj_set_style_pad_top(wrapper, 4, LV_PART_MAIN);
lv_obj_set_style_pad_bottom(wrapper, 4, LV_PART_MAIN);
lv_obj_set_style_pad_left(wrapper, 10, LV_PART_MAIN);
lv_obj_set_style_pad_right(wrapper, 10, LV_PART_MAIN);
lv_obj_set_style_pad_column(wrapper, 40, LV_PART_MAIN);
lv_obj_set_style_border_width(wrapper, 0, 0);
lv_obj_remove_flag(wrapper, LV_OBJ_FLAG_SCROLLABLE);
displayLabel = lv_label_create(wrapper);
lv_label_set_text(displayLabel, "0");
lv_obj_set_width(displayLabel, LV_SIZE_CONTENT);
lv_obj_set_align(displayLabel, LV_ALIGN_LEFT_MID);
resultLabel = lv_label_create(wrapper);
lv_label_set_text(resultLabel, "");
lv_obj_set_width(resultLabel, LV_SIZE_CONTENT);
lv_obj_set_align(resultLabel, LV_ALIGN_RIGHT_MID);
static const char* btn_map[] = {
"(", ")", "C", "/", "\n",
"7", "8", "9", "*", "\n",
"4", "5", "6", "-", "\n",
"1", "2", "3", "+", "\n",
"0", "=", "", "", ""
};
lv_obj_t* btnm = lv_btnmatrix_create(parent);
lv_btnmatrix_set_map(btnm, btn_map);
lv_obj_set_style_pad_all(btnm, 5, LV_PART_MAIN);
lv_obj_set_style_pad_row(btnm, 10, LV_PART_MAIN);
lv_obj_set_style_pad_column(btnm, 5, LV_PART_MAIN);
lv_obj_set_style_border_width(btnm, 0, LV_PART_MAIN);
lv_obj_set_style_bg_color(btnm, lv_palette_main(LV_PALETTE_BLUE), LV_PART_ITEMS);
lv_obj_set_style_border_width(btnm, 0, LV_PART_MAIN);
if (lv_display_get_horizontal_resolution(nullptr) <= 240 || lv_display_get_vertical_resolution(nullptr) <= 240) { //small screens
lv_obj_set_size(btnm, lv_pct(100), lv_pct(60));
} else { //large screens
lv_obj_set_size(btnm, lv_pct(100), lv_pct(80));
}
lv_obj_align(btnm, LV_ALIGN_BOTTOM_MID, 0, -5);
lv_obj_add_event_cb(btnm, button_event_cb, LV_EVENT_VALUE_CHANGED, this);
}
};
extern const AppManifest manifest = {
.id = "Calculator",
.name = "Calculator",
.icon = TT_ASSETS_APP_ICON_CALCULATOR,
.createApp = create<CalculatorApp>
};
} // namespace tt::app::calculator
+3 -1
View File
@@ -2,6 +2,7 @@
#include <Tactility/app/AppManifest.h> #include <Tactility/app/AppManifest.h>
#include <Tactility/lvgl/Toolbar.h> #include <Tactility/lvgl/Toolbar.h>
#include <Tactility/Assets.h>
#include <Tactility/service/espnow/EspNow.h> #include <Tactility/service/espnow/EspNow.h>
#include "Tactility/service/gui/Gui.h" #include "Tactility/service/gui/Gui.h"
@@ -115,7 +116,7 @@ public:
msg_list = lv_list_create(parent); msg_list = lv_list_create(parent);
lv_obj_set_size(msg_list, lv_pct(75), lv_pct(43)); lv_obj_set_size(msg_list, lv_pct(75), lv_pct(43));
lv_obj_align(msg_list, LV_ALIGN_TOP_LEFT, 5, toolbar_height + 45); lv_obj_align(msg_list, LV_ALIGN_TOP_LEFT, 5, toolbar_height + 45);
lv_obj_set_style_bg_color(msg_list, lv_color_hex(0xEEEEEE), 0); lv_obj_set_style_bg_color(msg_list, lv_color_hex(0x262626), 0);
lv_obj_set_style_border_width(msg_list, 1, 0); lv_obj_set_style_border_width(msg_list, 1, 0);
lv_obj_set_style_pad_all(msg_list, 5, 0); lv_obj_set_style_pad_all(msg_list, 5, 0);
@@ -171,6 +172,7 @@ public:
extern const AppManifest manifest = { extern const AppManifest manifest = {
.id = "Chat", .id = "Chat",
.name = "Chat", .name = "Chat",
.icon = TT_ASSETS_APP_ICON_CHAT,
.createApp = create<ChatApp> .createApp = create<ChatApp>
}; };
+78 -50
View File
@@ -22,10 +22,38 @@ static uint8_t gamma = 255;
#define ROTATION_270 2 #define ROTATION_270 2
#define ROTATION_90 3 #define ROTATION_90 3
static std::shared_ptr<tt::hal::display::DisplayDevice> getHalDisplay() { static std::shared_ptr<hal::display::DisplayDevice> getHalDisplay() {
return hal::findFirstDevice<hal::display::DisplayDevice>(hal::Device::Type::Display); return hal::findFirstDevice<hal::display::DisplayDevice>(hal::Device::Type::Display);
} }
static lv_display_rotation_t orientationSettingToDisplayRotation(uint32_t setting) {
switch (setting) {
case ROTATION_180:
return LV_DISPLAY_ROTATION_180;
case ROTATION_270:
return LV_DISPLAY_ROTATION_270;
case ROTATION_90:
return LV_DISPLAY_ROTATION_90;
default:
return LV_DISPLAY_ROTATION_0;
}
}
static uint32_t displayOrientationToOrientationSetting(lv_display_rotation_t orientation) {
switch (orientation) {
case LV_DISPLAY_ROTATION_90:
return ROTATION_90;
case LV_DISPLAY_ROTATION_180:
return ROTATION_180;
case LV_DISPLAY_ROTATION_270:
return ROTATION_270;
default:
return ROTATION_DEFAULT;
}
}
class DisplayApp : public App {
static void onBacklightSliderEvent(lv_event_t* event) { static void onBacklightSliderEvent(lv_event_t* event) {
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event)); auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event));
@@ -35,7 +63,7 @@ static void onBacklightSliderEvent(lv_event_t* event) {
if (hal_display->supportsBacklightDuty()) { if (hal_display->supportsBacklightDuty()) {
int32_t slider_value = lv_slider_get_value(slider); int32_t slider_value = lv_slider_get_value(slider);
backlight_duty = (uint8_t)slider_value; backlight_duty = static_cast<uint8_t>(slider_value);
backlight_duty_set = true; backlight_duty_set = true;
hal_display->setBacklightDuty(backlight_duty); hal_display->setBacklightDuty(backlight_duty);
@@ -46,41 +74,19 @@ static void onGammaSliderEvent(lv_event_t* event) {
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event)); auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event));
auto* lvgl_display = lv_display_get_default(); auto* lvgl_display = lv_display_get_default();
assert(lvgl_display != nullptr); assert(lvgl_display != nullptr);
auto* hal_display = (tt::hal::display::DisplayDevice*)lv_display_get_user_data(lvgl_display); auto* hal_display = static_cast<hal::display::DisplayDevice*>(lv_display_get_user_data(lvgl_display));
assert(hal_display != nullptr); assert(hal_display != nullptr);
if (hal_display->getGammaCurveCount() > 0) { if (hal_display->getGammaCurveCount() > 0) {
int32_t slider_value = lv_slider_get_value(slider); int32_t slider_value = lv_slider_get_value(slider);
gamma = (uint8_t)slider_value; gamma = static_cast<uint8_t>(slider_value);
hal_display->setGammaCurve(gamma); hal_display->setGammaCurve(gamma);
setGammaCurve(gamma);
} }
} }
static lv_display_rotation_t orientationSettingToDisplayRotation(uint32_t setting) {
if (setting == ROTATION_180) {
return LV_DISPLAY_ROTATION_180;
} else if (setting == ROTATION_270) {
return LV_DISPLAY_ROTATION_270;
} else if (setting == ROTATION_90) {
return LV_DISPLAY_ROTATION_90;
} else {
return LV_DISPLAY_ROTATION_0;
}
}
static uint32_t dipslayOrientationToOrientationSetting(lv_display_rotation_t orientation) {
if (orientation == LV_DISPLAY_ROTATION_90) {
return ROTATION_90;
} else if (orientation == LV_DISPLAY_ROTATION_180) {
return ROTATION_180;
} else if (orientation == LV_DISPLAY_ROTATION_270) {
return ROTATION_270;
} else {
return ROTATION_DEFAULT;
}
}
static void onOrientationSet(lv_event_t* event) { static void onOrientationSet(lv_event_t* event) {
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event)); auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
@@ -93,7 +99,7 @@ static void onOrientationSet(lv_event_t* event) {
} }
} }
class DisplayApp : public App { public:
void onShow(AppContext& app, lv_obj_t* parent) override { void onShow(AppContext& app, lv_obj_t* parent) override {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
@@ -108,59 +114,81 @@ class DisplayApp : public App {
lv_obj_set_width(main_wrapper, LV_PCT(100)); lv_obj_set_width(main_wrapper, LV_PCT(100));
lv_obj_set_flex_grow(main_wrapper, 1); lv_obj_set_flex_grow(main_wrapper, 1);
auto* wrapper = lv_obj_create(main_wrapper); if (hal_display->supportsBacklightDuty()) {
lv_obj_set_width(wrapper, LV_PCT(100)); auto* brightness_wrapper = lv_obj_create(main_wrapper);
lv_obj_set_style_pad_all(wrapper, 8, 0); lv_obj_set_size(brightness_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_border_width(wrapper, 0, 0); lv_obj_set_style_pad_hor(brightness_wrapper, 0, 0);
lv_obj_set_style_pad_ver(brightness_wrapper, 6, 0);
lv_obj_set_style_border_width(brightness_wrapper, 0, 0);
auto* brightness_label = lv_label_create(wrapper); auto* brightness_label = lv_label_create(brightness_wrapper);
lv_label_set_text(brightness_label, "Brightness"); lv_label_set_text(brightness_label, "Brightness");
auto* brightness_slider = lv_slider_create(wrapper); auto* brightness_slider = lv_slider_create(brightness_wrapper);
lv_obj_set_width(brightness_slider, LV_PCT(50)); lv_obj_set_width(brightness_slider, LV_PCT(50));
lv_obj_align(brightness_slider, LV_ALIGN_TOP_RIGHT, -8, 0); lv_obj_align(brightness_slider, LV_ALIGN_TOP_RIGHT, -8, 0);
lv_slider_set_range(brightness_slider, 0, 255); lv_slider_set_range(brightness_slider, 0, 255);
lv_obj_add_event_cb(brightness_slider, onBacklightSliderEvent, LV_EVENT_VALUE_CHANGED, nullptr); lv_obj_add_event_cb(brightness_slider, onBacklightSliderEvent, LV_EVENT_VALUE_CHANGED, nullptr);
auto* gamma_label = lv_label_create(wrapper); uint8_t value;
lv_label_set_text(gamma_label, "Gamma"); if (getBacklightDuty(value)) {
lv_obj_set_y(gamma_label, 40); lv_slider_set_value(brightness_slider, value, LV_ANIM_OFF);
} else {
lv_slider_set_value(brightness_slider, 0, LV_ANIM_OFF);
}
}
auto* gamma_slider = lv_slider_create(wrapper); if (hal_display->getGammaCurveCount() > 0) {
auto* gamma_wrapper = lv_obj_create(main_wrapper);
lv_obj_set_size(gamma_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_hor(gamma_wrapper, 0, 0);
lv_obj_set_style_pad_ver(gamma_wrapper, 6, 0);
lv_obj_set_style_border_width(gamma_wrapper, 0, 0);
auto* gamma_label = lv_label_create(gamma_wrapper);
lv_label_set_text(gamma_label, "Gamma");
lv_obj_set_y(gamma_label, 0);
auto* gamma_slider = lv_slider_create(gamma_wrapper);
lv_obj_set_width(gamma_slider, LV_PCT(50)); lv_obj_set_width(gamma_slider, LV_PCT(50));
lv_obj_align(gamma_slider, LV_ALIGN_TOP_RIGHT, -8, 40); lv_obj_align(gamma_slider, LV_ALIGN_TOP_RIGHT, -8, 0);
lv_slider_set_range(gamma_slider, 0, hal_display->getGammaCurveCount()); lv_slider_set_range(gamma_slider, 0, hal_display->getGammaCurveCount());
lv_obj_add_event_cb(gamma_slider, onGammaSliderEvent, LV_EVENT_VALUE_CHANGED, nullptr); lv_obj_add_event_cb(gamma_slider, onGammaSliderEvent, LV_EVENT_VALUE_CHANGED, nullptr);
if (!hal_display->supportsBacklightDuty()) { uint8_t curve_index;
lv_slider_set_value(brightness_slider, 255, LV_ANIM_OFF); if (getGammaCurve(curve_index)) {
lv_obj_add_state(brightness_slider, LV_STATE_DISABLED); lv_slider_set_value(gamma_slider, curve_index, LV_ANIM_OFF);
} else { } else {
uint8_t value = getBacklightDuty(); lv_slider_set_value(gamma_slider, 0, LV_ANIM_OFF);
lv_slider_set_value(brightness_slider, value, LV_ANIM_OFF); }
} }
lv_slider_set_value(gamma_slider, 128, LV_ANIM_OFF); auto* orientation_wrapper = lv_obj_create(main_wrapper);
lv_obj_set_size(orientation_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(orientation_wrapper, 0, 0);
lv_obj_set_style_border_width(orientation_wrapper, 0, 0);
auto* orientation_label = lv_label_create(wrapper); auto* orientation_label = lv_label_create(orientation_wrapper);
lv_label_set_text(orientation_label, "Orientation"); lv_label_set_text(orientation_label, "Orientation");
lv_obj_align(orientation_label, LV_ALIGN_TOP_LEFT, 0, 80); lv_obj_align(orientation_label, LV_ALIGN_TOP_LEFT, 0, 8);
auto lvgl_display = lv_obj_get_display(parent); auto lvgl_display = lv_obj_get_display(parent);
auto horizontal_px = lv_display_get_horizontal_resolution(lvgl_display); auto horizontal_px = lv_display_get_horizontal_resolution(lvgl_display);
auto vertical_px = lv_display_get_vertical_resolution(lvgl_display); auto vertical_px = lv_display_get_vertical_resolution(lvgl_display);
bool is_landscape_display = horizontal_px > vertical_px; bool is_landscape_display = horizontal_px > vertical_px;
auto* orientation_dropdown = lv_dropdown_create(wrapper); auto* orientation_dropdown = lv_dropdown_create(orientation_wrapper);
if (is_landscape_display) { if (is_landscape_display) {
lv_dropdown_set_options(orientation_dropdown, "Landscape\nLandscape (flipped)\nPortrait Left\nPortrait Right"); lv_dropdown_set_options(orientation_dropdown, "Landscape\nLandscape (flipped)\nPortrait Left\nPortrait Right");
} else { } else {
lv_dropdown_set_options(orientation_dropdown, "Portrait\nPortrait (flipped)\nLandscape Left\nLandscape Right"); lv_dropdown_set_options(orientation_dropdown, "Portrait\nPortrait (flipped)\nLandscape Left\nLandscape Right");
} }
lv_obj_align(orientation_dropdown, LV_ALIGN_TOP_RIGHT, 0, 72); lv_obj_align(orientation_dropdown, LV_ALIGN_TOP_RIGHT, 0, 0);
lv_obj_set_style_border_color(orientation_dropdown, lv_color_hex(0xFAFAFA), LV_PART_MAIN);
lv_obj_set_style_border_width(orientation_dropdown, 1, LV_PART_MAIN);
lv_obj_add_event_cb(orientation_dropdown, onOrientationSet, LV_EVENT_VALUE_CHANGED, nullptr); lv_obj_add_event_cb(orientation_dropdown, onOrientationSet, LV_EVENT_VALUE_CHANGED, nullptr);
uint32_t orientation_selected = dipslayOrientationToOrientationSetting( uint32_t orientation_selected = displayOrientationToOrientationSetting(
lv_display_get_rotation(lv_display_get_default()) lv_display_get_rotation(lv_display_get_default())
); );
lv_dropdown_set_selected(orientation_dropdown, orientation_selected); lv_dropdown_set_selected(orientation_dropdown, orientation_selected);
@@ -6,19 +6,21 @@ namespace tt::app::display {
tt::Preferences preferences("display"); tt::Preferences preferences("display");
#define BACKLIGHT_DUTY_KEY "backlight_duty" constexpr const char* BACKLIGHT_DUTY_KEY = "backlight_duty";
#define ROTATION_KEY "rotation" constexpr const char* GAMMA_CURVE_KEY = "gamma";
constexpr const char* ROTATION_KEY = "rotation";
void setBacklightDuty(uint8_t value) { void setBacklightDuty(uint8_t value) {
preferences.putInt32(BACKLIGHT_DUTY_KEY, (int32_t)value); preferences.putInt32(BACKLIGHT_DUTY_KEY, (int32_t)value);
} }
uint8_t getBacklightDuty() { bool getBacklightDuty(uint8_t& duty) {
int32_t result; int32_t result;
if (preferences.optInt32(BACKLIGHT_DUTY_KEY, result)) { if (preferences.optInt32(BACKLIGHT_DUTY_KEY, result)) {
return (uint8_t)(result % 256); duty = (uint8_t)(result % 256);
return true;
} else { } else {
return 200; return false;
} }
} }
@@ -35,4 +37,18 @@ lv_display_rotation_t getRotation() {
} }
} }
void setGammaCurve(uint8_t curveIndex) {
preferences.putInt32(GAMMA_CURVE_KEY, (int32_t)curveIndex);
}
bool getGammaCurve(uint8_t& curveIndex) {
int32_t result;
if (preferences.optInt32(GAMMA_CURVE_KEY, result)) {
curveIndex = (uint8_t)(result % 256);
return true;
} else {
return false;
}
}
} // namespace } // namespace
@@ -1,5 +1,5 @@
#include "Tactility/app/files/View.h" #include "Tactility/app/filebrowser/View.h"
#include "Tactility/app/files/State.h" #include "Tactility/app/filebrowser/State.h"
#include "Tactility/app/AppContext.h" #include "Tactility/app/AppContext.h"
#include <Tactility/Assets.h> #include <Tactility/Assets.h>
@@ -7,18 +7,18 @@
#include <memory> #include <memory>
namespace tt::app::files { namespace tt::app::filebrowser {
#define TAG "files_app" #define TAG "filebrowser_app"
extern const AppManifest manifest; extern const AppManifest manifest;
class FilesApp : public App { class FileBrowser : public App {
std::unique_ptr<View> view; std::unique_ptr<View> view;
std::shared_ptr<State> state; std::shared_ptr<State> state;
public: public:
FilesApp() { FileBrowser() {
state = std::make_shared<State>(); state = std::make_shared<State>();
view = std::make_unique<View>(state); view = std::make_unique<View>(state);
} }
@@ -27,7 +27,7 @@ public:
view->init(parent); view->init(parent);
} }
void onResult(AppContext& appContext, Result result, std::unique_ptr<Bundle> bundle) override { void onResult(AppContext& appContext, TT_UNUSED LaunchId launchId, Result result, std::unique_ptr<Bundle> bundle) override {
view->onResult(result, std::move(bundle)); view->onResult(result, std::move(bundle));
} }
}; };
@@ -37,7 +37,7 @@ extern const AppManifest manifest = {
.name = "Files", .name = "Files",
.icon = TT_ASSETS_APP_ICON_FILES, .icon = TT_ASSETS_APP_ICON_FILES,
.type = Type::Hidden, .type = Type::Hidden,
.createApp = create<FilesApp> .createApp = create<FileBrowser>
}; };
void start() { void start() {
@@ -1,6 +1,6 @@
#include "Tactility/app/files/State.h" #include "Tactility/app/filebrowser/State.h"
#include "Tactility/app/files/FileUtils.h"
#include <Tactility/file/File.h>
#include "Tactility/hal/sdcard/SdCardDevice.h" #include "Tactility/hal/sdcard/SdCardDevice.h"
#include <Tactility/Log.h> #include <Tactility/Log.h>
#include <Tactility/Partitions.h> #include <Tactility/Partitions.h>
@@ -11,9 +11,9 @@
#include <vector> #include <vector>
#include <dirent.h> #include <dirent.h>
#define TAG "files_app" #define TAG "filebrowser_app"
namespace tt::app::files { namespace tt::app::filebrowser {
State::State() { State::State() {
if (kernel::getPlatform() == kernel::PlatformSimulator) { if (kernel::getPlatform() == kernel::PlatformSimulator) {
@@ -30,7 +30,7 @@ State::State() {
} }
std::string State::getSelectedChildPath() const { std::string State::getSelectedChildPath() const {
return getChildPath(current_path, selected_child_entry); return file::getChildPath(current_path, selected_child_entry);
} }
bool State::setEntriesForPath(const std::string& path) { bool State::setEntriesForPath(const std::string& path) {
@@ -52,12 +52,12 @@ bool State::setEntriesForPath(const std::string& path) {
dir_entries.clear(); dir_entries.clear();
dir_entries.push_back(dirent{ dir_entries.push_back(dirent{
.d_ino = 0, .d_ino = 0,
.d_type = TT_DT_DIR, .d_type = file::TT_DT_DIR,
.d_name = SYSTEM_PARTITION_NAME .d_name = SYSTEM_PARTITION_NAME
}); });
dir_entries.push_back(dirent{ dir_entries.push_back(dirent{
.d_ino = 1, .d_ino = 1,
.d_type = TT_DT_DIR, .d_type = file::TT_DT_DIR,
.d_name = DATA_PARTITION_NAME .d_name = DATA_PARTITION_NAME
}); });
@@ -68,7 +68,7 @@ bool State::setEntriesForPath(const std::string& path) {
auto mount_name = sdcard->getMountPath().substr(1); auto mount_name = sdcard->getMountPath().substr(1);
auto dir_entry = dirent { auto dir_entry = dirent {
.d_ino = 2, .d_ino = 2,
.d_type = TT_DT_DIR, .d_type = file::TT_DT_DIR,
.d_name = { 0 } .d_name = { 0 }
}; };
assert(mount_name.length() < sizeof(dirent::d_name)); assert(mount_name.length() < sizeof(dirent::d_name));
@@ -83,7 +83,7 @@ bool State::setEntriesForPath(const std::string& path) {
return true; return true;
} else { } else {
dir_entries.clear(); dir_entries.clear();
int count = tt::app::files::scandir(path, dir_entries, &dirent_filter_dot_entries, dirent_sort_alpha_and_type); int count = file::scandir(path, dir_entries, &file::direntFilterDotEntries, file::direntSortAlphaAndType);
if (count >= 0) { if (count >= 0) {
TT_LOG_I(TAG, "%s has %u entries", path.c_str(), count); TT_LOG_I(TAG, "%s has %u entries", path.c_str(), count);
current_path = path; current_path = path;
@@ -97,8 +97,8 @@ bool State::setEntriesForPath(const std::string& path) {
} }
} }
bool State::setEntriesForChildPath(const std::string& child_path) { bool State::setEntriesForChildPath(const std::string& childPath) {
auto path = getChildPath(current_path, child_path); auto path = file::getChildPath(current_path, childPath);
TT_LOG_I(TAG, "Navigating from %s to %s", current_path.c_str(), path.c_str()); TT_LOG_I(TAG, "Navigating from %s to %s", current_path.c_str(), path.c_str());
return setEntriesForPath(path); return setEntriesForPath(path);
} }

Some files were not shown because too many files have changed in this diff Show More