mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-02-18 10:53:17 +00:00
Time & date, system events and much more (#152)
## Time & Date - Added time to statusbar widget - Added Time & Date Settings app - Added TimeZone app for selecting TimeZone - Added `tt::time` namespace with timezone code ## Other changes - Added `SystemEvent` to publish/subscribe to system wide (e.g. for init code, but also for time settings changes) - Changed the way the statusbar widget works: now there's only 1 that gets shown/hidden, instead of 1 instance per app instance. - Moved `lowercase()` function to new namespace: `tt::string` - Increased T-Deck flash & PSRAM SPI frequencies to 120 MHz (from 80 MHz) - Temporary work-around (+ TODO item) for LVGL stack size (issue with WiFi app) - Suppress T-Deck keystroke debugging to debug level (privacy issue) - Improved SDL dependency wiring in various `CMakeLists.txt` - `Loader` service had some variables renamed to the newer C++ style (from previous C style)
This commit is contained in:
parent
4f360741a1
commit
bf91e7530d
@ -24,6 +24,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
|
||||
)
|
||||
else()
|
||||
|
||||
|
||||
file(GLOB_RECURSE SOURCES "Source/*.c*")
|
||||
add_executable(AppSim ${SOURCES})
|
||||
target_link_libraries(AppSim
|
||||
@ -33,9 +34,11 @@ else()
|
||||
PRIVATE Simulator
|
||||
)
|
||||
|
||||
find_package(SDL2 REQUIRED CONFIG)
|
||||
target_link_libraries(AppSim PRIVATE ${SDL2_LIBRARIES})
|
||||
include_directories(${SDL2_INCLUDE_DIRS})
|
||||
if (NOT DEFINED ENV{SKIP_SDL})
|
||||
find_package(SDL2 REQUIRED CONFIG)
|
||||
include_directories(${SDL2_INCLUDE_DIRS})
|
||||
target_link_libraries(AppSim PRIVATE ${SDL2_LIBRARIES})
|
||||
endif()
|
||||
|
||||
add_definitions(-D_Nullable=)
|
||||
add_definitions(-D_Nonnull=)
|
||||
|
||||
@ -8,8 +8,8 @@
|
||||
|
||||
// LVGL
|
||||
// The minimum task stack seems to be about 3500, but that crashes the wifi app in some scenarios
|
||||
// At 4000, it crashes when the fps renderer is available
|
||||
#define TDECK_LVGL_TASK_STACK_DEPTH 8192
|
||||
// At 8192, it sometimes crashes when wifi-auto enables and is busy connecting and then you open WifiManage
|
||||
#define TDECK_LVGL_TASK_STACK_DEPTH 9216
|
||||
|
||||
bool tdeck_init_lvgl() {
|
||||
static lv_disp_t* display = nullptr;
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
#define TDECK_LCD_PIN_CS GPIO_NUM_12
|
||||
#define TDECK_LCD_PIN_DC GPIO_NUM_11 // RS
|
||||
#define TDECK_LCD_PIN_BACKLIGHT GPIO_NUM_42
|
||||
#define TDECK_LCD_SPI_FREQUENCY 40000000
|
||||
#define TDECK_LCD_SPI_FREQUENCY 80000000
|
||||
#define TDECK_LCD_HORIZONTAL_RESOLUTION 320
|
||||
#define TDECK_LCD_VERTICAL_RESOLUTION 240
|
||||
#define TDECK_LCD_BITS_PER_PIXEL 16
|
||||
|
||||
@ -30,11 +30,11 @@ static void keyboard_read_callback(TT_UNUSED lv_indev_t* indev, lv_indev_data_t*
|
||||
|
||||
if (keyboard_i2c_read(&read_buffer)) {
|
||||
if (read_buffer == 0 && read_buffer != last_buffer) {
|
||||
TT_LOG_I(TAG, "Released %d", last_buffer);
|
||||
TT_LOG_D(TAG, "Released %d", last_buffer);
|
||||
data->key = last_buffer;
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
} else if (read_buffer != 0) {
|
||||
TT_LOG_I(TAG, "Pressed %d", read_buffer);
|
||||
TT_LOG_D(TAG, "Pressed %d", read_buffer);
|
||||
data->key = read_buffer;
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
// LVGL
|
||||
// The minimum task stack seems to be about 3500, but that crashes the wifi app in some scenarios
|
||||
// At 4000, it crashes when the fps renderer is available
|
||||
#define CORE2_LVGL_TASK_STACK_DEPTH 8192
|
||||
#define CORE2_LVGL_TASK_STACK_DEPTH 9216
|
||||
|
||||
bool initLvgl() {
|
||||
const lvgl_port_cfg_t lvgl_cfg = {
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
// LVGL
|
||||
// The minimum task stack seems to be about 3500, but that crashes the wifi app in some scenarios
|
||||
// At 4000, it crashes when the fps renderer is available
|
||||
#define CORE2_LVGL_TASK_STACK_DEPTH 8192
|
||||
#define CORE2_LVGL_TASK_STACK_DEPTH 9216
|
||||
|
||||
bool initLvgl() {
|
||||
const lvgl_port_cfg_t lvgl_cfg = {
|
||||
|
||||
@ -11,13 +11,20 @@ if (NOT DEFINED ENV{ESP_IDF_VERSION})
|
||||
PRIVATE ${SOURCES}
|
||||
PUBLIC ${HEADERS}
|
||||
)
|
||||
|
||||
target_link_libraries(Simulator
|
||||
PRIVATE Tactility
|
||||
PRIVATE TactilityCore
|
||||
PRIVATE TactilityHeadless
|
||||
PRIVATE lvgl
|
||||
PRIVATE ${SDL2_LIBRARIES}
|
||||
)
|
||||
|
||||
if (NOT DEFINED ENV{SKIP_SDL})
|
||||
find_package(SDL2 REQUIRED CONFIG)
|
||||
target_link_libraries(Simulator PRIVATE ${SDL2_LIBRARIES})
|
||||
endif()
|
||||
|
||||
add_definitions(-D_Nullable=)
|
||||
add_definitions(-D_Nonnull=)
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
#include "lvgl.h"
|
||||
#include "Log.h"
|
||||
#include "TactilityCore.h"
|
||||
#include "Thread.h"
|
||||
#include "lvgl/LvglSync.h"
|
||||
|
||||
|
||||
@ -64,7 +64,6 @@ project(Tactility)
|
||||
# Defined as regular project for PC and component for ESP
|
||||
if (NOT DEFINED ENV{ESP_IDF_VERSION})
|
||||
add_subdirectory(Tactility)
|
||||
add_subdirectory(TactilityC)
|
||||
add_subdirectory(TactilityCore)
|
||||
add_subdirectory(TactilityHeadless)
|
||||
add_subdirectory(Boards/Simulator)
|
||||
@ -96,65 +95,14 @@ if (NOT DEFINED ENV{ESP_IDF_VERSION})
|
||||
add_subdirectory(Tests)
|
||||
|
||||
# LVGL
|
||||
|
||||
add_subdirectory(Libraries/lvgl) # Added as idf component for ESP and as library for other targets
|
||||
include_directories(lvgl PUBLIC ${PROJECT_SOURCE_DIR}/Libraries/lvgl_conf)
|
||||
if (NOT DEFINED ENV{SKIP_SDL})
|
||||
target_include_directories(lvgl PUBLIC ${SDL2_IMAGE_INCLUDE_DIRS})
|
||||
find_package(SDL2 REQUIRED CONFIG)
|
||||
target_include_directories(lvgl PUBLIC ${SDL2_INCLUDE_DIRS})
|
||||
target_link_libraries(lvgl
|
||||
PRIVATE ${SDL2_LIBRARIES}
|
||||
)
|
||||
endif()
|
||||
target_compile_definitions(lvgl PUBLIC "-DLV_CONF_PATH=\"${PROJECT_SOURCE_DIR}/Libraries/lvgl_conf/lv_conf_simulator.h\"")
|
||||
|
||||
# SDL
|
||||
# TODO: This is a temporary skipping option for running unit tests
|
||||
# TODO: Remove when github action for SDL is working again
|
||||
if (NOT DEFINED ENV{SKIP_SDL})
|
||||
find_package(SDL2 REQUIRED CONFIG)
|
||||
|
||||
option(LV_USE_DRAW_SDL "Use SDL draw unit" OFF)
|
||||
option(LV_USE_LIBPNG "Use libpng to decode PNG" OFF)
|
||||
option(LV_USE_LIBJPEG_TURBO "Use libjpeg turbo to decode JPEG" OFF)
|
||||
option(LV_USE_FFMPEG "Use libffmpeg to display video using lv_ffmpeg" OFF)
|
||||
option(LV_USE_FREETYPE "Use freetype lib" OFF)
|
||||
|
||||
add_compile_definitions($<$<BOOL:${LV_USE_DRAW_SDL}>:LV_USE_DRAW_SDL=1>)
|
||||
add_compile_definitions($<$<BOOL:${LV_USE_LIBPNG}>:LV_USE_LIBPNG=1>)
|
||||
add_compile_definitions($<$<BOOL:${LV_USE_LIBJPEG_TURBO}>:LV_USE_LIBJPEG_TURBO=1>)
|
||||
add_compile_definitions($<$<BOOL:${LV_USE_FFMPEG}>:LV_USE_FFMPEG=1>)
|
||||
|
||||
if (LV_USE_DRAW_SDL)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
||||
# Need to install libsdl2-image-dev
|
||||
# `sudo apt install libsdl2-image-dev`
|
||||
# `brew install sdl2_image`
|
||||
find_package(SDL2_image REQUIRED)
|
||||
include_directories(lvgl PUBLIC ${PROJECT_SOURCE_DIR}/Libraries/lvgl_conf)
|
||||
target_link_libraries(AppSim ${SDL2_IMAGE_LIBRARIES})
|
||||
target_link_libraries(Simulator ${SDL2_IMAGE_LIBRARIES})
|
||||
endif(LV_USE_DRAW_SDL)
|
||||
|
||||
if (LV_USE_LIBPNG)
|
||||
find_package(PNG REQUIRED)
|
||||
target_include_directories(lvgl PUBLIC ${PNG_INCLUDE_DIR})
|
||||
target_link_libraries(AppSim ${PNG_LIBRARY})
|
||||
endif(LV_USE_LIBPNG)
|
||||
|
||||
if (LV_USE_LIBJPEG_TURBO)
|
||||
# Need to install libjpeg-turbo8-dev
|
||||
# `sudo apt install libjpeg-turbo8-dev`
|
||||
# `brew install libjpeg-turbo`
|
||||
find_package(JPEG REQUIRED)
|
||||
target_include_directories(lvgl PUBLIC ${JPEG_INCLUDE_DIRS})
|
||||
target_link_libraries(AppSim ${JPEG_LIBRARIES})
|
||||
endif(LV_USE_LIBJPEG_TURBO)
|
||||
|
||||
if (LV_USE_FFMPEG)
|
||||
target_link_libraries(main avformat avcodec avutil swscale)
|
||||
endif(LV_USE_FFMPEG)
|
||||
|
||||
if (LV_USE_FREETYPE)
|
||||
find_package(Freetype REQUIRED)
|
||||
target_link_libraries(AppSim ${FREETYPE_LIBRARIES})
|
||||
target_include_directories(lvgl PUBLIC ${FREETYPE_INCLUDE_DIRS})
|
||||
endif(LV_USE_FREETYPE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@ -33,6 +33,12 @@ Website: https://fonts.google.com/icons
|
||||
|
||||
License: Multiple (SIL Open Font License, Apache License, Ubuntu Font License): https://developers.google.com/fonts/faq
|
||||
|
||||
### Timezones CSV
|
||||
|
||||
Website: https://github.com/nayarsystems/posix_tz_db
|
||||
|
||||
License: [MIT](https://github.com/nayarsystems/posix_tz_db/blob/master/LICENSE)
|
||||
|
||||
### Other Components
|
||||
|
||||
See `/components` for the respective projects and their licenses.
|
||||
|
||||
BIN
Data/screenshot-AppList.png
Normal file
BIN
Data/screenshot-AppList.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
BIN
Data/screenshot-Launcher.png
Normal file
BIN
Data/screenshot-Launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
BIN
Data/system/app/TimeZone/search.png
Normal file
BIN
Data/system/app/TimeZone/search.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 753 B |
BIN
Data/system/app_icon_time_date_settings.png
Normal file
BIN
Data/system/app_icon_time_date_settings.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 392 B |
461
Data/system/timezones.csv
Normal file
461
Data/system/timezones.csv
Normal file
@ -0,0 +1,461 @@
|
||||
"Africa/Abidjan","GMT0"
|
||||
"Africa/Accra","GMT0"
|
||||
"Africa/Addis_Ababa","EAT-3"
|
||||
"Africa/Algiers","CET-1"
|
||||
"Africa/Asmara","EAT-3"
|
||||
"Africa/Bamako","GMT0"
|
||||
"Africa/Bangui","WAT-1"
|
||||
"Africa/Banjul","GMT0"
|
||||
"Africa/Bissau","GMT0"
|
||||
"Africa/Blantyre","CAT-2"
|
||||
"Africa/Brazzaville","WAT-1"
|
||||
"Africa/Bujumbura","CAT-2"
|
||||
"Africa/Cairo","EET-2EEST,M4.5.5/0,M10.5.4/24"
|
||||
"Africa/Casablanca","<+01>-1"
|
||||
"Africa/Ceuta","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Africa/Conakry","GMT0"
|
||||
"Africa/Dakar","GMT0"
|
||||
"Africa/Dar_es_Salaam","EAT-3"
|
||||
"Africa/Djibouti","EAT-3"
|
||||
"Africa/Douala","WAT-1"
|
||||
"Africa/El_Aaiun","<+01>-1"
|
||||
"Africa/Freetown","GMT0"
|
||||
"Africa/Gaborone","CAT-2"
|
||||
"Africa/Harare","CAT-2"
|
||||
"Africa/Johannesburg","SAST-2"
|
||||
"Africa/Juba","CAT-2"
|
||||
"Africa/Kampala","EAT-3"
|
||||
"Africa/Khartoum","CAT-2"
|
||||
"Africa/Kigali","CAT-2"
|
||||
"Africa/Kinshasa","WAT-1"
|
||||
"Africa/Lagos","WAT-1"
|
||||
"Africa/Libreville","WAT-1"
|
||||
"Africa/Lome","GMT0"
|
||||
"Africa/Luanda","WAT-1"
|
||||
"Africa/Lubumbashi","CAT-2"
|
||||
"Africa/Lusaka","CAT-2"
|
||||
"Africa/Malabo","WAT-1"
|
||||
"Africa/Maputo","CAT-2"
|
||||
"Africa/Maseru","SAST-2"
|
||||
"Africa/Mbabane","SAST-2"
|
||||
"Africa/Mogadishu","EAT-3"
|
||||
"Africa/Monrovia","GMT0"
|
||||
"Africa/Nairobi","EAT-3"
|
||||
"Africa/Ndjamena","WAT-1"
|
||||
"Africa/Niamey","WAT-1"
|
||||
"Africa/Nouakchott","GMT0"
|
||||
"Africa/Ouagadougou","GMT0"
|
||||
"Africa/Porto-Novo","WAT-1"
|
||||
"Africa/Sao_Tome","GMT0"
|
||||
"Africa/Tripoli","EET-2"
|
||||
"Africa/Tunis","CET-1"
|
||||
"Africa/Windhoek","CAT-2"
|
||||
"America/Adak","HST10HDT,M3.2.0,M11.1.0"
|
||||
"America/Anchorage","AKST9AKDT,M3.2.0,M11.1.0"
|
||||
"America/Anguilla","AST4"
|
||||
"America/Antigua","AST4"
|
||||
"America/Araguaina","<-03>3"
|
||||
"America/Argentina/Buenos_Aires","<-03>3"
|
||||
"America/Argentina/Catamarca","<-03>3"
|
||||
"America/Argentina/Cordoba","<-03>3"
|
||||
"America/Argentina/Jujuy","<-03>3"
|
||||
"America/Argentina/La_Rioja","<-03>3"
|
||||
"America/Argentina/Mendoza","<-03>3"
|
||||
"America/Argentina/Rio_Gallegos","<-03>3"
|
||||
"America/Argentina/Salta","<-03>3"
|
||||
"America/Argentina/San_Juan","<-03>3"
|
||||
"America/Argentina/San_Luis","<-03>3"
|
||||
"America/Argentina/Tucuman","<-03>3"
|
||||
"America/Argentina/Ushuaia","<-03>3"
|
||||
"America/Aruba","AST4"
|
||||
"America/Asuncion","<-04>4<-03>,M10.1.0/0,M3.4.0/0"
|
||||
"America/Atikokan","EST5"
|
||||
"America/Bahia","<-03>3"
|
||||
"America/Bahia_Banderas","CST6"
|
||||
"America/Barbados","AST4"
|
||||
"America/Belem","<-03>3"
|
||||
"America/Belize","CST6"
|
||||
"America/Blanc-Sablon","AST4"
|
||||
"America/Boa_Vista","<-04>4"
|
||||
"America/Bogota","<-05>5"
|
||||
"America/Boise","MST7MDT,M3.2.0,M11.1.0"
|
||||
"America/Cambridge_Bay","MST7MDT,M3.2.0,M11.1.0"
|
||||
"America/Campo_Grande","<-04>4"
|
||||
"America/Cancun","EST5"
|
||||
"America/Caracas","<-04>4"
|
||||
"America/Cayenne","<-03>3"
|
||||
"America/Cayman","EST5"
|
||||
"America/Chicago","CST6CDT,M3.2.0,M11.1.0"
|
||||
"America/Chihuahua","CST6"
|
||||
"America/Costa_Rica","CST6"
|
||||
"America/Creston","MST7"
|
||||
"America/Cuiaba","<-04>4"
|
||||
"America/Curacao","AST4"
|
||||
"America/Danmarkshavn","GMT0"
|
||||
"America/Dawson","MST7"
|
||||
"America/Dawson_Creek","MST7"
|
||||
"America/Denver","MST7MDT,M3.2.0,M11.1.0"
|
||||
"America/Detroit","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Dominica","AST4"
|
||||
"America/Edmonton","MST7MDT,M3.2.0,M11.1.0"
|
||||
"America/Eirunepe","<-05>5"
|
||||
"America/El_Salvador","CST6"
|
||||
"America/Fortaleza","<-03>3"
|
||||
"America/Fort_Nelson","MST7"
|
||||
"America/Glace_Bay","AST4ADT,M3.2.0,M11.1.0"
|
||||
"America/Godthab","<-02>2<-01>,M3.5.0/-1,M10.5.0/0"
|
||||
"America/Goose_Bay","AST4ADT,M3.2.0,M11.1.0"
|
||||
"America/Grand_Turk","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Grenada","AST4"
|
||||
"America/Guadeloupe","AST4"
|
||||
"America/Guatemala","CST6"
|
||||
"America/Guayaquil","<-05>5"
|
||||
"America/Guyana","<-04>4"
|
||||
"America/Halifax","AST4ADT,M3.2.0,M11.1.0"
|
||||
"America/Havana","CST5CDT,M3.2.0/0,M11.1.0/1"
|
||||
"America/Hermosillo","MST7"
|
||||
"America/Indiana/Indianapolis","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Indiana/Knox","CST6CDT,M3.2.0,M11.1.0"
|
||||
"America/Indiana/Marengo","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Indiana/Petersburg","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Indiana/Tell_City","CST6CDT,M3.2.0,M11.1.0"
|
||||
"America/Indiana/Vevay","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Indiana/Vincennes","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Indiana/Winamac","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Inuvik","MST7MDT,M3.2.0,M11.1.0"
|
||||
"America/Iqaluit","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Jamaica","EST5"
|
||||
"America/Juneau","AKST9AKDT,M3.2.0,M11.1.0"
|
||||
"America/Kentucky/Louisville","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Kentucky/Monticello","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Kralendijk","AST4"
|
||||
"America/La_Paz","<-04>4"
|
||||
"America/Lima","<-05>5"
|
||||
"America/Los_Angeles","PST8PDT,M3.2.0,M11.1.0"
|
||||
"America/Lower_Princes","AST4"
|
||||
"America/Maceio","<-03>3"
|
||||
"America/Managua","CST6"
|
||||
"America/Manaus","<-04>4"
|
||||
"America/Marigot","AST4"
|
||||
"America/Martinique","AST4"
|
||||
"America/Matamoros","CST6CDT,M3.2.0,M11.1.0"
|
||||
"America/Mazatlan","MST7"
|
||||
"America/Menominee","CST6CDT,M3.2.0,M11.1.0"
|
||||
"America/Merida","CST6"
|
||||
"America/Metlakatla","AKST9AKDT,M3.2.0,M11.1.0"
|
||||
"America/Mexico_City","CST6"
|
||||
"America/Miquelon","<-03>3<-02>,M3.2.0,M11.1.0"
|
||||
"America/Moncton","AST4ADT,M3.2.0,M11.1.0"
|
||||
"America/Monterrey","CST6"
|
||||
"America/Montevideo","<-03>3"
|
||||
"America/Montreal","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Montserrat","AST4"
|
||||
"America/Nassau","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/New_York","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Nipigon","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Nome","AKST9AKDT,M3.2.0,M11.1.0"
|
||||
"America/Noronha","<-02>2"
|
||||
"America/North_Dakota/Beulah","CST6CDT,M3.2.0,M11.1.0"
|
||||
"America/North_Dakota/Center","CST6CDT,M3.2.0,M11.1.0"
|
||||
"America/North_Dakota/New_Salem","CST6CDT,M3.2.0,M11.1.0"
|
||||
"America/Nuuk","<-02>2<-01>,M3.5.0/-1,M10.5.0/0"
|
||||
"America/Ojinaga","CST6CDT,M3.2.0,M11.1.0"
|
||||
"America/Panama","EST5"
|
||||
"America/Pangnirtung","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Paramaribo","<-03>3"
|
||||
"America/Phoenix","MST7"
|
||||
"America/Port-au-Prince","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Port_of_Spain","AST4"
|
||||
"America/Porto_Velho","<-04>4"
|
||||
"America/Puerto_Rico","AST4"
|
||||
"America/Punta_Arenas","<-03>3"
|
||||
"America/Rainy_River","CST6CDT,M3.2.0,M11.1.0"
|
||||
"America/Rankin_Inlet","CST6CDT,M3.2.0,M11.1.0"
|
||||
"America/Recife","<-03>3"
|
||||
"America/Regina","CST6"
|
||||
"America/Resolute","CST6CDT,M3.2.0,M11.1.0"
|
||||
"America/Rio_Branco","<-05>5"
|
||||
"America/Santarem","<-03>3"
|
||||
"America/Santiago","<-04>4<-03>,M9.1.6/24,M4.1.6/24"
|
||||
"America/Santo_Domingo","AST4"
|
||||
"America/Sao_Paulo","<-03>3"
|
||||
"America/Scoresbysund","<-02>2<-01>,M3.5.0/-1,M10.5.0/0"
|
||||
"America/Sitka","AKST9AKDT,M3.2.0,M11.1.0"
|
||||
"America/St_Barthelemy","AST4"
|
||||
"America/St_Johns","NST3:30NDT,M3.2.0,M11.1.0"
|
||||
"America/St_Kitts","AST4"
|
||||
"America/St_Lucia","AST4"
|
||||
"America/St_Thomas","AST4"
|
||||
"America/St_Vincent","AST4"
|
||||
"America/Swift_Current","CST6"
|
||||
"America/Tegucigalpa","CST6"
|
||||
"America/Thule","AST4ADT,M3.2.0,M11.1.0"
|
||||
"America/Thunder_Bay","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Tijuana","PST8PDT,M3.2.0,M11.1.0"
|
||||
"America/Toronto","EST5EDT,M3.2.0,M11.1.0"
|
||||
"America/Tortola","AST4"
|
||||
"America/Vancouver","PST8PDT,M3.2.0,M11.1.0"
|
||||
"America/Whitehorse","MST7"
|
||||
"America/Winnipeg","CST6CDT,M3.2.0,M11.1.0"
|
||||
"America/Yakutat","AKST9AKDT,M3.2.0,M11.1.0"
|
||||
"America/Yellowknife","MST7MDT,M3.2.0,M11.1.0"
|
||||
"Antarctica/Casey","<+08>-8"
|
||||
"Antarctica/Davis","<+07>-7"
|
||||
"Antarctica/DumontDUrville","<+10>-10"
|
||||
"Antarctica/Macquarie","AEST-10AEDT,M10.1.0,M4.1.0/3"
|
||||
"Antarctica/Mawson","<+05>-5"
|
||||
"Antarctica/McMurdo","NZST-12NZDT,M9.5.0,M4.1.0/3"
|
||||
"Antarctica/Palmer","<-03>3"
|
||||
"Antarctica/Rothera","<-03>3"
|
||||
"Antarctica/Syowa","<+03>-3"
|
||||
"Antarctica/Troll","<+00>0<+02>-2,M3.5.0/1,M10.5.0/3"
|
||||
"Antarctica/Vostok","<+05>-5"
|
||||
"Arctic/Longyearbyen","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Asia/Aden","<+03>-3"
|
||||
"Asia/Almaty","<+05>-5"
|
||||
"Asia/Amman","<+03>-3"
|
||||
"Asia/Anadyr","<+12>-12"
|
||||
"Asia/Aqtau","<+05>-5"
|
||||
"Asia/Aqtobe","<+05>-5"
|
||||
"Asia/Ashgabat","<+05>-5"
|
||||
"Asia/Atyrau","<+05>-5"
|
||||
"Asia/Baghdad","<+03>-3"
|
||||
"Asia/Bahrain","<+03>-3"
|
||||
"Asia/Baku","<+04>-4"
|
||||
"Asia/Bangkok","<+07>-7"
|
||||
"Asia/Barnaul","<+07>-7"
|
||||
"Asia/Beirut","EET-2EEST,M3.5.0/0,M10.5.0/0"
|
||||
"Asia/Bishkek","<+06>-6"
|
||||
"Asia/Brunei","<+08>-8"
|
||||
"Asia/Chita","<+09>-9"
|
||||
"Asia/Choibalsan","<+08>-8"
|
||||
"Asia/Colombo","<+0530>-5:30"
|
||||
"Asia/Damascus","<+03>-3"
|
||||
"Asia/Dhaka","<+06>-6"
|
||||
"Asia/Dili","<+09>-9"
|
||||
"Asia/Dubai","<+04>-4"
|
||||
"Asia/Dushanbe","<+05>-5"
|
||||
"Asia/Famagusta","EET-2EEST,M3.5.0/3,M10.5.0/4"
|
||||
"Asia/Gaza","EET-2EEST,M3.4.4/50,M10.4.4/50"
|
||||
"Asia/Hebron","EET-2EEST,M3.4.4/50,M10.4.4/50"
|
||||
"Asia/Ho_Chi_Minh","<+07>-7"
|
||||
"Asia/Hong_Kong","HKT-8"
|
||||
"Asia/Hovd","<+07>-7"
|
||||
"Asia/Irkutsk","<+08>-8"
|
||||
"Asia/Jakarta","WIB-7"
|
||||
"Asia/Jayapura","WIT-9"
|
||||
"Asia/Jerusalem","IST-2IDT,M3.4.4/26,M10.5.0"
|
||||
"Asia/Kabul","<+0430>-4:30"
|
||||
"Asia/Kamchatka","<+12>-12"
|
||||
"Asia/Karachi","PKT-5"
|
||||
"Asia/Kathmandu","<+0545>-5:45"
|
||||
"Asia/Khandyga","<+09>-9"
|
||||
"Asia/Kolkata","IST-5:30"
|
||||
"Asia/Krasnoyarsk","<+07>-7"
|
||||
"Asia/Kuala_Lumpur","<+08>-8"
|
||||
"Asia/Kuching","<+08>-8"
|
||||
"Asia/Kuwait","<+03>-3"
|
||||
"Asia/Macau","CST-8"
|
||||
"Asia/Magadan","<+11>-11"
|
||||
"Asia/Makassar","WITA-8"
|
||||
"Asia/Manila","PST-8"
|
||||
"Asia/Muscat","<+04>-4"
|
||||
"Asia/Nicosia","EET-2EEST,M3.5.0/3,M10.5.0/4"
|
||||
"Asia/Novokuznetsk","<+07>-7"
|
||||
"Asia/Novosibirsk","<+07>-7"
|
||||
"Asia/Omsk","<+06>-6"
|
||||
"Asia/Oral","<+05>-5"
|
||||
"Asia/Phnom_Penh","<+07>-7"
|
||||
"Asia/Pontianak","WIB-7"
|
||||
"Asia/Pyongyang","KST-9"
|
||||
"Asia/Qatar","<+03>-3"
|
||||
"Asia/Qyzylorda","<+05>-5"
|
||||
"Asia/Riyadh","<+03>-3"
|
||||
"Asia/Sakhalin","<+11>-11"
|
||||
"Asia/Samarkand","<+05>-5"
|
||||
"Asia/Seoul","KST-9"
|
||||
"Asia/Shanghai","CST-8"
|
||||
"Asia/Singapore","<+08>-8"
|
||||
"Asia/Srednekolymsk","<+11>-11"
|
||||
"Asia/Taipei","CST-8"
|
||||
"Asia/Tashkent","<+05>-5"
|
||||
"Asia/Tbilisi","<+04>-4"
|
||||
"Asia/Tehran","<+0330>-3:30"
|
||||
"Asia/Thimphu","<+06>-6"
|
||||
"Asia/Tokyo","JST-9"
|
||||
"Asia/Tomsk","<+07>-7"
|
||||
"Asia/Ulaanbaatar","<+08>-8"
|
||||
"Asia/Urumqi","<+06>-6"
|
||||
"Asia/Ust-Nera","<+10>-10"
|
||||
"Asia/Vientiane","<+07>-7"
|
||||
"Asia/Vladivostok","<+10>-10"
|
||||
"Asia/Yakutsk","<+09>-9"
|
||||
"Asia/Yangon","<+0630>-6:30"
|
||||
"Asia/Yekaterinburg","<+05>-5"
|
||||
"Asia/Yerevan","<+04>-4"
|
||||
"Atlantic/Azores","<-01>1<+00>,M3.5.0/0,M10.5.0/1"
|
||||
"Atlantic/Bermuda","AST4ADT,M3.2.0,M11.1.0"
|
||||
"Atlantic/Canary","WET0WEST,M3.5.0/1,M10.5.0"
|
||||
"Atlantic/Cape_Verde","<-01>1"
|
||||
"Atlantic/Faroe","WET0WEST,M3.5.0/1,M10.5.0"
|
||||
"Atlantic/Madeira","WET0WEST,M3.5.0/1,M10.5.0"
|
||||
"Atlantic/Reykjavik","GMT0"
|
||||
"Atlantic/South_Georgia","<-02>2"
|
||||
"Atlantic/Stanley","<-03>3"
|
||||
"Atlantic/St_Helena","GMT0"
|
||||
"Australia/Adelaide","ACST-9:30ACDT,M10.1.0,M4.1.0/3"
|
||||
"Australia/Brisbane","AEST-10"
|
||||
"Australia/Broken_Hill","ACST-9:30ACDT,M10.1.0,M4.1.0/3"
|
||||
"Australia/Currie","AEST-10AEDT,M10.1.0,M4.1.0/3"
|
||||
"Australia/Darwin","ACST-9:30"
|
||||
"Australia/Eucla","<+0845>-8:45"
|
||||
"Australia/Hobart","AEST-10AEDT,M10.1.0,M4.1.0/3"
|
||||
"Australia/Lindeman","AEST-10"
|
||||
"Australia/Lord_Howe","<+1030>-10:30<+11>-11,M10.1.0,M4.1.0"
|
||||
"Australia/Melbourne","AEST-10AEDT,M10.1.0,M4.1.0/3"
|
||||
"Australia/Perth","AWST-8"
|
||||
"Australia/Sydney","AEST-10AEDT,M10.1.0,M4.1.0/3"
|
||||
"Europe/Amsterdam","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Andorra","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Astrakhan","<+04>-4"
|
||||
"Europe/Athens","EET-2EEST,M3.5.0/3,M10.5.0/4"
|
||||
"Europe/Belgrade","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Berlin","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Bratislava","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Brussels","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Bucharest","EET-2EEST,M3.5.0/3,M10.5.0/4"
|
||||
"Europe/Budapest","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Busingen","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Chisinau","EET-2EEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Copenhagen","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Dublin","IST-1GMT0,M10.5.0,M3.5.0/1"
|
||||
"Europe/Gibraltar","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Guernsey","GMT0BST,M3.5.0/1,M10.5.0"
|
||||
"Europe/Helsinki","EET-2EEST,M3.5.0/3,M10.5.0/4"
|
||||
"Europe/Isle_of_Man","GMT0BST,M3.5.0/1,M10.5.0"
|
||||
"Europe/Istanbul","<+03>-3"
|
||||
"Europe/Jersey","GMT0BST,M3.5.0/1,M10.5.0"
|
||||
"Europe/Kaliningrad","EET-2"
|
||||
"Europe/Kiev","EET-2EEST,M3.5.0/3,M10.5.0/4"
|
||||
"Europe/Kirov","MSK-3"
|
||||
"Europe/Lisbon","WET0WEST,M3.5.0/1,M10.5.0"
|
||||
"Europe/Ljubljana","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/London","GMT0BST,M3.5.0/1,M10.5.0"
|
||||
"Europe/Luxembourg","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Madrid","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Malta","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Mariehamn","EET-2EEST,M3.5.0/3,M10.5.0/4"
|
||||
"Europe/Minsk","<+03>-3"
|
||||
"Europe/Monaco","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Moscow","MSK-3"
|
||||
"Europe/Oslo","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Paris","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Podgorica","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Prague","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Riga","EET-2EEST,M3.5.0/3,M10.5.0/4"
|
||||
"Europe/Rome","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Samara","<+04>-4"
|
||||
"Europe/San_Marino","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Sarajevo","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Saratov","<+04>-4"
|
||||
"Europe/Simferopol","MSK-3"
|
||||
"Europe/Skopje","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Sofia","EET-2EEST,M3.5.0/3,M10.5.0/4"
|
||||
"Europe/Stockholm","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Tallinn","EET-2EEST,M3.5.0/3,M10.5.0/4"
|
||||
"Europe/Tirane","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Ulyanovsk","<+04>-4"
|
||||
"Europe/Uzhgorod","EET-2EEST,M3.5.0/3,M10.5.0/4"
|
||||
"Europe/Vaduz","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Vatican","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Vienna","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Vilnius","EET-2EEST,M3.5.0/3,M10.5.0/4"
|
||||
"Europe/Volgograd","MSK-3"
|
||||
"Europe/Warsaw","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Zagreb","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Europe/Zaporozhye","EET-2EEST,M3.5.0/3,M10.5.0/4"
|
||||
"Europe/Zurich","CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
"Indian/Antananarivo","EAT-3"
|
||||
"Indian/Chagos","<+06>-6"
|
||||
"Indian/Christmas","<+07>-7"
|
||||
"Indian/Cocos","<+0630>-6:30"
|
||||
"Indian/Comoro","EAT-3"
|
||||
"Indian/Kerguelen","<+05>-5"
|
||||
"Indian/Mahe","<+04>-4"
|
||||
"Indian/Maldives","<+05>-5"
|
||||
"Indian/Mauritius","<+04>-4"
|
||||
"Indian/Mayotte","EAT-3"
|
||||
"Indian/Reunion","<+04>-4"
|
||||
"Pacific/Apia","<+13>-13"
|
||||
"Pacific/Auckland","NZST-12NZDT,M9.5.0,M4.1.0/3"
|
||||
"Pacific/Bougainville","<+11>-11"
|
||||
"Pacific/Chatham","<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45"
|
||||
"Pacific/Chuuk","<+10>-10"
|
||||
"Pacific/Easter","<-06>6<-05>,M9.1.6/22,M4.1.6/22"
|
||||
"Pacific/Efate","<+11>-11"
|
||||
"Pacific/Enderbury","<+13>-13"
|
||||
"Pacific/Fakaofo","<+13>-13"
|
||||
"Pacific/Fiji","<+12>-12"
|
||||
"Pacific/Funafuti","<+12>-12"
|
||||
"Pacific/Galapagos","<-06>6"
|
||||
"Pacific/Gambier","<-09>9"
|
||||
"Pacific/Guadalcanal","<+11>-11"
|
||||
"Pacific/Guam","ChST-10"
|
||||
"Pacific/Honolulu","HST10"
|
||||
"Pacific/Kiritimati","<+14>-14"
|
||||
"Pacific/Kosrae","<+11>-11"
|
||||
"Pacific/Kwajalein","<+12>-12"
|
||||
"Pacific/Majuro","<+12>-12"
|
||||
"Pacific/Marquesas","<-0930>9:30"
|
||||
"Pacific/Midway","SST11"
|
||||
"Pacific/Nauru","<+12>-12"
|
||||
"Pacific/Niue","<-11>11"
|
||||
"Pacific/Norfolk","<+11>-11<+12>,M10.1.0,M4.1.0/3"
|
||||
"Pacific/Noumea","<+11>-11"
|
||||
"Pacific/Pago_Pago","SST11"
|
||||
"Pacific/Palau","<+09>-9"
|
||||
"Pacific/Pitcairn","<-08>8"
|
||||
"Pacific/Pohnpei","<+11>-11"
|
||||
"Pacific/Port_Moresby","<+10>-10"
|
||||
"Pacific/Rarotonga","<-10>10"
|
||||
"Pacific/Saipan","ChST-10"
|
||||
"Pacific/Tahiti","<-10>10"
|
||||
"Pacific/Tarawa","<+12>-12"
|
||||
"Pacific/Tongatapu","<+13>-13"
|
||||
"Pacific/Wake","<+12>-12"
|
||||
"Pacific/Wallis","<+12>-12"
|
||||
"Etc/GMT","GMT0"
|
||||
"Etc/GMT-0","GMT0"
|
||||
"Etc/GMT-1","<+01>-1"
|
||||
"Etc/GMT-2","<+02>-2"
|
||||
"Etc/GMT-3","<+03>-3"
|
||||
"Etc/GMT-4","<+04>-4"
|
||||
"Etc/GMT-5","<+05>-5"
|
||||
"Etc/GMT-6","<+06>-6"
|
||||
"Etc/GMT-7","<+07>-7"
|
||||
"Etc/GMT-8","<+08>-8"
|
||||
"Etc/GMT-9","<+09>-9"
|
||||
"Etc/GMT-10","<+10>-10"
|
||||
"Etc/GMT-11","<+11>-11"
|
||||
"Etc/GMT-12","<+12>-12"
|
||||
"Etc/GMT-13","<+13>-13"
|
||||
"Etc/GMT-14","<+14>-14"
|
||||
"Etc/GMT0","GMT0"
|
||||
"Etc/GMT+0","GMT0"
|
||||
"Etc/GMT+1","<-01>1"
|
||||
"Etc/GMT+2","<-02>2"
|
||||
"Etc/GMT+3","<-03>3"
|
||||
"Etc/GMT+4","<-04>4"
|
||||
"Etc/GMT+5","<-05>5"
|
||||
"Etc/GMT+6","<-06>6"
|
||||
"Etc/GMT+7","<-07>7"
|
||||
"Etc/GMT+8","<-08>8"
|
||||
"Etc/GMT+9","<-09>9"
|
||||
"Etc/GMT+10","<-10>10"
|
||||
"Etc/GMT+11","<-11>11"
|
||||
"Etc/GMT+12","<-12>12"
|
||||
"Etc/UCT","UTC0"
|
||||
"Etc/UTC","UTC0"
|
||||
"Etc/Greenwich","GMT0"
|
||||
"Etc/Universal","UTC0"
|
||||
"Etc/Zulu","UTC0"
|
||||
|
42
Data/system_sources/app/TimeZone/search.svg
Normal file
42
Data/system_sources/app/TimeZone/search.svg
Normal 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="search.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
inkscape:export-filename="search.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
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="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="47.5"
|
||||
inkscape:cx="12"
|
||||
inkscape:cy="10.989474"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1368"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="M 884.20117,0.42105034 548.79064,-334.98948 q -39.92982,31.94386 -91.8386,50.57778 -51.90877,18.63392 -110.47251,18.63392 -145.07836,0 -245.56842,-100.49006 Q 0.42105263,-466.7579 0.42105263,-611.83626 q 0,-145.07836 100.49005737,-245.56842 100.49006,-100.49006 245.56842,-100.49006 145.07836,0 245.56842,100.49006 100.49006,100.49006 100.49006,245.56842 0,58.56374 -18.63392,110.47251 -18.63392,51.90878 -50.57778,91.8386 L 958.73684,-74.114622 Z M 346.47953,-372.25731 q 99.82456,0 169.70175,-69.8772 69.8772,-69.87719 69.8772,-169.70175 0,-99.82456 -69.8772,-169.70175 -69.87719,-69.8772 -169.70175,-69.8772 -99.82456,0 -169.70176,69.8772 -69.87718,69.87719 -69.87718,169.70175 0,99.82456 69.87718,169.70175 69.8772,69.8772 169.70176,69.8772 z"
|
||||
id="path1"
|
||||
style="stroke-width:1.33099" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
42
Data/system_sources/app_icon_time_date_settings.svg
Normal file
42
Data/system_sources/app_icon_time_date_settings.svg
Normal file
@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="16"
|
||||
viewBox="0 -960 640 640"
|
||||
width="16"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="calendar_clock_24dp_E8EAED_FILL0_wght400_GRAD0_opsz24.svg"
|
||||
inkscape:export-filename="calendar_clock_24dp_E8EAED_FILL0_wght400_GRAD0_opsz24.png"
|
||||
inkscape:export-xdpi="96.18351"
|
||||
inkscape:export-ydpi="96.18351"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
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="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="47.5"
|
||||
inkscape:cx="10.842105"
|
||||
inkscape:cy="10.821053"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1368"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="M 61.257143,-775.80752 H 487.10977 v -60.83609 H 61.257143 Z m 0,0 v -60.83609 z m 0,425.85263 q -25.094888,0 -42.965489,-17.8706 -17.870601,-17.8706 -17.870601,-42.96549 v -425.85263 q 0,-25.09489 17.870601,-42.96549 17.870601,-17.8706 42.965489,-17.8706 H 91.67518 v -60.83609 h 60.83609 v 60.83609 h 243.34437 v -60.83609 h 60.83609 v 60.83609 h 30.41804 q 25.09489,0 42.96549,17.8706 17.8706,17.8706 17.8706,42.96549 v 172.62241 q -14.44857,-6.84406 -29.6576,-11.40677 -15.20901,-4.56271 -31.17849,-6.84406 v -32.6994 H 61.257143 v 304.18045 H 252.89083 q 5.32315,16.72993 12.54744,31.93895 7.22428,15.20902 17.11015,28.89714 z m 395.434587,30.41805 q -63.11745,0 -107.60384,-44.48639 -44.48639,-44.4864 -44.48639,-107.60384 0,-63.11744 44.48639,-107.60384 44.48639,-44.48638 107.60384,-44.48638 63.11744,0 107.60383,44.48638 44.48639,44.4864 44.48639,107.60384 0,63.11744 -44.48639,107.60384 -44.48639,44.48639 -107.60383,44.48639 z m 50.95022,-79.84737 21.29264,-21.29263 -57.03384,-57.03384 V -562.8812 H 441.4827 v 97.33774 z"
|
||||
id="path1"
|
||||
style="stroke-width:0.760454;fill:#000000" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@ -12,8 +12,16 @@
|
||||
- M5Stack CoreS3 SD card mounts, but cannot be read. There is currently a notice about it [here](https://github.com/espressif/esp-bsp/blob/master/bsp/m5stack_core_s3/README.md).
|
||||
- EventFlag: Fix return value of set/get/wait (the errors are weirdly mixed in)
|
||||
- Consistently use either ESP_TARGET or ESP_PLATFORM
|
||||
- tt_check() failure during app argument bundle nullptr check seems to trigger SIGSEGV
|
||||
- Fix bug in T-Deck/etc: esp_lvgl_port settings has a large stack depth (~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.
|
||||
|
||||
# TODOs
|
||||
- Try to fix SDL pipeline issue with apt-get:
|
||||
```yaml
|
||||
- name: Install SDL
|
||||
run: sudo apt-get install -y libsdl2-dev cmake
|
||||
```
|
||||
- Make "blocking" argument the last one, and put it default to false (or remove it entirely?): void startApp(const std::string& id, bool blocking, std::shared_ptr<const Bundle> parameters) {
|
||||
- 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
|
||||
|
||||
@ -23,13 +23,14 @@ struct Gui {
|
||||
PubSubSubscription* loader_pubsub_subscription = nullptr;
|
||||
|
||||
// Layers and Canvas
|
||||
lv_obj_t* lvgl_parent = nullptr;
|
||||
lv_obj_t* appRootWidget = nullptr;
|
||||
lv_obj_t* statusbarWidget = nullptr;
|
||||
|
||||
// App-specific
|
||||
ViewPort* app_view_port = nullptr;
|
||||
ViewPort* appViewPort = nullptr;
|
||||
|
||||
lv_obj_t* _Nullable keyboard = nullptr;
|
||||
lv_group_t* keyboard_group = nullptr;
|
||||
lv_group_t* keyboardGroup = nullptr;
|
||||
};
|
||||
|
||||
/** Update GUI, request redraw */
|
||||
|
||||
@ -90,10 +90,13 @@ public:
|
||||
// endregion LoaderMessage
|
||||
|
||||
struct Loader {
|
||||
std::shared_ptr<PubSub> pubsub_internal = std::make_shared<PubSub>();
|
||||
std::shared_ptr<PubSub> pubsub_external = std::make_shared<PubSub>();
|
||||
std::shared_ptr<PubSub> pubsubInternal = std::make_shared<PubSub>();
|
||||
std::shared_ptr<PubSub> pubsubExternal = std::make_shared<PubSub>();
|
||||
Mutex mutex = Mutex(Mutex::TypeRecursive);
|
||||
std::stack<app::AppInstance*> app_stack;
|
||||
std::stack<app::AppInstance*> appStack;
|
||||
/** The dispatcher thread needs a callstack large enough to accommodate all the dispatched methods.
|
||||
* This includes full LVGL redraw via Gui::redraw()
|
||||
*/
|
||||
std::unique_ptr<DispatcherThread> dispatcherThread = std::make_unique<DispatcherThread>("loader_dispatcher", 6144); // Files app requires ~5k
|
||||
};
|
||||
|
||||
|
||||
@ -54,6 +54,8 @@ namespace app {
|
||||
namespace settings { extern const AppManifest manifest; }
|
||||
namespace systeminfo { extern const AppManifest manifest; }
|
||||
namespace textviewer { extern const AppManifest manifest; }
|
||||
namespace timedatesettings { extern const AppManifest manifest; }
|
||||
namespace timezone { extern const AppManifest manifest; }
|
||||
namespace usbsettings { extern const AppManifest manifest; }
|
||||
namespace wifiapsettings { extern const AppManifest manifest; }
|
||||
namespace wificonnect { extern const AppManifest manifest; }
|
||||
@ -87,6 +89,8 @@ static const std::vector<const app::AppManifest*> system_apps = {
|
||||
&app::selectiondialog::manifest,
|
||||
&app::systeminfo::manifest,
|
||||
&app::textviewer::manifest,
|
||||
&app::timedatesettings::manifest,
|
||||
&app::timezone::manifest,
|
||||
&app::usbsettings::manifest,
|
||||
&app::wifiapsettings::manifest,
|
||||
&app::wificonnect::manifest,
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include "lvgl.h"
|
||||
#include "Tactility.h"
|
||||
#include "hal/usb/Usb.h"
|
||||
#include "kernel/SystemEvents.h"
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "kernel/PanicHandler.h"
|
||||
@ -35,6 +36,8 @@ struct Data {
|
||||
static int32_t bootThreadCallback(TT_UNUSED void* context) {
|
||||
TickType_t start_time = kernel::getTicks();
|
||||
|
||||
kernel::systemEventPublish(kernel::SystemEvent::BootSplash);
|
||||
|
||||
auto* lvgl_display = lv_display_get_default();
|
||||
tt_assert(lvgl_display != nullptr);
|
||||
auto* hal_display = (hal::Display*)lv_display_get_user_data(lvgl_display);
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#include "TactilityCore.h"
|
||||
#include <cstring>
|
||||
#include <bits/stdc++.h>
|
||||
#include <StringUtils.h>
|
||||
|
||||
namespace tt::app::files {
|
||||
|
||||
@ -69,25 +70,13 @@ bool isSupportedExecutableFile(const std::string& filename) {
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::basic_string<T> lowercase(const std::basic_string<T>& input) {
|
||||
std::basic_string<T> output = input;
|
||||
std::transform(
|
||||
output.begin(),
|
||||
output.end(),
|
||||
output.begin(),
|
||||
[](const T character) { return static_cast<T>(std::tolower(character)); }
|
||||
);
|
||||
return std::move(output);
|
||||
}
|
||||
|
||||
bool isSupportedImageFile(const std::string& filename) {
|
||||
// Currently only the PNG library is built into Tactility
|
||||
return lowercase(filename).ends_with(".png");
|
||||
return string::lowercase(filename).ends_with(".png");
|
||||
}
|
||||
|
||||
bool isSupportedTextFile(const std::string& filename) {
|
||||
std::string filename_lower = lowercase(filename);
|
||||
std::string filename_lower = string::lowercase(filename);
|
||||
return filename_lower.ends_with(".txt") ||
|
||||
filename_lower.ends_with(".ini") ||
|
||||
filename_lower.ends_with(".json") ||
|
||||
|
||||
136
Tactility/Source/app/timedatesettings/TimeDateSettings.cpp
Normal file
136
Tactility/Source/app/timedatesettings/TimeDateSettings.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
#include <StringUtils.h>
|
||||
#include "lvgl.h"
|
||||
#include "lvgl/Toolbar.h"
|
||||
#include "service/loader/Loader.h"
|
||||
#include "app/timezone/TimeZone.h"
|
||||
#include "Assets.h"
|
||||
#include "Tactility.h"
|
||||
#include "time/Time.h"
|
||||
#include "lvgl/LvglSync.h"
|
||||
|
||||
#define TAG "text_viewer"
|
||||
|
||||
namespace tt::app::timedatesettings {
|
||||
|
||||
extern const AppManifest manifest;
|
||||
|
||||
struct Data {
|
||||
Mutex mutex = Mutex(Mutex::TypeRecursive);
|
||||
lv_obj_t* regionLabelWidget = nullptr;
|
||||
};
|
||||
|
||||
/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */
|
||||
std::shared_ptr<Data> _Nullable optData() {
|
||||
app::AppContext* app = service::loader::getCurrentApp();
|
||||
if (app->getManifest().id == manifest.id) {
|
||||
return std::static_pointer_cast<Data>(app->getData());
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void onConfigureTimeZonePressed(TT_UNUSED lv_event_t* event) {
|
||||
timezone::start();
|
||||
}
|
||||
|
||||
static void onTimeFormatChanged(lv_event_t* event) {
|
||||
auto* widget = lv_event_get_target_obj(event);
|
||||
bool show_24 = lv_obj_has_state(widget, LV_STATE_CHECKED);
|
||||
time::setTimeFormat24Hour(show_24);
|
||||
}
|
||||
|
||||
static void onShow(AppContext& app, lv_obj_t* parent) {
|
||||
auto data = std::static_pointer_cast<Data>(app.getData());
|
||||
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
|
||||
lvgl::toolbar_create(parent, app);
|
||||
|
||||
auto* main_wrapper = lv_obj_create(parent);
|
||||
lv_obj_set_flex_flow(main_wrapper, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_width(main_wrapper, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(main_wrapper, 1);
|
||||
|
||||
auto* region_wrapper = lv_obj_create(main_wrapper);
|
||||
lv_obj_set_width(region_wrapper, LV_PCT(100));
|
||||
lv_obj_set_height(region_wrapper, LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_pad_all(region_wrapper, 0, 0);
|
||||
lv_obj_set_style_border_width(region_wrapper, 0, 0);
|
||||
|
||||
auto* region_prefix_label = lv_label_create(region_wrapper);
|
||||
lv_label_set_text(region_prefix_label, "Region: ");
|
||||
lv_obj_align(region_prefix_label, LV_ALIGN_LEFT_MID, 0, 0);
|
||||
|
||||
auto* region_label = lv_label_create(region_wrapper);
|
||||
std::string timeZoneName = time::getTimeZoneName();
|
||||
if (timeZoneName.empty()) {
|
||||
timeZoneName = "not set";
|
||||
}
|
||||
data->regionLabelWidget = region_label;
|
||||
lv_label_set_text(region_label, timeZoneName.c_str());
|
||||
// TODO: Find out why Y offset is needed
|
||||
lv_obj_align_to(region_label, region_prefix_label, LV_ALIGN_OUT_RIGHT_MID, 0, 8);
|
||||
|
||||
auto* region_button = lv_button_create(region_wrapper);
|
||||
lv_obj_align(region_button, LV_ALIGN_TOP_RIGHT, 0, 0);
|
||||
auto* region_button_image = lv_image_create(region_button);
|
||||
lv_obj_add_event_cb(region_button, onConfigureTimeZonePressed, LV_EVENT_SHORT_CLICKED, nullptr);
|
||||
lv_image_set_src(region_button_image, LV_SYMBOL_SETTINGS);
|
||||
|
||||
auto* time_format_wrapper= lv_obj_create(main_wrapper);
|
||||
lv_obj_set_width(time_format_wrapper, LV_PCT(100));
|
||||
lv_obj_set_height(time_format_wrapper, LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_pad_all(time_format_wrapper, 0, 0);
|
||||
lv_obj_set_style_border_width(time_format_wrapper, 0, 0);
|
||||
|
||||
auto* time_24h_label = lv_label_create(time_format_wrapper);
|
||||
lv_label_set_text(time_24h_label, "24-hour clock");
|
||||
lv_obj_align(time_24h_label, LV_ALIGN_LEFT_MID, 0, 0);
|
||||
|
||||
auto* time_24h_switch = lv_switch_create(time_format_wrapper);
|
||||
lv_obj_align(time_24h_switch, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||
lv_obj_add_event_cb(time_24h_switch, onTimeFormatChanged, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
if (time::isTimeFormat24Hour()) {
|
||||
lv_obj_add_state(time_24h_switch, LV_STATE_CHECKED);
|
||||
} else {
|
||||
lv_obj_remove_state(time_24h_switch, LV_STATE_CHECKED);
|
||||
}
|
||||
}
|
||||
|
||||
static void onStart(AppContext& app) {
|
||||
auto data = std::make_shared<Data>();
|
||||
app.setData(data);
|
||||
}
|
||||
|
||||
static void onResult(AppContext& app, Result result, const Bundle& bundle) {
|
||||
if (result == ResultOk) {
|
||||
auto data = std::static_pointer_cast<Data>(app.getData());
|
||||
auto name = timezone::getResultName(bundle);
|
||||
auto code = timezone::getResultCode(bundle);
|
||||
TT_LOG_I(TAG, "Result name=%s code=%s", name.c_str(), code.c_str());
|
||||
time::setTimeZone(name, code);
|
||||
|
||||
if (!name.empty()) {
|
||||
if (lvgl::lock(100 / portTICK_PERIOD_MS)) {
|
||||
lv_label_set_text(data->regionLabelWidget, name.c_str());
|
||||
lvgl::unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "TimeDateSettings",
|
||||
.name = "Time & Date",
|
||||
.icon = TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS,
|
||||
.type = TypeSettings,
|
||||
.onStart = onStart,
|
||||
.onShow = onShow,
|
||||
.onResult = onResult
|
||||
};
|
||||
|
||||
void start() {
|
||||
service::loader::startApp(manifest.id);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
7
Tactility/Source/app/timedatesettings/TimeDateSettings.h
Normal file
7
Tactility/Source/app/timedatesettings/TimeDateSettings.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace tt::app::timedatesettings {
|
||||
|
||||
void start();
|
||||
|
||||
}
|
||||
243
Tactility/Source/app/timezone/TimeZone.cpp
Normal file
243
Tactility/Source/app/timezone/TimeZone.cpp
Normal file
@ -0,0 +1,243 @@
|
||||
#include "TimeZone.h"
|
||||
#include "app/AppManifest.h"
|
||||
#include "app/AppContext.h"
|
||||
#include "service/loader/Loader.h"
|
||||
#include "lvgl.h"
|
||||
#include "lvgl/Toolbar.h"
|
||||
#include "Partitions.h"
|
||||
#include "TactilityHeadless.h"
|
||||
#include "lvgl/LvglSync.h"
|
||||
#include "service/gui/Gui.h"
|
||||
#include <memory>
|
||||
#include <StringUtils.h>
|
||||
#include <Timer.h>
|
||||
|
||||
namespace tt::app::timezone {
|
||||
|
||||
#define TAG "timezone_select"
|
||||
|
||||
#define RESULT_BUNDLE_CODE_INDEX "code"
|
||||
#define RESULT_BUNDLE_NAME_INDEX "name"
|
||||
|
||||
extern const AppManifest manifest;
|
||||
|
||||
struct TimeZoneEntry {
|
||||
std::string name;
|
||||
std::string code;
|
||||
};
|
||||
|
||||
struct Data {
|
||||
Mutex mutex;
|
||||
std::vector<TimeZoneEntry> entries;
|
||||
std::unique_ptr<Timer> updateTimer;
|
||||
lv_obj_t* listWidget = nullptr;
|
||||
lv_obj_t* filterTextareaWidget = nullptr;
|
||||
};
|
||||
|
||||
static void updateList(std::shared_ptr<Data>& data);
|
||||
|
||||
static bool parseEntry(const std::string& input, std::string& outName, std::string& outCode) {
|
||||
std::string partial_strip = input.substr(1, input.size() - 3);
|
||||
auto first_end_quote = partial_strip.find('"');
|
||||
if (first_end_quote == std::string::npos) {
|
||||
return false;
|
||||
} else {
|
||||
outName = partial_strip.substr(0, first_end_quote);
|
||||
outCode = partial_strip.substr(first_end_quote + 3);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// region Result
|
||||
|
||||
std::string getResultName(const Bundle& bundle) {
|
||||
std::string result;
|
||||
bundle.optString(RESULT_BUNDLE_NAME_INDEX, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string getResultCode(const Bundle& bundle) {
|
||||
std::string result;
|
||||
bundle.optString(RESULT_BUNDLE_CODE_INDEX, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void setResultName(std::shared_ptr<Bundle>& bundle, const std::string& name) {
|
||||
bundle->putString(RESULT_BUNDLE_NAME_INDEX, name);
|
||||
}
|
||||
|
||||
void setResultCode(std::shared_ptr<Bundle>& bundle, const std::string& code) {
|
||||
bundle->putString(RESULT_BUNDLE_CODE_INDEX, code);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
static void onUpdateTimer(std::shared_ptr<void> context) {
|
||||
auto data = std::static_pointer_cast<Data>(context);
|
||||
updateList(data);
|
||||
}
|
||||
|
||||
static void onTextareaValueChanged(TT_UNUSED lv_event_t* e) {
|
||||
auto* app = service::loader::getCurrentApp();
|
||||
auto app_data = app->getData();
|
||||
auto data = std::static_pointer_cast<Data>(app_data);
|
||||
|
||||
if (data->mutex.lock(100 / portTICK_PERIOD_MS)) {
|
||||
if (data->updateTimer->isRunning()) {
|
||||
data->updateTimer->stop();
|
||||
}
|
||||
|
||||
data->updateTimer->start(500 / portTICK_PERIOD_MS);
|
||||
|
||||
data->mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
static void onListItemSelected(lv_event_t* e) {
|
||||
auto index = reinterpret_cast<std::size_t>(lv_event_get_user_data(e));
|
||||
TT_LOG_I(TAG, "Selected item at index %zu", index);
|
||||
auto* app = service::loader::getCurrentApp();
|
||||
auto data = std::static_pointer_cast<Data>(app->getData());
|
||||
|
||||
auto& entry = data->entries[index];
|
||||
|
||||
auto bundle = std::make_shared<Bundle>();
|
||||
setResultName(bundle, entry.name);
|
||||
setResultCode(bundle, entry.code);
|
||||
app->setResult(app::ResultOk, bundle);
|
||||
|
||||
service::loader::stopApp();
|
||||
}
|
||||
|
||||
static void createListItem(lv_obj_t* list, const std::string& title, size_t index) {
|
||||
lv_obj_t* btn = lv_list_add_button(list, nullptr, title.c_str());
|
||||
lv_obj_add_event_cb(btn, &onListItemSelected, LV_EVENT_SHORT_CLICKED, (void*)index);
|
||||
}
|
||||
|
||||
static void readTimeZones(const std::shared_ptr<Data>& data, std::string filter) {
|
||||
auto path = std::string(MOUNT_POINT_SYSTEM) + "/timezones.csv";
|
||||
auto* file = fopen(path.c_str(), "rb");
|
||||
if (file == nullptr) {
|
||||
TT_LOG_E(TAG, "Failed to open %s", path.c_str());
|
||||
return;
|
||||
}
|
||||
char line[96];
|
||||
std::string name;
|
||||
std::string code;
|
||||
uint32_t count = 0;
|
||||
std::vector<TimeZoneEntry> entries;
|
||||
while (fgets(line, 96, file)) {
|
||||
if (parseEntry(line, name, code)) {
|
||||
if (tt::string::lowercase(name).find(filter) != std::string::npos) {
|
||||
count++;
|
||||
entries.push_back({
|
||||
.name = name,
|
||||
.code = code
|
||||
});
|
||||
|
||||
// Safety guard
|
||||
if (count > 50) {
|
||||
// TODO: Show warning that we're not displaying a complete list
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Parse error at line %lu", count);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
if (data->mutex.lock(100 / portTICK_PERIOD_MS)) {
|
||||
data->entries = std::move(entries);
|
||||
data->mutex.unlock();
|
||||
} else {
|
||||
TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
|
||||
}
|
||||
|
||||
TT_LOG_I(TAG, "Processed %lu entries", count);
|
||||
}
|
||||
|
||||
static void updateList(std::shared_ptr<Data>& data) {
|
||||
if (lvgl::lock(100 / portTICK_PERIOD_MS)) {
|
||||
std::string filter = tt::string::lowercase(std::string(lv_textarea_get_text(data->filterTextareaWidget)));
|
||||
readTimeZones(data, filter);
|
||||
lvgl::unlock();
|
||||
} else {
|
||||
TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL");
|
||||
return;
|
||||
}
|
||||
|
||||
if (lvgl::lock(100 / portTICK_PERIOD_MS)) {
|
||||
if (data->mutex.lock(100 / portTICK_PERIOD_MS)) {
|
||||
lv_obj_clean(data->listWidget);
|
||||
|
||||
uint32_t index = 0;
|
||||
for (auto& entry : data->entries) {
|
||||
createListItem(data->listWidget, entry.name, index);
|
||||
index++;
|
||||
}
|
||||
|
||||
data->mutex.unlock();
|
||||
}
|
||||
|
||||
lvgl::unlock();
|
||||
}
|
||||
}
|
||||
|
||||
static void onShow(AppContext& app, lv_obj_t* parent) {
|
||||
auto data = std::static_pointer_cast<Data>(app.getData());
|
||||
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lvgl::toolbar_create(parent, app);
|
||||
|
||||
auto* search_wrapper = lv_obj_create(parent);
|
||||
lv_obj_set_size(search_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_flex_flow(search_wrapper, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(search_wrapper, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START);
|
||||
lv_obj_set_style_pad_all(search_wrapper, 0, 0);
|
||||
lv_obj_set_style_border_width(search_wrapper, 0, 0);
|
||||
|
||||
auto* icon = lv_image_create(search_wrapper);
|
||||
lv_obj_set_style_margin_left(icon, 8, 0);
|
||||
lv_obj_set_style_image_recolor_opa(icon, 255, 0);
|
||||
lv_obj_set_style_image_recolor(icon, lv_theme_get_color_primary(parent), 0);
|
||||
|
||||
std::string icon_path = app.getPaths()->getSystemPathLvgl("search.png");
|
||||
lv_image_set_src(icon, icon_path.c_str());
|
||||
lv_obj_set_style_image_recolor(icon, lv_theme_get_color_primary(parent), 0);
|
||||
|
||||
auto* textarea = lv_textarea_create(search_wrapper);
|
||||
lv_textarea_set_placeholder_text(textarea, "e.g. Europe/Amsterdam");
|
||||
lv_textarea_set_one_line(textarea, true);
|
||||
lv_obj_add_event_cb(textarea, onTextareaValueChanged, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
data->filterTextareaWidget = textarea;
|
||||
lv_obj_set_flex_grow(textarea, 1);
|
||||
service::gui::keyboardAddTextArea(textarea);
|
||||
|
||||
auto* list = lv_list_create(parent);
|
||||
lv_obj_set_width(list, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(list, 1);
|
||||
lv_obj_set_style_border_width(list, 0, 0);
|
||||
data->listWidget = list;
|
||||
}
|
||||
|
||||
static void onStart(AppContext& app) {
|
||||
auto data = std::make_shared<Data>();
|
||||
data->updateTimer = std::make_unique<Timer>(Timer::TypeOnce, onUpdateTimer, data);
|
||||
app.setData(data);
|
||||
}
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.id = "TimeZone",
|
||||
.name = "Select timezone",
|
||||
.type = TypeHidden,
|
||||
.onStart = onStart,
|
||||
.onShow = onShow,
|
||||
};
|
||||
|
||||
void start() {
|
||||
service::loader::startApp(manifest.id);
|
||||
}
|
||||
|
||||
}
|
||||
12
Tactility/Source/app/timezone/TimeZone.h
Normal file
12
Tactility/Source/app/timezone/TimeZone.h
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "Bundle.h"
|
||||
|
||||
namespace tt::app::timezone {
|
||||
|
||||
void start();
|
||||
|
||||
std::string getResultName(const Bundle& bundle);
|
||||
std::string getResultCode(const Bundle& bundle);
|
||||
|
||||
}
|
||||
@ -6,6 +6,7 @@
|
||||
#include "hal/Keyboard.h"
|
||||
#include "lvgl/LvglKeypad.h"
|
||||
#include "lvgl/Lvgl.h"
|
||||
#include "kernel/SystemEvents.h"
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
@ -76,6 +77,8 @@ bool initKeyboard(hal::Display* display, hal::Keyboard* keyboard) {
|
||||
void init(const hal::Configuration& config) {
|
||||
TT_LOG_I(TAG, "Starting");
|
||||
|
||||
kernel::systemEventPublish(kernel::SystemEvent::BootInitLvglBegin);
|
||||
|
||||
if (config.initLvgl != nullptr && !config.initLvgl()) {
|
||||
TT_LOG_E(TAG, "LVGL init failed");
|
||||
return;
|
||||
@ -98,6 +101,8 @@ void init(const hal::Configuration& config) {
|
||||
}
|
||||
|
||||
TT_LOG_I(TAG, "Finished");
|
||||
|
||||
kernel::systemEventPublish(kernel::SystemEvent::BootInitLvglEnd);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
#define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration
|
||||
|
||||
#include <Timer.h>
|
||||
#include "Statusbar.h"
|
||||
|
||||
#include "Mutex.h"
|
||||
@ -8,11 +10,15 @@
|
||||
|
||||
#include "LvglSync.h"
|
||||
#include "lvgl.h"
|
||||
#include "kernel/SystemEvents.h"
|
||||
#include "time/Time.h"
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
#define TAG "statusbar"
|
||||
|
||||
static void onUpdateTime(TT_UNUSED std::shared_ptr<void> context);
|
||||
|
||||
struct StatusbarIcon {
|
||||
std::string image;
|
||||
bool visible = false;
|
||||
@ -23,12 +29,18 @@ struct StatusbarData {
|
||||
Mutex mutex = Mutex(Mutex::TypeRecursive);
|
||||
std::shared_ptr<PubSub> pubsub = std::make_shared<PubSub>();
|
||||
StatusbarIcon icons[STATUSBAR_ICON_LIMIT] = {};
|
||||
Timer* time_update_timer = new Timer(Timer::TypeOnce, onUpdateTime, nullptr);
|
||||
uint8_t time_hours = 0;
|
||||
uint8_t time_minutes = 0;
|
||||
bool time_set = false;
|
||||
kernel::SystemEventSubscription systemEventSubscription = 0;
|
||||
};
|
||||
|
||||
static StatusbarData statusbar_data;
|
||||
|
||||
typedef struct {
|
||||
lv_obj_t obj;
|
||||
lv_obj_t* time;
|
||||
lv_obj_t* icons[STATUSBAR_ICON_LIMIT];
|
||||
lv_obj_t* battery_icon;
|
||||
PubSubSubscription* pubsub_subscription;
|
||||
@ -46,8 +58,40 @@ static void statusbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj);
|
||||
static void statusbar_destructor(const lv_obj_class_t* class_p, lv_obj_t* obj);
|
||||
static void statusbar_event(const lv_obj_class_t* class_p, lv_event_t* event);
|
||||
|
||||
static void update_time(Statusbar* statusbar);
|
||||
static void update_main(Statusbar* statusbar);
|
||||
|
||||
static TickType_t getNextUpdateTime() {
|
||||
time_t now = ::time(nullptr);
|
||||
struct tm* tm_struct = localtime(&now);
|
||||
uint32_t seconds_to_wait = 60U - tm_struct->tm_sec;
|
||||
TT_LOG_I(TAG, "Update in %lu s", seconds_to_wait);
|
||||
return pdMS_TO_TICKS(seconds_to_wait * 1000U);
|
||||
}
|
||||
|
||||
static void onUpdateTime(TT_UNUSED std::shared_ptr<void> context) {
|
||||
time_t now = ::time(nullptr);
|
||||
struct tm* tm_struct = localtime(&now);
|
||||
|
||||
if (statusbar_data.mutex.lock(100 / portTICK_PERIOD_MS)) {
|
||||
if (tm_struct->tm_year >= (2025 - 1900)) {
|
||||
statusbar_data.time_hours = tm_struct->tm_hour;
|
||||
statusbar_data.time_minutes = tm_struct->tm_min;
|
||||
statusbar_data.time_set = true;
|
||||
|
||||
// Reschedule
|
||||
statusbar_data.time_update_timer->start(getNextUpdateTime());
|
||||
|
||||
// Notify widget
|
||||
tt_pubsub_publish(statusbar_data.pubsub, nullptr);
|
||||
} else {
|
||||
statusbar_data.time_update_timer->start(pdMS_TO_TICKS(60000U));
|
||||
}
|
||||
|
||||
statusbar_data.mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
static const lv_obj_class_t statusbar_class = {
|
||||
.base_class = &lv_obj_class,
|
||||
.constructor_cb = &statusbar_constructor,
|
||||
@ -73,6 +117,15 @@ static void statusbar_pubsub_event(TT_UNUSED const void* message, void* obj) {
|
||||
}
|
||||
}
|
||||
|
||||
static void onNetworkConnected(TT_UNUSED kernel::SystemEvent event) {
|
||||
if (statusbar_data.mutex.lock(100 / portTICK_PERIOD_MS)) {
|
||||
statusbar_data.time_update_timer->stop();
|
||||
statusbar_data.time_update_timer->start(5);
|
||||
|
||||
statusbar_data.mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
static void statusbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) {
|
||||
LV_UNUSED(class_p);
|
||||
LV_TRACE_OBJ_CREATE("begin");
|
||||
@ -80,6 +133,14 @@ static void statusbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
|
||||
LV_TRACE_OBJ_CREATE("finished");
|
||||
auto* statusbar = (Statusbar*)obj;
|
||||
statusbar->pubsub_subscription = tt_pubsub_subscribe(statusbar_data.pubsub, &statusbar_pubsub_event, statusbar);
|
||||
|
||||
if (!statusbar_data.time_update_timer->isRunning()) {
|
||||
statusbar_data.time_update_timer->start(50 / portTICK_PERIOD_MS);
|
||||
statusbar_data.systemEventSubscription = kernel::systemEventAddListener(
|
||||
kernel::SystemEvent::Time,
|
||||
onNetworkConnected
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static void statusbar_destructor(TT_UNUSED const lv_obj_class_t* class_p, lv_obj_t* obj) {
|
||||
@ -108,15 +169,21 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) {
|
||||
obj_set_style_no_padding(obj);
|
||||
lv_obj_center(obj);
|
||||
lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
|
||||
lv_obj_t* left_spacer = lv_obj_create(obj);
|
||||
statusbar->time = lv_label_create(obj);
|
||||
lv_obj_set_style_text_color(statusbar->time, lv_color_white(), 0);
|
||||
lv_obj_set_style_margin_left(statusbar->time, 4, 0);
|
||||
update_time(statusbar);
|
||||
|
||||
auto* left_spacer = lv_obj_create(obj);
|
||||
lv_obj_set_size(left_spacer, 1, 1);
|
||||
obj_set_style_bg_invisible(left_spacer);
|
||||
lv_obj_set_flex_grow(left_spacer, 1);
|
||||
|
||||
statusbar_lock(TtWaitForever);
|
||||
for (int i = 0; i < STATUSBAR_ICON_LIMIT; ++i) {
|
||||
lv_obj_t* image = lv_image_create(obj);
|
||||
auto* image = lv_image_create(obj);
|
||||
lv_obj_set_size(image, STATUSBAR_ICON_SIZE, STATUSBAR_ICON_SIZE);
|
||||
obj_set_style_no_padding(image);
|
||||
obj_set_style_bg_blacken(image);
|
||||
@ -129,7 +196,19 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
static void update_time(Statusbar* statusbar) {
|
||||
if (statusbar_data.time_set) {
|
||||
bool format24 = time::isTimeFormat24Hour();
|
||||
int hours = format24 ? statusbar_data.time_hours : statusbar_data.time_hours % 12;
|
||||
lv_label_set_text_fmt(statusbar->time, "%d:%02d", hours, statusbar_data.time_minutes);
|
||||
} else {
|
||||
lv_label_set_text(statusbar->time, "");
|
||||
}
|
||||
}
|
||||
|
||||
static void update_main(Statusbar* statusbar) {
|
||||
update_time(statusbar);
|
||||
|
||||
if (statusbar_lock(50 / portTICK_PERIOD_MS)) {
|
||||
for (int i = 0; i < STATUSBAR_ICON_LIMIT; ++i) {
|
||||
update_icon(statusbar->icons[i], &(statusbar_data.icons[i]));
|
||||
@ -150,8 +229,6 @@ static void statusbar_event(TT_UNUSED const lv_obj_class_t* class_p, lv_event_t*
|
||||
|
||||
if (code == LV_EVENT_VALUE_CHANGED) {
|
||||
lv_obj_invalidate(obj);
|
||||
} else if (code == LV_EVENT_DRAW_MAIN) {
|
||||
// NO-OP
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
#include "Tactility.h"
|
||||
#include "service/gui/Gui_i.h"
|
||||
#include "service/loader/Loader_i.h"
|
||||
#include "lvgl/LvglKeypad.h"
|
||||
#include "lvgl/LvglSync.h"
|
||||
#include "RtosCompat.h"
|
||||
#include "lvgl/Style.h"
|
||||
#include "lvgl/Statusbar.h"
|
||||
|
||||
namespace tt::service::gui {
|
||||
|
||||
@ -11,11 +12,11 @@ namespace tt::service::gui {
|
||||
|
||||
// Forward declarations
|
||||
void redraw(Gui*);
|
||||
static int32_t gui_main(void*);
|
||||
static int32_t guiMain(TT_UNUSED void* p);
|
||||
|
||||
Gui* gui = nullptr;
|
||||
|
||||
void loader_callback(const void* message, TT_UNUSED void* context) {
|
||||
void onLoaderMessage(const void* message, TT_UNUSED void* context) {
|
||||
auto* event = static_cast<const loader::LoaderEvent*>(message);
|
||||
if (event->type == loader::LoaderEventTypeApplicationShowing) {
|
||||
app::AppContext& app = event->app_showing.app;
|
||||
@ -32,13 +33,34 @@ Gui* gui_alloc() {
|
||||
instance->thread = new Thread(
|
||||
"gui",
|
||||
4096, // Last known minimum was 2800 for launching desktop
|
||||
&gui_main,
|
||||
&guiMain,
|
||||
nullptr
|
||||
);
|
||||
instance->loader_pubsub_subscription = tt_pubsub_subscribe(loader::getPubsub(), &loader_callback, instance);
|
||||
instance->loader_pubsub_subscription = tt_pubsub_subscribe(loader::getPubsub(), &onLoaderMessage, instance);
|
||||
tt_check(lvgl::lock(1000 / portTICK_PERIOD_MS));
|
||||
instance->keyboard_group = lv_group_create();
|
||||
instance->lvgl_parent = lv_scr_act();
|
||||
instance->keyboardGroup = lv_group_create();
|
||||
auto* screen_root = lv_scr_act();
|
||||
|
||||
lvgl::obj_set_style_bg_blacken(screen_root);
|
||||
|
||||
lv_obj_t* vertical_container = lv_obj_create(screen_root);
|
||||
lv_obj_set_size(vertical_container, LV_PCT(100), LV_PCT(100));
|
||||
lv_obj_set_flex_flow(vertical_container, LV_FLEX_FLOW_COLUMN);
|
||||
lvgl::obj_set_style_no_padding(vertical_container);
|
||||
lvgl::obj_set_style_bg_blacken(vertical_container);
|
||||
|
||||
instance->statusbarWidget = lvgl::statusbar_create(vertical_container);
|
||||
|
||||
auto* app_container = lv_obj_create(vertical_container);
|
||||
lvgl::obj_set_style_no_padding(app_container);
|
||||
lv_obj_set_style_border_width(app_container, 0, 0);
|
||||
lvgl::obj_set_style_bg_blacken(app_container);
|
||||
lv_obj_set_width(app_container, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(app_container, 1);
|
||||
lv_obj_set_flex_flow(app_container, LV_FLEX_FLOW_COLUMN);
|
||||
|
||||
instance->appRootWidget = app_container;
|
||||
|
||||
lvgl::unlock();
|
||||
|
||||
return instance;
|
||||
@ -48,9 +70,9 @@ void gui_free(Gui* instance) {
|
||||
tt_assert(instance != nullptr);
|
||||
delete instance->thread;
|
||||
|
||||
lv_group_delete(instance->keyboard_group);
|
||||
lv_group_delete(instance->keyboardGroup);
|
||||
tt_check(lvgl::lock(1000 / portTICK_PERIOD_MS));
|
||||
lv_group_del(instance->keyboard_group);
|
||||
lv_group_del(instance->keyboardGroup);
|
||||
lvgl::unlock();
|
||||
|
||||
delete instance;
|
||||
@ -74,15 +96,15 @@ void requestDraw() {
|
||||
|
||||
void showApp(app::AppContext& app, ViewPortShowCallback on_show, ViewPortHideCallback on_hide) {
|
||||
lock();
|
||||
tt_check(gui->app_view_port == nullptr);
|
||||
gui->app_view_port = view_port_alloc(app, on_show, on_hide);
|
||||
tt_check(gui->appViewPort == nullptr);
|
||||
gui->appViewPort = view_port_alloc(app, on_show, on_hide);
|
||||
unlock();
|
||||
requestDraw();
|
||||
}
|
||||
|
||||
void hideApp() {
|
||||
lock();
|
||||
ViewPort* view_port = gui->app_view_port;
|
||||
ViewPort* view_port = gui->appViewPort;
|
||||
tt_check(view_port != nullptr);
|
||||
|
||||
// We must lock the LVGL port, because the viewport hide callbacks
|
||||
@ -92,11 +114,11 @@ void hideApp() {
|
||||
lvgl::unlock();
|
||||
|
||||
view_port_free(view_port);
|
||||
gui->app_view_port = nullptr;
|
||||
gui->appViewPort = nullptr;
|
||||
unlock();
|
||||
}
|
||||
|
||||
static int32_t gui_main(TT_UNUSED void* p) {
|
||||
static int32_t guiMain(TT_UNUSED void* p) {
|
||||
tt_check(gui);
|
||||
Gui* local_gui = gui;
|
||||
|
||||
|
||||
@ -9,27 +9,14 @@ namespace tt::service::gui {
|
||||
|
||||
#define TAG "gui"
|
||||
|
||||
static lv_obj_t* create_app_views(Gui* gui, lv_obj_t* parent, app::AppContext& app) {
|
||||
lvgl::obj_set_style_bg_blacken(parent);
|
||||
|
||||
lv_obj_t* vertical_container = lv_obj_create(parent);
|
||||
lv_obj_set_size(vertical_container, LV_PCT(100), LV_PCT(100));
|
||||
lv_obj_set_flex_flow(vertical_container, LV_FLEX_FLOW_COLUMN);
|
||||
lvgl::obj_set_style_no_padding(vertical_container);
|
||||
lvgl::obj_set_style_bg_blacken(vertical_container);
|
||||
|
||||
// TODO: Move statusbar into separate ViewPort
|
||||
app::Flags flags = app.getFlags();
|
||||
if (flags.showStatusbar) {
|
||||
lvgl::statusbar_create(vertical_container);
|
||||
}
|
||||
|
||||
lv_obj_t* child_container = lv_obj_create(vertical_container);
|
||||
static lv_obj_t* createAppViews(Gui* gui, lv_obj_t* parent, app::AppContext& app) {
|
||||
lv_obj_send_event(gui->statusbarWidget, LV_EVENT_DRAW_MAIN, nullptr);
|
||||
lv_obj_t* child_container = lv_obj_create(parent);
|
||||
lv_obj_set_width(child_container, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(child_container, 1);
|
||||
|
||||
if (keyboardIsEnabled()) {
|
||||
gui->keyboard = lv_keyboard_create(vertical_container);
|
||||
gui->keyboard = lv_keyboard_create(parent);
|
||||
lv_obj_add_flag(gui->keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
gui->keyboard = nullptr;
|
||||
@ -45,12 +32,20 @@ void redraw(Gui* gui) {
|
||||
lock();
|
||||
|
||||
if (lvgl::lock(1000)) {
|
||||
lv_obj_clean(gui->lvgl_parent);
|
||||
lv_obj_clean(gui->appRootWidget);
|
||||
|
||||
ViewPort* view_port = gui->app_view_port;
|
||||
ViewPort* view_port = gui->appViewPort;
|
||||
if (view_port != nullptr) {
|
||||
app::AppContext& app = view_port->app;
|
||||
lv_obj_t* container = create_app_views(gui, gui->lvgl_parent, app);
|
||||
|
||||
app::Flags flags = app.getFlags();
|
||||
if (flags.showStatusbar) {
|
||||
lv_obj_remove_flag(gui->statusbarWidget, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_add_flag(gui->statusbarWidget, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
lv_obj_t* container = createAppViews(gui, gui->appRootWidget, app);
|
||||
view_port_show(view_port, container);
|
||||
} else {
|
||||
TT_LOG_W(TAG, "nothing to draw");
|
||||
|
||||
@ -54,9 +54,9 @@ void keyboardAddTextArea(lv_obj_t* textarea) {
|
||||
}
|
||||
|
||||
// lv_obj_t auto-remove themselves from the group when they are destroyed (last checked in LVGL 8.3)
|
||||
lv_group_add_obj(gui->keyboard_group, textarea);
|
||||
lv_group_add_obj(gui->keyboardGroup, textarea);
|
||||
|
||||
lvgl::keypad_activate(gui->keyboard_group);
|
||||
lvgl::keypad_activate(gui->keyboardGroup);
|
||||
|
||||
lvgl::unlock();
|
||||
unlock();
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
|
||||
#else
|
||||
#include "lvgl/LvglSync.h"
|
||||
#include "TactilityHeadless.h"
|
||||
#endif
|
||||
|
||||
namespace tt::service::loader {
|
||||
@ -42,7 +41,7 @@ static void loader_free() {
|
||||
loader_singleton = nullptr;
|
||||
}
|
||||
|
||||
void startApp(const std::string& id, bool blocking, std::shared_ptr<const Bundle> parameters) {
|
||||
void startApp(const std::string& id, bool blocking, const std::shared_ptr<const Bundle>& parameters) {
|
||||
TT_LOG_I(TAG, "Start app %s", id.c_str());
|
||||
tt_assert(loader_singleton);
|
||||
|
||||
@ -67,7 +66,7 @@ void stopApp() {
|
||||
app::AppContext* _Nullable getCurrentApp() {
|
||||
tt_assert(loader_singleton);
|
||||
if (loader_singleton->mutex.lock(10 / portTICK_PERIOD_MS)) {
|
||||
app::AppInstance* app = loader_singleton->app_stack.top();
|
||||
app::AppInstance* app = loader_singleton->appStack.top();
|
||||
loader_singleton->mutex.unlock();
|
||||
return dynamic_cast<app::AppContext*>(app);
|
||||
} else {
|
||||
@ -80,7 +79,7 @@ std::shared_ptr<PubSub> getPubsub() {
|
||||
// it's safe to return pubsub without locking
|
||||
// because it's never freed and loader is never exited
|
||||
// also the loader instance cannot be obtained until the pubsub is created
|
||||
return loader_singleton->pubsub_external;
|
||||
return loader_singleton->pubsubExternal;
|
||||
}
|
||||
|
||||
static const char* appStateToString(app::State state) {
|
||||
@ -129,7 +128,7 @@ static void transitionAppToState(app::AppInstance& app, app::State state) {
|
||||
.app = app
|
||||
}
|
||||
};
|
||||
tt_pubsub_publish(loader_singleton->pubsub_external, &event_showing);
|
||||
tt_pubsub_publish(loader_singleton->pubsubExternal, &event_showing);
|
||||
app.setState(app::StateShowing);
|
||||
break;
|
||||
}
|
||||
@ -140,7 +139,7 @@ static void transitionAppToState(app::AppInstance& app, app::State state) {
|
||||
.app = app
|
||||
}
|
||||
};
|
||||
tt_pubsub_publish(loader_singleton->pubsub_external, &event_hiding);
|
||||
tt_pubsub_publish(loader_singleton->pubsubExternal, &event_hiding);
|
||||
app.setState(app::StateHiding);
|
||||
break;
|
||||
}
|
||||
@ -167,11 +166,11 @@ static LoaderStatus startAppWithManifestInternal(
|
||||
return LoaderStatusErrorInternal;
|
||||
}
|
||||
|
||||
auto previous_app = !loader_singleton->app_stack.empty() ? loader_singleton->app_stack.top() : nullptr;
|
||||
auto previous_app = !loader_singleton->appStack.empty() ? loader_singleton->appStack.top() : nullptr;
|
||||
auto new_app = new app::AppInstance(*manifest, parameters);
|
||||
new_app->mutableFlags().showStatusbar = (manifest->type != app::TypeBoot);
|
||||
|
||||
loader_singleton->app_stack.push(new_app);
|
||||
loader_singleton->appStack.push(new_app);
|
||||
transitionAppToState(*new_app, app::StateInitial);
|
||||
transitionAppToState(*new_app, app::StateStarted);
|
||||
|
||||
@ -183,7 +182,7 @@ static LoaderStatus startAppWithManifestInternal(
|
||||
transitionAppToState(*new_app, app::StateShowing);
|
||||
|
||||
LoaderEventInternal event_internal = {.type = LoaderEventTypeApplicationStarted};
|
||||
tt_pubsub_publish(loader_singleton->pubsub_internal, &event_internal);
|
||||
tt_pubsub_publish(loader_singleton->pubsubInternal, &event_internal);
|
||||
|
||||
LoaderEvent event_external = {
|
||||
.type = LoaderEventTypeApplicationStarted,
|
||||
@ -191,7 +190,7 @@ static LoaderStatus startAppWithManifestInternal(
|
||||
.app = *new_app
|
||||
}
|
||||
};
|
||||
tt_pubsub_publish(loader_singleton->pubsub_external, &event_external);
|
||||
tt_pubsub_publish(loader_singleton->pubsubExternal, &event_external);
|
||||
|
||||
return LoaderStatusOk;
|
||||
}
|
||||
@ -228,7 +227,7 @@ static void stopAppInternal() {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t original_stack_size = loader_singleton->app_stack.size();
|
||||
size_t original_stack_size = loader_singleton->appStack.size();
|
||||
|
||||
if (original_stack_size == 0) {
|
||||
TT_LOG_E(TAG, "Stop app: no app running");
|
||||
@ -236,7 +235,7 @@ static void stopAppInternal() {
|
||||
}
|
||||
|
||||
// Stop current app
|
||||
app::AppInstance* app_to_stop = loader_singleton->app_stack.top();
|
||||
app::AppInstance* app_to_stop = loader_singleton->appStack.top();
|
||||
|
||||
if (original_stack_size == 1 && app_to_stop->getManifest().type != app::TypeBoot) {
|
||||
TT_LOG_E(TAG, "Stop app: can't stop root app");
|
||||
@ -249,7 +248,7 @@ static void stopAppInternal() {
|
||||
transitionAppToState(*app_to_stop, app::StateHiding);
|
||||
transitionAppToState(*app_to_stop, app::StateStopped);
|
||||
|
||||
loader_singleton->app_stack.pop();
|
||||
loader_singleton->appStack.pop();
|
||||
delete app_to_stop;
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
@ -259,8 +258,8 @@ static void stopAppInternal() {
|
||||
app::AppOnResult on_result = nullptr;
|
||||
app::AppInstance* app_to_resume = nullptr;
|
||||
// If there's a previous app, resume it
|
||||
if (!loader_singleton->app_stack.empty()) {
|
||||
app_to_resume = loader_singleton->app_stack.top();
|
||||
if (!loader_singleton->appStack.empty()) {
|
||||
app_to_resume = loader_singleton->appStack.top();
|
||||
tt_assert(app_to_resume);
|
||||
transitionAppToState(*app_to_resume, app::StateShowing);
|
||||
|
||||
@ -272,7 +271,7 @@ static void stopAppInternal() {
|
||||
// WARNING: After this point we cannot change the app states from this method directly anymore as we don't have a lock!
|
||||
|
||||
LoaderEventInternal event_internal = {.type = LoaderEventTypeApplicationStopped};
|
||||
tt_pubsub_publish(loader_singleton->pubsub_internal, &event_internal);
|
||||
tt_pubsub_publish(loader_singleton->pubsubInternal, &event_internal);
|
||||
|
||||
LoaderEvent event_external = {
|
||||
.type = LoaderEventTypeApplicationStopped,
|
||||
@ -280,7 +279,7 @@ static void stopAppInternal() {
|
||||
.manifest = manifest
|
||||
}
|
||||
};
|
||||
tt_pubsub_publish(loader_singleton->pubsub_external, &event_external);
|
||||
tt_pubsub_publish(loader_singleton->pubsubExternal, &event_external);
|
||||
|
||||
if (on_result != nullptr && app_to_resume != nullptr) {
|
||||
if (result_holder != nullptr) {
|
||||
|
||||
@ -24,7 +24,7 @@ typedef enum {
|
||||
* @param[in] blocking whether this call is blocking or not. You cannot call this from an LVGL thread.
|
||||
* @param[in] parameters optional parameters to pass onto the application
|
||||
*/
|
||||
void startApp(const std::string& id, bool blocking = false, std::shared_ptr<const Bundle> _Nullable parameters = nullptr);
|
||||
void startApp(const std::string& id, bool blocking = false, const std::shared_ptr<const Bundle>& _Nullable parameters = nullptr);
|
||||
|
||||
/** @brief Stop the currently showing app. Show the previous app if any app was still running. */
|
||||
void stopApp();
|
||||
|
||||
@ -33,3 +33,5 @@
|
||||
* @param[in] lower lower bounds for x
|
||||
*/
|
||||
#define TT_CLAMP(x, upper, lower) (TT_MIN(upper, TT_MAX(x, lower)))
|
||||
|
||||
#define TT_STRINGIFY(x) #x
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <bits/stdc++.h>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -48,4 +49,22 @@ std::vector<std::string> split(const std::string& input, const std::string& deli
|
||||
*/
|
||||
std::string join(const std::vector<std::string>& input, const std::string& delimiter);
|
||||
|
||||
/**
|
||||
* Returns the lowercase value of a string.
|
||||
* @param[in] the string with lower and/or uppercase characters
|
||||
* @return a string with only lowercase characters
|
||||
*/
|
||||
template <typename T>
|
||||
std::basic_string<T> lowercase(const std::basic_string<T>& input) {
|
||||
std::basic_string<T> output = input;
|
||||
std::transform(
|
||||
output.begin(),
|
||||
output.end(),
|
||||
output.begin(),
|
||||
[](const T character) { return static_cast<T>(std::tolower(character)); }
|
||||
);
|
||||
return std::move(output);
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
#include <utility>
|
||||
#include "Check.h"
|
||||
#include "kernel/Kernel.h"
|
||||
#include "RtosCompat.h"
|
||||
|
||||
namespace tt {
|
||||
|
||||
@ -6,7 +6,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
if (DEFINED ENV{ESP_IDF_VERSION})
|
||||
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
||||
|
||||
list(APPEND REQUIRES_LIST TactilityCore esp_wifi nvs_flash driver spiffs vfs fatfs)
|
||||
list(APPEND REQUIRES_LIST TactilityCore esp_wifi nvs_flash driver spiffs vfs fatfs lwip)
|
||||
if("${IDF_TARGET}" STREQUAL "esp32s3")
|
||||
list(APPEND REQUIRES_LIST esp_tinyusb)
|
||||
endif()
|
||||
|
||||
7
TactilityHeadless/Private/network/NtpPrivate.h
Normal file
7
TactilityHeadless/Private/network/NtpPrivate.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace tt::network::ntp {
|
||||
|
||||
void init();
|
||||
|
||||
}
|
||||
7
TactilityHeadless/Private/time/TimePrivate.h
Normal file
7
TactilityHeadless/Private/time/TimePrivate.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace tt::time {
|
||||
|
||||
void init();
|
||||
|
||||
}
|
||||
@ -14,3 +14,4 @@
|
||||
#define TT_ASSETS_APP_ICON_I2C_SETTINGS TT_ASSET("app_icon_i2c.png")
|
||||
#define TT_ASSETS_APP_ICON_SETTINGS TT_ASSET("app_icon_settings.png")
|
||||
#define TT_ASSETS_APP_ICON_SYSTEM_INFO TT_ASSET("app_icon_system_info.png")
|
||||
#define TT_ASSETS_APP_ICON_TIME_DATE_SETTINGS TT_ASSET("app_icon_time_date_settings.png")
|
||||
|
||||
@ -4,6 +4,9 @@
|
||||
#include "hal/Hal_i.h"
|
||||
#include "service/ServiceManifest.h"
|
||||
#include "service/ServiceRegistry.h"
|
||||
#include "kernel/SystemEvents.h"
|
||||
#include "network/NtpPrivate.h"
|
||||
#include "time/TimePrivate.h"
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "EspInit.h"
|
||||
@ -39,7 +42,9 @@ void initHeadless(const hal::Configuration& config) {
|
||||
initEsp();
|
||||
#endif
|
||||
hardwareConfig = &config;
|
||||
time::init();
|
||||
hal::init(config);
|
||||
network::ntp::init();
|
||||
register_and_start_system_services();
|
||||
}
|
||||
|
||||
|
||||
@ -10,8 +10,6 @@ typedef bool (*InitBoot)();
|
||||
typedef bool (*InitHardware)();
|
||||
typedef bool (*InitLvgl)();
|
||||
|
||||
typedef void (*SetBacklightDuty)(uint8_t);
|
||||
|
||||
class Display;
|
||||
class Keyboard;
|
||||
typedef Display* (*CreateDisplay)();
|
||||
@ -57,7 +55,7 @@ struct Configuration {
|
||||
const CreatePower _Nullable power = nullptr;
|
||||
|
||||
/**
|
||||
* A list of i2c devices (can be empty, but preferably accurately represents the device capabilities)
|
||||
* A list of i2c interfaces
|
||||
*/
|
||||
const std::vector<i2c::Configuration> i2c = {};
|
||||
};
|
||||
|
||||
@ -1,16 +1,21 @@
|
||||
#include "hal/Hal_i.h"
|
||||
#include "hal/i2c/I2c.h"
|
||||
#include "kernel/SystemEvents.h"
|
||||
|
||||
#define TAG "hal"
|
||||
|
||||
namespace tt::hal {
|
||||
|
||||
void init(const Configuration& configuration) {
|
||||
kernel::systemEventPublish(kernel::SystemEvent::BootInitHalBegin);
|
||||
|
||||
kernel::systemEventPublish(kernel::SystemEvent::BootInitI2cBegin);
|
||||
tt_check(i2c::init(configuration.i2c), "I2C init failed");
|
||||
if (configuration.initHardware != nullptr) {
|
||||
TT_LOG_I(TAG, "Init hardware");
|
||||
tt_check(configuration.initHardware(), "Hardware init failed");
|
||||
}
|
||||
kernel::systemEventPublish(kernel::SystemEvent::BootInitI2cEnd);
|
||||
|
||||
if (configuration.initBoot != nullptr) {
|
||||
TT_LOG_I(TAG, "Init power");
|
||||
@ -23,6 +28,8 @@ void init(const Configuration& configuration) {
|
||||
TT_LOG_W(TAG, "SD card mount failed (init can continue)");
|
||||
}
|
||||
}
|
||||
|
||||
kernel::systemEventPublish(kernel::SystemEvent::BootInitHalEnd);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
87
TactilityHeadless/Source/kernel/SystemEvents.cpp
Normal file
87
TactilityHeadless/Source/kernel/SystemEvents.cpp
Normal file
@ -0,0 +1,87 @@
|
||||
#include "SystemEvents.h"
|
||||
#include "Mutex.h"
|
||||
#include "CoreExtraDefines.h"
|
||||
#include <list>
|
||||
|
||||
#define TAG "system_event"
|
||||
|
||||
namespace tt::kernel {
|
||||
|
||||
struct SubscriptionData {
|
||||
SystemEventSubscription id;
|
||||
SystemEvent event;
|
||||
OnSystemEvent handler;
|
||||
};
|
||||
|
||||
static Mutex mutex;
|
||||
static SystemEventSubscription subscriptionCounter = 0;
|
||||
static std::list<SubscriptionData> subscriptions;
|
||||
|
||||
static const char* getEventName(SystemEvent event) {
|
||||
switch (event) {
|
||||
case SystemEvent::BootInitHalBegin:
|
||||
return TT_STRINGIFY(SystemEvent::BootInitHalBegin);
|
||||
case SystemEvent::BootInitHalEnd:
|
||||
return TT_STRINGIFY(SystemEvent::BootInitHalEnd);
|
||||
case SystemEvent::BootInitI2cBegin:
|
||||
return TT_STRINGIFY(SystemEvent::BootInitI2cBegin);
|
||||
case SystemEvent::BootInitI2cEnd:
|
||||
return TT_STRINGIFY(SystemEvent::BootInitI2cEnd);
|
||||
case SystemEvent::BootInitLvglBegin:
|
||||
return TT_STRINGIFY(SystemEvent::BootInitLvglBegin);
|
||||
case SystemEvent::BootInitLvglEnd:
|
||||
return TT_STRINGIFY(SystemEvent::BootInitLvglEnd);
|
||||
case SystemEvent::BootSplash:
|
||||
return TT_STRINGIFY(SystemEvent::BootSplash);
|
||||
case SystemEvent::NetworkConnected:
|
||||
return TT_STRINGIFY(SystemEvent::NetworkConnected);
|
||||
case SystemEvent::NetworkDisconnected:
|
||||
return TT_STRINGIFY(SystemEvent::NetworkDisconnected);
|
||||
case SystemEvent::Time:
|
||||
return TT_STRINGIFY(SystemEvent::Time);
|
||||
}
|
||||
|
||||
tt_crash(); // Missing case above
|
||||
}
|
||||
|
||||
void systemEventPublish(SystemEvent event) {
|
||||
TT_LOG_I(TAG, "%s", getEventName(event));
|
||||
|
||||
if (mutex.lock(portMAX_DELAY)) {
|
||||
for (auto& subscription : subscriptions) {
|
||||
if (subscription.event == event) {
|
||||
subscription.handler(event);
|
||||
}
|
||||
}
|
||||
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
SystemEventSubscription systemEventAddListener(SystemEvent event, OnSystemEvent handler) {
|
||||
if (mutex.lock(portMAX_DELAY)) {
|
||||
auto id = ++subscriptionCounter;
|
||||
|
||||
subscriptions.push_back({
|
||||
.id = id,
|
||||
.event = event,
|
||||
.handler = handler
|
||||
});
|
||||
|
||||
mutex.unlock();
|
||||
return id;
|
||||
} else {
|
||||
tt_crash();
|
||||
}
|
||||
}
|
||||
|
||||
void systemEventRemoveListener(SystemEventSubscription subscription) {
|
||||
if (mutex.lock(portMAX_DELAY)) {
|
||||
std::erase_if(subscriptions, [subscription](auto& item) {
|
||||
return (item.id == subscription);
|
||||
});
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
31
TactilityHeadless/Source/kernel/SystemEvents.h
Normal file
31
TactilityHeadless/Source/kernel/SystemEvents.h
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace tt::kernel {
|
||||
|
||||
enum class SystemEvent {
|
||||
BootInitHalBegin,
|
||||
BootInitHalEnd,
|
||||
BootInitI2cBegin,
|
||||
BootInitI2cEnd,
|
||||
BootInitLvglBegin,
|
||||
BootInitLvglEnd,
|
||||
BootSplash,
|
||||
/** Gained IP address */
|
||||
NetworkConnected,
|
||||
NetworkDisconnected,
|
||||
/** An important system time-related event, such as NTP update or time-zone change */
|
||||
Time,
|
||||
};
|
||||
|
||||
/** Value 0 mean "no subscription" */
|
||||
typedef uint32_t SystemEventSubscription;
|
||||
|
||||
typedef void (*OnSystemEvent)(SystemEvent event);
|
||||
|
||||
void systemEventPublish(SystemEvent event);
|
||||
SystemEventSubscription systemEventAddListener(SystemEvent event, OnSystemEvent handler);
|
||||
void systemEventRemoveListener(SystemEventSubscription subscription);
|
||||
|
||||
}
|
||||
34
TactilityHeadless/Source/network/Ntp.cpp
Normal file
34
TactilityHeadless/Source/network/Ntp.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
#include "network/NtpPrivate.h"
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "kernel/SystemEvents.h"
|
||||
#include "TactilityCore.h"
|
||||
#include <esp_netif_sntp.h>
|
||||
#include <esp_sntp.h>
|
||||
#endif
|
||||
|
||||
#define TAG "ntp"
|
||||
|
||||
namespace tt::network::ntp {
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
static void onTimeSynced(struct timeval* tv) {
|
||||
TT_LOG_I(TAG, "Time synced (%llu)", tv->tv_sec);
|
||||
kernel::systemEventPublish(kernel::SystemEvent::Time);
|
||||
}
|
||||
|
||||
void init() {
|
||||
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org");
|
||||
config.sync_cb = onTimeSynced;
|
||||
esp_netif_sntp_init(&config);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void init() {
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
101
TactilityHeadless/Source/time/Time.cpp
Normal file
101
TactilityHeadless/Source/time/Time.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
#include <ctime>
|
||||
#include "Time.h"
|
||||
#include "Preferences.h"
|
||||
#include "kernel/SystemEvents.h"
|
||||
|
||||
namespace tt::time {
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#define TIME_SETTINGS_NAMESPACE "time"
|
||||
|
||||
#define TIMEZONE_PREFERENCES_KEY_NAME "tz_name"
|
||||
#define TIMEZONE_PREFERENCES_KEY_CODE "tz_code"
|
||||
#define TIMEZONE_PREFERENCES_KEY_TIME24 "tz_time24"
|
||||
|
||||
void init() {
|
||||
auto code= getTimeZoneCode();
|
||||
if (!code.empty()) {
|
||||
setenv("TZ", code.c_str(), 1);
|
||||
tzset();
|
||||
}
|
||||
}
|
||||
|
||||
void setTimeZone(const std::string& name, const std::string& code) {
|
||||
Preferences preferences(TIME_SETTINGS_NAMESPACE);
|
||||
preferences.putString(TIMEZONE_PREFERENCES_KEY_NAME, name);
|
||||
preferences.putString(TIMEZONE_PREFERENCES_KEY_CODE, code);
|
||||
|
||||
setenv("TZ", code.c_str(), 1);
|
||||
tzset();
|
||||
|
||||
kernel::systemEventPublish(kernel::SystemEvent::Time);
|
||||
}
|
||||
|
||||
std::string getTimeZoneName() {
|
||||
Preferences preferences(TIME_SETTINGS_NAMESPACE);
|
||||
std::string result;
|
||||
if (preferences.optString(TIMEZONE_PREFERENCES_KEY_NAME, result)) {
|
||||
return result;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::string getTimeZoneCode() {
|
||||
Preferences preferences(TIME_SETTINGS_NAMESPACE);
|
||||
std::string result;
|
||||
if (preferences.optString(TIMEZONE_PREFERENCES_KEY_CODE, result)) {
|
||||
return result;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool isTimeFormat24Hour() {
|
||||
Preferences preferences(TIME_SETTINGS_NAMESPACE);
|
||||
bool show24Hour = true;
|
||||
preferences.optBool(TIMEZONE_PREFERENCES_KEY_TIME24, show24Hour);
|
||||
return show24Hour;
|
||||
}
|
||||
|
||||
void setTimeFormat24Hour(bool show24Hour) {
|
||||
Preferences preferences(TIME_SETTINGS_NAMESPACE);
|
||||
preferences.putBool(TIMEZONE_PREFERENCES_KEY_TIME24, show24Hour);
|
||||
kernel::systemEventPublish(kernel::SystemEvent::Time);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static std::string timeZoneName;
|
||||
static std::string timeZoneCode;
|
||||
static bool show24Hour = true;
|
||||
|
||||
void init() {}
|
||||
|
||||
void setTimeZone(const std::string& name, const std::string& code) {
|
||||
timeZoneName = name;
|
||||
timeZoneCode = code;
|
||||
kernel::systemEventPublish(kernel::SystemEvent::Time);
|
||||
}
|
||||
|
||||
std::string getTimeZoneName() {
|
||||
return timeZoneName;
|
||||
}
|
||||
|
||||
std::string getTimeZoneCode() {
|
||||
return timeZoneCode;
|
||||
}
|
||||
|
||||
bool isTimeFormat24Hour() {
|
||||
return show24Hour;
|
||||
}
|
||||
|
||||
void setTimeFormat24Hour(bool enabled) {
|
||||
show24Hour = enabled;
|
||||
kernel::systemEventPublish(kernel::SystemEvent::Time);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
32
TactilityHeadless/Source/time/Time.h
Normal file
32
TactilityHeadless/Source/time/Time.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tt::time {
|
||||
|
||||
/**
|
||||
* Set the timezone
|
||||
* @param[in] name human-readable name
|
||||
* @param[in] code the technical code (from timezones.csv)
|
||||
*/
|
||||
void setTimeZone(const std::string& name, const std::string& code);
|
||||
|
||||
/**
|
||||
* Get the name of the timezone
|
||||
*/
|
||||
std::string getTimeZoneName();
|
||||
|
||||
/**
|
||||
* Get the code of the timezone (see timezones.csv)
|
||||
*/
|
||||
std::string getTimeZoneCode();
|
||||
|
||||
/** @return true when clocks should be shown as a 24 hours one instead of 12 hours */
|
||||
bool isTimeFormat24Hour();
|
||||
|
||||
/** Set whether clocks should be shown as a 24 hours instead of 12 hours
|
||||
* @param[in] show24Hour
|
||||
*/
|
||||
void setTimeFormat24Hour(bool show24Hour);
|
||||
|
||||
}
|
||||
@ -29,6 +29,7 @@ CONFIG_FATFS_LFN_HEAP=y
|
||||
CONFIG_FATFS_VOLUME_COUNT=3
|
||||
|
||||
# Hardware: Main
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||
CONFIG_TT_BOARD_LILYGO_TDECK=y
|
||||
CONFIG_IDF_TARGET="esp32s3"
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
|
||||
@ -38,9 +39,11 @@ CONFIG_FLASHMODE_QIO=y
|
||||
# Hardware: SPI RAM
|
||||
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
|
||||
CONFIG_SPIRAM_MODE_OCT=y
|
||||
CONFIG_SPIRAM_SPEED_80M=y
|
||||
CONFIG_SPIRAM_SPEED_120M=y
|
||||
CONFIG_SPIRAM_USE_MALLOC=y
|
||||
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
|
||||
# SPI Flash (can set back to 80MHz after ESP-IDF bug is resolved)
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ_120M=y
|
||||
# LVGL
|
||||
CONFIG_LV_DISP_DEF_REFR_PERIOD=17
|
||||
CONFIG_LV_INDEV_DEF_READ_PERIOD=17
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user