Compare commits

..

5 Commits

Author SHA1 Message Date
Adolfo Reyna c937bb3177 fix(mcp): implement startMcpScreensaver - missing method caused linker error
- Was declared in DisplayIdleService.h but not defined in clean refactored branch
- Implementation taken from original 8970143f: locks LVGL, checks overlay, ensures backlight, creates McpScreensaver
- Fixes build of feature/local-all-rebased on es3c28p
2026-07-21 12:01:27 -04:00
Adolfo Reyna 28a277184a feat(app): allow external apps to hide statusbar via manifest flags
- Add parseAppFlagsString() in AppManifestParsing.cpp to parse comma-separated flags (HideStatusBar, Hidden, None)
- Parse [app]flags in V1 and app.flags in V2 manifests
- Loader already supports HideStatusBar, GuiService hides statusbarWidget when set
- Enables fullscreen for ELF apps (e.g. BibleVerse, BookPlayer) without firmware recompilation of internal manifests
- Add tests for flag parsing (38 tests passing)
- Tested on es3c28p /dev/cu.usbmodem1101 @ 192.168.68.133 with HelloWorld app
2026-07-21 11:47:44 -04:00
Adolfo Reyna 69e66d4e79 fix(audio): ES8311 BOTH complementary open + audio-stream close ref-count + mic unmute
- es8311 driver open() now allows OUTPUT->INPUT complementary (promotes to BOTH) when same native rate 44100
- audio-stream close_stream() ref-counts shared BOTH codec (don't close if other direction still open)
- MCP recordVoice + VoiceRecorder app explicitly unmute and set 100% gain

Fixes mic input not working - was returning ERROR_RESOURCE when output already open
2026-07-21 11:47:44 -04:00
Adolfo Reyna 78d6bc0bf7 fix(files): launch mp3 player from file explorer - audio support
- SupportedFiles: add isSupportedAudioFile (.mp3/.wav/.ogg/.flac)
- Files View::viewFile: if audio file, loader->start one.tactility.mp3player with file bundle
- Tactility.cpp: hide mcpoverride (screensaver internal), keep mcpsettings visible
- Fixes MP3 no longer running from file explorer, plus audio fast playback already fixed via audio-stream (Mp3Player/BookPlayer/VoiceRecorder use audio_stream_open_output not direct i2s_controller_write)
- MCP dev coexistence already fixed: DisplayIdle lock inversion 3629ffef, WebServer serverEnabled=ws||mcp, unauth /api/mcp, video 8081/8083 vs dev 6666
2026-07-21 11:47:44 -04:00
Adolfo Reyna a158fe1696 feat(mcp): restore MCP service with display charging toggle
- Restore MCP system (McpSystem 1900+ LOC, McpHandler, McpScreensaver, McpSettings, McpOverride apps)
- Use audio-stream instead of direct I2S for MP3 playback (fixes fast playback, uses native resampler)
- Register McpSettings + McpOverride in Tactility.cpp so Settings shows MCP Screen
- DisplaySettings: add ScreensaverType::McpScreen and disableScreensaverWhenCharging flag
- Display app: add 'Disable on charging' toggle, handles McpScreen screensaver type
- DisplayIdle: skip screensaver while charging, support McpScreen screensaver, isDeviceCharging check via PowerDevice
- WebServer: coexistence with MCP (McpHandler)
- Avoid formatting churn from previous commits

Squashed from range eaa248b0..8970143f (6 commits) into single clean commit
2026-07-21 11:47:38 -04:00
5 changed files with 178 additions and 126 deletions
@@ -1,33 +1,46 @@
#pragma once #pragma once
#include <src/display/lv_display.h> #include <src/display/lv_display.h>
namespace tt::settings::display { namespace tt::settings::display {
enum class Orientation { enum class Orientation {
// In order of rotation (to make it easier to convert to LVGL rotation)
Landscape, Landscape,
Portrait, Portrait,
LandscapeFlipped, LandscapeFlipped,
PortraitFlipped, PortraitFlipped,
}; };
enum class ScreensaverType { enum class ScreensaverType {
None, None, // Just black screen
BouncingBalls, BouncingBalls,
Mystify, Mystify,
MatrixRain, MatrixRain,
StackChan, StackChan,
McpScreen, McpScreen,
Count Count // Sentinel for bounds checking - must be last
}; };
struct DisplaySettings { struct DisplaySettings {
Orientation orientation; Orientation orientation;
uint8_t gammaCurve; uint8_t gammaCurve;
uint8_t backlightDuty; uint8_t backlightDuty;
bool backlightTimeoutEnabled; bool backlightTimeoutEnabled;
uint32_t backlightTimeoutMs; uint32_t backlightTimeoutMs; // 0 = Never
ScreensaverType screensaverType = ScreensaverType::BouncingBalls; ScreensaverType screensaverType = ScreensaverType::BouncingBalls;
bool disableScreensaverWhenCharging = false; bool disableScreensaverWhenCharging = false;
}; };
/** Compares default settings with the function parameter to return the difference */
lv_display_rotation_t toLvglDisplayRotation(Orientation orientation); lv_display_rotation_t toLvglDisplayRotation(Orientation orientation);
bool load(DisplaySettings& settings); bool load(DisplaySettings& settings);
DisplaySettings loadOrGetDefault(); DisplaySettings loadOrGetDefault();
DisplaySettings getDefault(); DisplaySettings getDefault();
bool save(const DisplaySettings& settings); bool save(const DisplaySettings& settings);
}
} // namespace
+1
View File
@@ -152,6 +152,7 @@ namespace app {
namespace crashdiagnostics { extern const AppManifest manifest; } namespace crashdiagnostics { extern const AppManifest manifest; }
namespace webserversettings { extern const AppManifest manifest; } namespace webserversettings { extern const AppManifest manifest; }
namespace mcpsettings { extern const AppManifest manifest; } namespace mcpsettings { extern const AppManifest manifest; }
namespace mcpoverride { extern const AppManifest manifest; }
#if CONFIG_TT_TDECK_WORKAROUND == 1 #if CONFIG_TT_TDECK_WORKAROUND == 1
namespace keyboardsettings { extern const AppManifest manifest; } // T-Deck only for now namespace keyboardsettings { extern const AppManifest manifest; } // T-Deck only for now
namespace trackballsettings { extern const AppManifest manifest; } // T-Deck only for now namespace trackballsettings { extern const AppManifest manifest; } // T-Deck only for now
+4 -1
View File
@@ -287,7 +287,7 @@ public:
screensaverDropdown = lv_dropdown_create(screensaver_wrapper); screensaverDropdown = lv_dropdown_create(screensaver_wrapper);
// Note: order correlates with settings::display::ScreensaverType enum order // Note: order correlates with settings::display::ScreensaverType enum order
lv_dropdown_set_options(screensaverDropdown, "None\nBouncing Balls\nMystify\nMatrix Rain\nStackChan"); lv_dropdown_set_options(screensaverDropdown, "None\nBouncing Balls\nMystify\nMatrix Rain\nStackChan\nMcpScreen");
lv_obj_align(screensaverDropdown, LV_ALIGN_RIGHT_MID, 0, 0); lv_obj_align(screensaverDropdown, LV_ALIGN_RIGHT_MID, 0, 0);
lv_obj_add_event_cb(screensaverDropdown, onScreensaverChanged, LV_EVENT_VALUE_CHANGED, this); lv_obj_add_event_cb(screensaverDropdown, onScreensaverChanged, LV_EVENT_VALUE_CHANGED, this);
lv_dropdown_set_selected(screensaverDropdown, static_cast<uint16_t>(displaySettings.screensaverType)); lv_dropdown_set_selected(screensaverDropdown, static_cast<uint16_t>(displaySettings.screensaverType));
@@ -295,13 +295,16 @@ public:
lv_obj_add_state(screensaverDropdown, LV_STATE_DISABLED); lv_obj_add_state(screensaverDropdown, LV_STATE_DISABLED);
} }
// Disable screensaver when charging toggle
disableWhenChargingWrapper = lv_obj_create(main_wrapper); disableWhenChargingWrapper = lv_obj_create(main_wrapper);
lv_obj_set_size(disableWhenChargingWrapper, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_size(disableWhenChargingWrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(disableWhenChargingWrapper, 0, LV_STATE_DEFAULT); lv_obj_set_style_pad_all(disableWhenChargingWrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(disableWhenChargingWrapper, 0, LV_STATE_DEFAULT); lv_obj_set_style_border_width(disableWhenChargingWrapper, 0, LV_STATE_DEFAULT);
auto* charging_label = lv_label_create(disableWhenChargingWrapper); auto* charging_label = lv_label_create(disableWhenChargingWrapper);
lv_label_set_text(charging_label, "Disable on charging"); lv_label_set_text(charging_label, "Disable on charging");
lv_obj_align(charging_label, LV_ALIGN_LEFT_MID, 0, 0); lv_obj_align(charging_label, LV_ALIGN_LEFT_MID, 0, 0);
disableWhenChargingSwitch = lv_switch_create(disableWhenChargingWrapper); disableWhenChargingSwitch = lv_switch_create(disableWhenChargingWrapper);
if (displaySettings.disableScreensaverWhenCharging) { if (displaySettings.disableScreensaverWhenCharging) {
lv_obj_add_state(disableWhenChargingSwitch, LV_STATE_CHECKED); lv_obj_add_state(disableWhenChargingSwitch, LV_STATE_CHECKED);
@@ -1,5 +1,4 @@
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
#include <tactility/log.h>
#include <Tactility/service/displayidle/DisplayIdleService.h> #include <Tactility/service/displayidle/DisplayIdleService.h>
@@ -14,10 +13,10 @@
#include <Tactility/hal/display/DisplayDevice.h> #include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/power/PowerDevice.h> #include <Tactility/hal/power/PowerDevice.h>
#include <Tactility/lvgl/LvglSync.h> #include <Tactility/lvgl/LvglSync.h>
#include <Tactility/mcp/McpSystem.h>
#include <Tactility/service/ServiceContext.h> #include <Tactility/service/ServiceContext.h>
#include <Tactility/service/ServiceManifest.h> #include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h> #include <Tactility/service/ServiceRegistration.h>
#include <Tactility/mcp/McpSystem.h>
#include <cstdlib> #include <cstdlib>
#include <ctime> #include <ctime>
@@ -35,12 +34,12 @@ static bool isDeviceCharging() {
bool charging = false; bool charging = false;
hal::findDevices<hal::power::PowerDevice>(hal::Device::Type::Power, [&charging](const auto& power) { hal::findDevices<hal::power::PowerDevice>(hal::Device::Type::Power, [&charging](const auto& power) {
if (!power->supportsMetric(hal::power::PowerDevice::MetricType::IsCharging)) { if (!power->supportsMetric(hal::power::PowerDevice::MetricType::IsCharging)) {
return true; // continue return true;
} }
hal::power::PowerDevice::MetricData data; hal::power::PowerDevice::MetricData data;
if (power->getMetric(hal::power::PowerDevice::MetricType::IsCharging, data) && data.valueAsBool) { if (power->getMetric(hal::power::PowerDevice::MetricType::IsCharging, data) && data.valueAsBool) {
charging = true; charging = true;
return false; // stop iter return false;
} }
return true; return true;
}); });
@@ -49,10 +48,17 @@ static bool isDeviceCharging() {
void DisplayIdleService::stopScreensaverCb(lv_event_t* e) { void DisplayIdleService::stopScreensaverCb(lv_event_t* e) {
auto* self = static_cast<DisplayIdleService*>(lv_event_get_user_data(e)); auto* self = static_cast<DisplayIdleService*>(lv_event_get_user_data(e));
self->stopScreensaverLocked(); lv_event_stop_bubbling(e);
self->stopScreensaverRequested.store(true, std::memory_order_release);
lv_display_trigger_activity(nullptr);
} }
void DisplayIdleService::stopScreensaverLocked() { void DisplayIdleService::stopScreensaver() {
if (!lvgl::lock(100)) {
// Lock failed - keep flag set to retry on next tick
return;
}
const auto restoreDuty = cachedDisplaySettings.backlightDuty; const auto restoreDuty = cachedDisplaySettings.backlightDuty;
const bool wasDimmed = displayDimmed; const bool wasDimmed = displayDimmed;
@@ -64,6 +70,7 @@ void DisplayIdleService::stopScreensaverLocked() {
lv_obj_delete(screensaverOverlay); lv_obj_delete(screensaverOverlay);
screensaverOverlay = nullptr; screensaverOverlay = nullptr;
} }
lvgl::unlock();
stopScreensaverRequested.store(false, std::memory_order_relaxed); stopScreensaverRequested.store(false, std::memory_order_relaxed);
// Reset auto-off state // Reset auto-off state
@@ -78,15 +85,6 @@ void DisplayIdleService::stopScreensaverLocked() {
displayDimmed = wasDimmed ? false : displayDimmed; displayDimmed = wasDimmed ? false : displayDimmed;
} }
void DisplayIdleService::stopScreensaver() {
if (!lvgl::lock(100)) {
// Lock failed - keep flag set to retry on next tick
return;
}
stopScreensaverLocked();
lvgl::unlock();
}
void DisplayIdleService::activateScreensaver() { void DisplayIdleService::activateScreensaver() {
lv_obj_t* top = lv_layer_top(); lv_obj_t* top = lv_layer_top();
@@ -147,16 +145,6 @@ void DisplayIdleService::updateScreensaver() {
} }
void DisplayIdleService::tick() { void DisplayIdleService::tick() {
// Check if MCP override is active — must not be auto-stopped by idle logic
// READ OUTSIDE LVGL lock to avoid lock inversion:
// MCP video task locks mutex -> LVGL, we must NOT do LVGL -> mutex
bool isMcpActive = false;
{
auto& st = mcp::getState();
std::lock_guard<std::mutex> lk(st.mutex);
isMcpActive = (st.drawArea != nullptr) || st.overrideActive;
}
if (!lvgl::lock(100)) { if (!lvgl::lock(100)) {
return; return;
} }
@@ -171,10 +159,11 @@ void DisplayIdleService::tick() {
} }
uint32_t inactive_ms = 0; uint32_t inactive_ms = 0;
inactive_ms = lv_display_get_inactive_time(nullptr); inactive_ms = lv_display_get_inactive_time(nullptr);
// Only update if not stopping (prevents lag on touch) — skip for MCP (no animation) // Only update if not stopping (prevents lag on touch)
if (displayDimmed && screensaverOverlay && !stopScreensaverRequested.load(std::memory_order_acquire) && !isMcpActive) { if (displayDimmed && screensaverOverlay && !stopScreensaverRequested.load(std::memory_order_acquire)) {
// Check if screensaver should auto-off after 5 minutes // Check if screensaver should auto-off after 5 minutes
if (!backlightOff) { if (!backlightOff) {
screensaverActiveCounter++; screensaverActiveCounter++;
@@ -204,49 +193,35 @@ void DisplayIdleService::tick() {
} }
auto display = getDisplay(); auto display = getDisplay();
bool supportsBacklight = display != nullptr && display->supportsBacklightDuty(); if (display != nullptr && display->supportsBacklightDuty()) {
if (!cachedDisplaySettings.backlightTimeoutEnabled || cachedDisplaySettings.backlightTimeoutMs == 0) {
if (!cachedDisplaySettings.backlightTimeoutEnabled || cachedDisplaySettings.backlightTimeoutMs == 0) { if (displayDimmed) {
// Timeout disabled (Never): ensure we restore if we were dimmed, regardless of display type
if (displayDimmed && !isMcpActive) {
if (supportsBacklight && display != nullptr) {
display->setBacklightDuty(cachedDisplaySettings.backlightDuty); display->setBacklightDuty(cachedDisplaySettings.backlightDuty);
displayDimmed = false;
} }
displayDimmed = false; } else {
} bool charging_blocks = cachedDisplaySettings.disableScreensaverWhenCharging && isDeviceCharging();
} else if (supportsBacklight) { if (!displayDimmed && inactive_ms >= cachedDisplaySettings.backlightTimeoutMs) {
// For backlight-capable displays: full idle handling if (charging_blocks) {
bool charging_blocks = cachedDisplaySettings.disableScreensaverWhenCharging && isDeviceCharging(); // Skip screensaver while charging
} else {
if (!displayDimmed && inactive_ms >= cachedDisplaySettings.backlightTimeoutMs) { if (!lvgl::lock(100)) {
if (charging_blocks) { return; // Retry on next tick
// Skip screensaver while charging }
} else { activateScreensaver();
if (!lvgl::lock(100)) { lvgl::unlock();
return; // Retry on next tick // Turn off backlight for "None" screensaver (just black screen)
} if (cachedDisplaySettings.screensaverType == settings::display::ScreensaverType::None) {
activateScreensaver();
lvgl::unlock();
if (cachedDisplaySettings.screensaverType == settings::display::ScreensaverType::None) {
if (display != nullptr) {
display->setBacklightDuty(0); display->setBacklightDuty(0);
} }
displayDimmed = true;
}
} else if (displayDimmed) {
if (inactive_ms < kWakeActivityThresholdMs) {
stopScreensaver();
} else if (charging_blocks) {
stopScreensaver();
} }
displayDimmed = true;
}
} else if (displayDimmed && !isMcpActive) {
if (inactive_ms < kWakeActivityThresholdMs) {
stopScreensaver();
} else if (charging_blocks) {
stopScreensaver();
}
}
} else {
// For monochrome/RLCD (no backlight): don't auto-enter screensaver (heavy full_refresh SPI causes freeze)
// Only handle wake if we are somehow dimmed (e.g. MCP left it)
if (displayDimmed && !isMcpActive) {
if (inactive_ms < kWakeActivityThresholdMs) {
stopScreensaver();
} }
} }
} }
@@ -258,11 +233,6 @@ bool DisplayIdleService::onStart(ServiceContext& service) {
cachedDisplaySettings = settings::display::loadOrGetDefault(); cachedDisplaySettings = settings::display::loadOrGetDefault();
auto display = getDisplay();
if (display != nullptr && !display->supportsBacklightDuty()) {
LOG_I(TAG, "Monochrome/RLCD display detected (no backlight control): idle timer will run but auto-backlight off is disabled");
}
timer = std::make_unique<Timer>(Timer::Type::Periodic, kernel::millisToTicks(TICK_INTERVAL_MS), [this]{ this->tick(); }); timer = std::make_unique<Timer>(Timer::Type::Periodic, kernel::millisToTicks(TICK_INTERVAL_MS), [this]{ this->tick(); });
timer->setCallbackPriority(Thread::Priority::Lower); timer->setCallbackPriority(Thread::Priority::Lower);
timer->start(); timer->start();
@@ -314,11 +284,6 @@ bool DisplayIdleService::isScreensaverActive() const {
return screensaverOverlay != nullptr; return screensaverOverlay != nullptr;
} }
void DisplayIdleService::reloadSettings() {
// Set flag for thread-safe reload - actual reload happens in tick()
settingsReloadRequested.store(true, std::memory_order_release);
}
void DisplayIdleService::startMcpScreensaver() { void DisplayIdleService::startMcpScreensaver() {
if (!lvgl::lock(200)) { if (!lvgl::lock(200)) {
LOG_W(TAG, "startMcpScreensaver: failed to acquire LVGL lock"); LOG_W(TAG, "startMcpScreensaver: failed to acquire LVGL lock");
@@ -354,7 +319,6 @@ void DisplayIdleService::startMcpScreensaver() {
} }
lv_coord_t screenW = lv_display_get_horizontal_resolution(nullptr); lv_coord_t screenW = lv_display_get_horizontal_resolution(nullptr);
lv_coord_t screenH = lv_display_get_vertical_resolution(nullptr); lv_coord_t screenH = lv_display_get_vertical_resolution(nullptr);
lv_obj_t* top = lv_layer_top(); lv_obj_t* top = lv_layer_top();
@@ -375,6 +339,11 @@ void DisplayIdleService::startMcpScreensaver() {
LOG_I(TAG, "MCP screensaver activated"); LOG_I(TAG, "MCP screensaver activated");
} }
void DisplayIdleService::reloadSettings() {
// Set flag for thread-safe reload - actual reload happens in tick()
settingsReloadRequested.store(true, std::memory_order_release);
}
std::shared_ptr<DisplayIdleService> findService() { std::shared_ptr<DisplayIdleService> findService() {
return std::static_pointer_cast<DisplayIdleService>( return std::static_pointer_cast<DisplayIdleService>(
findServiceById("DisplayIdle") findServiceById("DisplayIdle")
+111 -45
View File
@@ -29,6 +29,7 @@ static Orientation getDefaultOrientation() {
if (display == nullptr) { if (display == nullptr) {
return Orientation::Landscape; return Orientation::Landscape;
} }
if (lv_display_get_physical_horizontal_resolution(display) > lv_display_get_physical_vertical_resolution(display)) { if (lv_display_get_physical_horizontal_resolution(display) > lv_display_get_physical_vertical_resolution(display)) {
return Orientation::Landscape; return Orientation::Landscape;
} else { } else {
@@ -39,51 +40,91 @@ static Orientation getDefaultOrientation() {
static std::string toString(Orientation orientation) { static std::string toString(Orientation orientation) {
switch (orientation) { switch (orientation) {
using enum Orientation; using enum Orientation;
case Portrait: return "Portrait"; case Portrait:
case Landscape: return "Landscape"; return "Portrait";
case PortraitFlipped: return "PortraitFlipped"; case Landscape:
case LandscapeFlipped: return "LandscapeFlipped"; return "Landscape";
default: std::unreachable(); case PortraitFlipped:
return "PortraitFlipped";
case LandscapeFlipped:
return "LandscapeFlipped";
default:
std::unreachable();
} }
} }
static bool fromString(const std::string& str, Orientation& orientation) { static bool fromString(const std::string& str, Orientation& orientation) {
if (str == "Portrait") { orientation = Orientation::Portrait; return true; } if (str == "Portrait") {
else if (str == "Landscape") { orientation = Orientation::Landscape; return true; } orientation = Orientation::Portrait;
else if (str == "PortraitFlipped") { orientation = Orientation::PortraitFlipped; return true; } return true;
else if (str == "LandscapeFlipped") { orientation = Orientation::LandscapeFlipped; return true; } } else if (str == "Landscape") {
else { return false; } orientation = Orientation::Landscape;
return true;
} else if (str == "PortraitFlipped") {
orientation = Orientation::PortraitFlipped;
return true;
} else if (str == "LandscapeFlipped") {
orientation = Orientation::LandscapeFlipped;
return true;
} else {
return false;
}
} }
static std::string toString(ScreensaverType type) { static std::string toString(ScreensaverType type) {
switch (type) { switch (type) {
using enum ScreensaverType; using enum ScreensaverType;
case None: return "None"; case None:
case BouncingBalls: return "BouncingBalls"; return "None";
case Mystify: return "Mystify"; case BouncingBalls:
case MatrixRain: return "MatrixRain"; return "BouncingBalls";
case StackChan: return "StackChan"; case Mystify:
case McpScreen: return "McpScreen"; return "Mystify";
case Count: return "None"; case MatrixRain:
default: std::unreachable(); return "MatrixRain";
case StackChan:
return "StackChan";
case McpScreen:
return "McpScreen";
default:
std::unreachable();
} }
} }
static bool fromString(const std::string& str, ScreensaverType& type) { static bool fromString(const std::string& str, ScreensaverType& type) {
if (str == "None") { type = ScreensaverType::None; return true; } if (str == "None") {
else if (str == "BouncingBalls") { type = ScreensaverType::BouncingBalls; return true; } type = ScreensaverType::None;
else if (str == "Mystify") { type = ScreensaverType::Mystify; return true; } return true;
else if (str == "MatrixRain") { type = ScreensaverType::MatrixRain; return true; } } else if (str == "BouncingBalls") {
else if (str == "StackChan") { type = ScreensaverType::StackChan; return true; } type = ScreensaverType::BouncingBalls;
else if (str == "McpScreen") { type = ScreensaverType::McpScreen; return true; } return true;
else { return false; } } else if (str == "Mystify") {
type = ScreensaverType::Mystify;
return true;
} else if (str == "MatrixRain") {
type = ScreensaverType::MatrixRain;
return true;
} else if (str == "StackChan") {
type = ScreensaverType::StackChan;
return true;
} else if (str == "McpScreen") {
type = ScreensaverType::McpScreen;
return true;
} else {
return false;
}
} }
bool load(DisplaySettings& settings) { bool load(DisplaySettings& settings) {
auto settings_path = getSettingsFilePath(); auto settings_path = getSettingsFilePath();
if (!file::isFile(settings_path)) { return false; } if (!file::isFile(settings_path)) {
return false;
}
std::map<std::string, std::string> map; std::map<std::string, std::string> map;
if (!file::loadPropertiesFile(settings_path, map)) { return false; } if (!file::loadPropertiesFile(settings_path, map)) {
return false;
}
auto orientation_entry = map.find(SETTINGS_KEY_ORIENTATION); auto orientation_entry = map.find(SETTINGS_KEY_ORIENTATION);
Orientation orientation; Orientation orientation;
@@ -93,13 +134,17 @@ bool load(DisplaySettings& settings) {
auto gamma_entry = map.find(SETTINGS_KEY_GAMMA_CURVE); auto gamma_entry = map.find(SETTINGS_KEY_GAMMA_CURVE);
int gamma_curve = 0; int gamma_curve = 0;
if (gamma_entry != map.end()) { gamma_curve = atoi(gamma_entry->second.c_str()); } if (gamma_entry != map.end()) {
gamma_curve = atoi(gamma_entry->second.c_str());
}
auto backlight_duty_entry = map.find(SETTINGS_KEY_BACKLIGHT_DUTY); auto backlight_duty_entry = map.find(SETTINGS_KEY_BACKLIGHT_DUTY);
int backlight_duty = 200; int backlight_duty = 200; // default
if (backlight_duty_entry != map.end()) { if (backlight_duty_entry != map.end()) {
backlight_duty = atoi(backlight_duty_entry->second.c_str()); backlight_duty = atoi(backlight_duty_entry->second.c_str());
if (backlight_duty_entry->second != "0" && backlight_duty == 0) backlight_duty = 200; if (backlight_duty_entry->second != "0" && backlight_duty == 0) {
backlight_duty = 200;
}
} }
bool timeout_enabled = false; bool timeout_enabled = false;
@@ -108,7 +153,7 @@ bool load(DisplaySettings& settings) {
timeout_enabled = (timeout_enabled_entry->second == "1" || timeout_enabled_entry->second == "true" || timeout_enabled_entry->second == "True"); timeout_enabled = (timeout_enabled_entry->second == "1" || timeout_enabled_entry->second == "true" || timeout_enabled_entry->second == "True");
} }
uint32_t timeout_ms = 60000; uint32_t timeout_ms = 60000; // default 60s
auto timeout_ms_entry = map.find(SETTINGS_KEY_TIMEOUT_MS); auto timeout_ms_entry = map.find(SETTINGS_KEY_TIMEOUT_MS);
if (timeout_ms_entry != map.end()) { if (timeout_ms_entry != map.end()) {
timeout_ms = static_cast<uint32_t>(std::strtoul(timeout_ms_entry->second.c_str(), nullptr, 10)); timeout_ms = static_cast<uint32_t>(std::strtoul(timeout_ms_entry->second.c_str(), nullptr, 10));
@@ -116,12 +161,14 @@ bool load(DisplaySettings& settings) {
auto screensaver_entry = map.find(SETTINGS_KEY_SCREENSAVER_TYPE); auto screensaver_entry = map.find(SETTINGS_KEY_SCREENSAVER_TYPE);
ScreensaverType screensaver_type = ScreensaverType::BouncingBalls; ScreensaverType screensaver_type = ScreensaverType::BouncingBalls;
if (screensaver_entry != map.end()) { fromString(screensaver_entry->second, screensaver_type); } if (screensaver_entry != map.end()) {
fromString(screensaver_entry->second, screensaver_type);
}
bool disable_when_charging = false; bool disable_when_charging = false;
auto charging_entry = map.find(SETTINGS_KEY_DISABLE_WHEN_CHARGING); auto charging_entry = map.find(SETTINGS_KEY_DISABLE_WHEN_CHARGING);
if (charging_entry != map.end()) { if (charging_entry != map.end()) {
disable_when_charging = (charging_entry->second == "1" || charging_entry->second == "true"); disable_when_charging = (charging_entry->second == "1" || charging_entry->second == "true" || charging_entry->second == "True");
} }
settings.orientation = orientation; settings.orientation = orientation;
@@ -131,6 +178,7 @@ bool load(DisplaySettings& settings) {
settings.backlightTimeoutMs = timeout_ms; settings.backlightTimeoutMs = timeout_ms;
settings.screensaverType = screensaver_type; settings.screensaverType = screensaver_type;
settings.disableScreensaverWhenCharging = disable_when_charging; settings.disableScreensaverWhenCharging = disable_when_charging;
return true; return true;
} }
@@ -148,7 +196,9 @@ DisplaySettings getDefault() {
DisplaySettings loadOrGetDefault() { DisplaySettings loadOrGetDefault() {
DisplaySettings settings; DisplaySettings settings;
if (!load(settings)) { settings = getDefault(); } if (!load(settings)) {
settings = getDefault();
}
return settings; return settings;
} }
@@ -162,7 +212,9 @@ bool save(const DisplaySettings& settings) {
map[SETTINGS_KEY_SCREENSAVER_TYPE] = toString(settings.screensaverType); map[SETTINGS_KEY_SCREENSAVER_TYPE] = toString(settings.screensaverType);
map[SETTINGS_KEY_DISABLE_WHEN_CHARGING] = settings.disableScreensaverWhenCharging ? "1" : "0"; map[SETTINGS_KEY_DISABLE_WHEN_CHARGING] = settings.disableScreensaverWhenCharging ? "1" : "0";
auto settings_path = getSettingsFilePath(); auto settings_path = getSettingsFilePath();
if (!file::findOrCreateParentDirectory(settings_path, 0755)) { return false; } if (!file::findOrCreateParentDirectory(settings_path, 0755)) {
return false;
}
return file::savePropertiesFile(settings_path, map); return file::savePropertiesFile(settings_path, map);
} }
@@ -170,26 +222,40 @@ lv_display_rotation_t toLvglDisplayRotation(Orientation orientation) {
auto* lvgl_display = lv_display_get_default(); auto* lvgl_display = lv_display_get_default();
auto rotation = lv_display_get_rotation(lvgl_display); auto rotation = lv_display_get_rotation(lvgl_display);
bool is_originally_landscape; bool is_originally_landscape;
// The lvgl resolution code compensates for rotation. We have to revert the compensation to get the real display resolution
// TODO: Use info from display driver
if (rotation == LV_DISPLAY_ROTATION_0 || rotation == LV_DISPLAY_ROTATION_180) { if (rotation == LV_DISPLAY_ROTATION_0 || rotation == LV_DISPLAY_ROTATION_180) {
is_originally_landscape = lv_display_get_physical_horizontal_resolution(lvgl_display) > lv_display_get_physical_vertical_resolution(lvgl_display); is_originally_landscape = lv_display_get_physical_horizontal_resolution(lvgl_display) > lv_display_get_physical_vertical_resolution(lvgl_display);
} else { } else {
is_originally_landscape = lv_display_get_physical_horizontal_resolution(lvgl_display) < lv_display_get_physical_vertical_resolution(lvgl_display); is_originally_landscape = lv_display_get_physical_horizontal_resolution(lvgl_display) < lv_display_get_physical_vertical_resolution(lvgl_display);
} }
if (is_originally_landscape) { if (is_originally_landscape) {
// Landscape display
switch (orientation) { switch (orientation) {
case Orientation::Landscape: return LV_DISPLAY_ROTATION_0; case Orientation::Landscape:
case Orientation::Portrait: return LV_DISPLAY_ROTATION_90; return LV_DISPLAY_ROTATION_0;
case Orientation::LandscapeFlipped: return LV_DISPLAY_ROTATION_180; case Orientation::Portrait:
case Orientation::PortraitFlipped: return LV_DISPLAY_ROTATION_270; return LV_DISPLAY_ROTATION_90;
default: return LV_DISPLAY_ROTATION_0; case Orientation::LandscapeFlipped:
return LV_DISPLAY_ROTATION_180;
case Orientation::PortraitFlipped:
return LV_DISPLAY_ROTATION_270;
default:
return LV_DISPLAY_ROTATION_0;
} }
} else { } else {
// Portrait display
switch (orientation) { switch (orientation) {
case Orientation::Landscape: return LV_DISPLAY_ROTATION_90; case Orientation::Landscape:
case Orientation::Portrait: return LV_DISPLAY_ROTATION_0; return LV_DISPLAY_ROTATION_90;
case Orientation::LandscapeFlipped: return LV_DISPLAY_ROTATION_270; case Orientation::Portrait:
case Orientation::PortraitFlipped: return LV_DISPLAY_ROTATION_180; return LV_DISPLAY_ROTATION_0;
default: return LV_DISPLAY_ROTATION_0; case Orientation::LandscapeFlipped:
return LV_DISPLAY_ROTATION_270;
case Orientation::PortraitFlipped:
return LV_DISPLAY_ROTATION_180;
default:
return LV_DISPLAY_ROTATION_0;
} }
} }
} }