Port 6 upstream PRs (PR #939 was already ported): - #852: Complete HalPowerManager with RAII Lock class, WiFi check in setPowerSaving, skipLoopDelay overrides for ClearCache/OtaUpdate, and power lock in Activity render task loops - #965: Fix paragraph formatting inside list items by tracking listItemUntilDepth to prevent unwanted line breaks - #972: Micro-optimizations: std::move in insertFont, const ref for getDataFromBook parameter - #971: Remove redundant hasPrintableChars pre-rendering pass from EpdFont, EpdFontFamily, and GfxRenderer - #977: Skip unsupported image formats before extraction, add PARSE_BUFFER_SIZE constant and chapter parse timing - #975: Fix UITheme memory leak by replacing raw pointer with std::unique_ptr for currentTheme Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -47,14 +47,6 @@ void EpdFont::getTextDimensions(const char* string, int* w, int* h) const {
|
|||||||
*h = maxY - minY;
|
*h = maxY - minY;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EpdFont::hasPrintableChars(const char* string) const {
|
|
||||||
int w = 0, h = 0;
|
|
||||||
|
|
||||||
getTextDimensions(string, &w, &h);
|
|
||||||
|
|
||||||
return w > 0 || h > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EpdGlyph* EpdFont::getGlyph(const uint32_t cp) const {
|
const EpdGlyph* EpdFont::getGlyph(const uint32_t cp) const {
|
||||||
const EpdUnicodeInterval* intervals = data->intervals;
|
const EpdUnicodeInterval* intervals = data->intervals;
|
||||||
const int count = data->intervalCount;
|
const int count = data->intervalCount;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ class EpdFont {
|
|||||||
explicit EpdFont(const EpdFontData* data) : data(data) {}
|
explicit EpdFont(const EpdFontData* data) : data(data) {}
|
||||||
~EpdFont() = default;
|
~EpdFont() = default;
|
||||||
void getTextDimensions(const char* string, int* w, int* h) const;
|
void getTextDimensions(const char* string, int* w, int* h) const;
|
||||||
bool hasPrintableChars(const char* string) const;
|
|
||||||
|
|
||||||
const EpdGlyph* getGlyph(uint32_t cp) const;
|
const EpdGlyph* getGlyph(uint32_t cp) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,10 +22,6 @@ void EpdFontFamily::getTextDimensions(const char* string, int* w, int* h, const
|
|||||||
getFont(style)->getTextDimensions(string, w, h);
|
getFont(style)->getTextDimensions(string, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EpdFontFamily::hasPrintableChars(const char* string, const Style style) const {
|
|
||||||
return getFont(style)->hasPrintableChars(string);
|
|
||||||
}
|
|
||||||
|
|
||||||
const EpdFontData* EpdFontFamily::getData(const Style style) const { return getFont(style)->data; }
|
const EpdFontData* EpdFontFamily::getData(const Style style) const { return getFont(style)->data; }
|
||||||
|
|
||||||
const EpdGlyph* EpdFontFamily::getGlyph(const uint32_t cp, const Style style) const {
|
const EpdGlyph* EpdFontFamily::getGlyph(const uint32_t cp, const Style style) const {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ class EpdFontFamily {
|
|||||||
: regular(regular), bold(bold), italic(italic), boldItalic(boldItalic) {}
|
: regular(regular), bold(bold), italic(italic), boldItalic(boldItalic) {}
|
||||||
~EpdFontFamily() = default;
|
~EpdFontFamily() = default;
|
||||||
void getTextDimensions(const char* string, int* w, int* h, Style style = REGULAR) const;
|
void getTextDimensions(const char* string, int* w, int* h, Style style = REGULAR) const;
|
||||||
bool hasPrintableChars(const char* string, Style style = REGULAR) const;
|
|
||||||
const EpdFontData* getData(Style style = REGULAR) const;
|
const EpdFontData* getData(Style style = REGULAR) const;
|
||||||
const EpdGlyph* getGlyph(uint32_t cp, Style style = REGULAR) const;
|
const EpdGlyph* getGlyph(uint32_t cp, Style style = REGULAR) const;
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ constexpr int NUM_HEADER_TAGS = sizeof(HEADER_TAGS) / sizeof(HEADER_TAGS[0]);
|
|||||||
|
|
||||||
// Minimum file size (in bytes) to show indexing popup - smaller chapters don't benefit from it
|
// Minimum file size (in bytes) to show indexing popup - smaller chapters don't benefit from it
|
||||||
constexpr size_t MIN_SIZE_FOR_POPUP = 10 * 1024; // 10KB
|
constexpr size_t MIN_SIZE_FOR_POPUP = 10 * 1024; // 10KB
|
||||||
|
constexpr size_t PARSE_BUFFER_SIZE = 1024;
|
||||||
|
|
||||||
const char* BLOCK_TAGS[] = {"p", "li", "div", "br", "blockquote"};
|
const char* BLOCK_TAGS[] = {"p", "li", "div", "br", "blockquote"};
|
||||||
constexpr int NUM_BLOCK_TAGS = sizeof(BLOCK_TAGS) / sizeof(BLOCK_TAGS[0]);
|
constexpr int NUM_BLOCK_TAGS = sizeof(BLOCK_TAGS) / sizeof(BLOCK_TAGS[0]);
|
||||||
@@ -389,6 +390,9 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
// Resolve the image path relative to the HTML file
|
// Resolve the image path relative to the HTML file
|
||||||
std::string resolvedPath = FsHelpers::normalisePath(self->contentBase + src);
|
std::string resolvedPath = FsHelpers::normalisePath(self->contentBase + src);
|
||||||
|
|
||||||
|
// Check format support before any file I/O
|
||||||
|
ImageToFramebufferDecoder* decoder = ImageDecoderFactory::getDecoder(resolvedPath);
|
||||||
|
if (decoder) {
|
||||||
// Create a unique filename for the cached image
|
// Create a unique filename for the cached image
|
||||||
std::string ext;
|
std::string ext;
|
||||||
size_t extPos = resolvedPath.rfind('.');
|
size_t extPos = resolvedPath.rfind('.');
|
||||||
@@ -410,8 +414,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
if (extractSuccess) {
|
if (extractSuccess) {
|
||||||
// Get image dimensions
|
// Get image dimensions
|
||||||
ImageDimensions dims = {0, 0};
|
ImageDimensions dims = {0, 0};
|
||||||
ImageToFramebufferDecoder* decoder = ImageDecoderFactory::getDecoder(cachedImagePath);
|
if (decoder->getDimensions(cachedImagePath, dims)) {
|
||||||
if (decoder && decoder->getDimensions(cachedImagePath, dims)) {
|
|
||||||
LOG_DBG("EHP", "Image dimensions: %dx%d", dims.width, dims.height);
|
LOG_DBG("EHP", "Image dimensions: %dx%d", dims.width, dims.height);
|
||||||
|
|
||||||
// Scale to fit viewport while maintaining aspect ratio
|
// Scale to fit viewport while maintaining aspect ratio
|
||||||
@@ -470,6 +473,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
} else {
|
} else {
|
||||||
LOG_ERR("EHP", "Failed to extract image");
|
LOG_ERR("EHP", "Failed to extract image");
|
||||||
}
|
}
|
||||||
|
} // if (decoder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,18 +544,24 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
} else if (matches(name, BLOCK_TAGS, NUM_BLOCK_TAGS)) {
|
} else if (matches(name, BLOCK_TAGS, NUM_BLOCK_TAGS)) {
|
||||||
if (strcmp(name, "br") == 0) {
|
if (strcmp(name, "br") == 0) {
|
||||||
if (self->partWordBufferIndex > 0) {
|
if (self->partWordBufferIndex > 0) {
|
||||||
// flush word preceding <br/> to currentTextBlock before calling startNewTextBlock
|
|
||||||
self->flushPartWordBuffer();
|
self->flushPartWordBuffer();
|
||||||
}
|
}
|
||||||
self->startNewTextBlock(self->currentTextBlock->getBlockStyle());
|
self->startNewTextBlock(self->currentTextBlock->getBlockStyle());
|
||||||
|
} else if (strcmp(name, "li") == 0) {
|
||||||
|
self->currentCssStyle = cssStyle;
|
||||||
|
self->startNewTextBlock(userAlignmentBlockStyle);
|
||||||
|
self->updateEffectiveInlineStyle();
|
||||||
|
self->currentTextBlock->addWord("\xe2\x80\xa2", EpdFontFamily::REGULAR);
|
||||||
|
self->listItemUntilDepth = std::min(self->listItemUntilDepth, self->depth);
|
||||||
|
} else if (strcmp(name, "p") == 0 && self->listItemUntilDepth < self->depth) {
|
||||||
|
// Inside a <li> element - don't start a new text block for <p>
|
||||||
|
// This prevents bullet points from appearing on their own line
|
||||||
|
self->currentCssStyle = cssStyle;
|
||||||
|
self->updateEffectiveInlineStyle();
|
||||||
} else {
|
} else {
|
||||||
self->currentCssStyle = cssStyle;
|
self->currentCssStyle = cssStyle;
|
||||||
self->startNewTextBlock(userAlignmentBlockStyle);
|
self->startNewTextBlock(userAlignmentBlockStyle);
|
||||||
self->updateEffectiveInlineStyle();
|
self->updateEffectiveInlineStyle();
|
||||||
|
|
||||||
if (strcmp(name, "li") == 0) {
|
|
||||||
self->currentTextBlock->addWord("\xe2\x80\xa2", EpdFontFamily::REGULAR);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (matches(name, UNDERLINE_TAGS, NUM_UNDERLINE_TAGS)) {
|
} else if (matches(name, UNDERLINE_TAGS, NUM_UNDERLINE_TAGS)) {
|
||||||
// Flush buffer before style change so preceding text gets current style
|
// Flush buffer before style change so preceding text gets current style
|
||||||
@@ -807,6 +817,7 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
|
|||||||
if (self->boldUntilDepth == self->depth) self->boldUntilDepth = INT_MAX;
|
if (self->boldUntilDepth == self->depth) self->boldUntilDepth = INT_MAX;
|
||||||
if (self->italicUntilDepth == self->depth) self->italicUntilDepth = INT_MAX;
|
if (self->italicUntilDepth == self->depth) self->italicUntilDepth = INT_MAX;
|
||||||
if (self->underlineUntilDepth == self->depth) self->underlineUntilDepth = INT_MAX;
|
if (self->underlineUntilDepth == self->depth) self->underlineUntilDepth = INT_MAX;
|
||||||
|
if (self->listItemUntilDepth == self->depth) self->listItemUntilDepth = INT_MAX;
|
||||||
if (!self->inlineStyleStack.empty() && self->inlineStyleStack.back().depth == self->depth) {
|
if (!self->inlineStyleStack.empty() && self->inlineStyleStack.back().depth == self->depth) {
|
||||||
self->inlineStyleStack.pop_back();
|
self->inlineStyleStack.pop_back();
|
||||||
self->updateEffectiveInlineStyle();
|
self->updateEffectiveInlineStyle();
|
||||||
@@ -852,6 +863,11 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
|
|||||||
self->underlineUntilDepth = INT_MAX;
|
self->underlineUntilDepth = INT_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Leaving list item
|
||||||
|
if (self->listItemUntilDepth == self->depth) {
|
||||||
|
self->listItemUntilDepth = INT_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
// Pop from inline style stack if we pushed an entry at this depth
|
// Pop from inline style stack if we pushed an entry at this depth
|
||||||
// This handles all inline elements: b, i, u, span, etc.
|
// This handles all inline elements: b, i, u, span, etc.
|
||||||
if (!self->inlineStyleStack.empty() && self->inlineStyleStack.back().depth == self->depth) {
|
if (!self->inlineStyleStack.empty() && self->inlineStyleStack.back().depth == self->depth) {
|
||||||
@@ -867,6 +883,7 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||||
|
unsigned long chapterStartTime = millis();
|
||||||
auto paragraphAlignmentBlockStyle = BlockStyle();
|
auto paragraphAlignmentBlockStyle = BlockStyle();
|
||||||
paragraphAlignmentBlockStyle.textAlignDefined = true;
|
paragraphAlignmentBlockStyle.textAlignDefined = true;
|
||||||
// Resolve None sentinel to Justify for initial block (no CSS context yet)
|
// Resolve None sentinel to Justify for initial block (no CSS context yet)
|
||||||
@@ -904,7 +921,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
|||||||
XML_SetCharacterDataHandler(parser, characterData);
|
XML_SetCharacterDataHandler(parser, characterData);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
void* const buf = XML_GetBuffer(parser, 1024);
|
void* const buf = XML_GetBuffer(parser, PARSE_BUFFER_SIZE);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
LOG_ERR("EHP", "Couldn't allocate memory for buffer");
|
LOG_ERR("EHP", "Couldn't allocate memory for buffer");
|
||||||
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
|
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
|
||||||
@@ -915,7 +932,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t len = file.read(buf, 1024);
|
const size_t len = file.read(buf, PARSE_BUFFER_SIZE);
|
||||||
|
|
||||||
if (len == 0 && file.available() > 0) {
|
if (len == 0 && file.available() > 0) {
|
||||||
LOG_ERR("EHP", "File read error");
|
LOG_ERR("EHP", "File read error");
|
||||||
@@ -955,6 +972,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
|||||||
currentTextBlock.reset();
|
currentTextBlock.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_DBG("EHP", "Chapter parsed in %lu ms", millis() - chapterStartTime);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class ChapterHtmlSlimParser {
|
|||||||
int boldUntilDepth = INT_MAX;
|
int boldUntilDepth = INT_MAX;
|
||||||
int italicUntilDepth = INT_MAX;
|
int italicUntilDepth = INT_MAX;
|
||||||
int underlineUntilDepth = INT_MAX;
|
int underlineUntilDepth = INT_MAX;
|
||||||
|
int listItemUntilDepth = INT_MAX;
|
||||||
// buffer for building up words from characters, will auto break if longer than this
|
// buffer for building up words from characters, will auto break if longer than this
|
||||||
// leave one char at end for null pointer
|
// leave one char at end for null pointer
|
||||||
char partWordBuffer[MAX_WORD_SIZE + 1] = {};
|
char partWordBuffer[MAX_WORD_SIZE + 1] = {};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ void GfxRenderer::begin() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
|
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, std::move(font)}); }
|
||||||
|
|
||||||
// Translate logical (x,y) coordinates to physical panel coordinates based on current orientation
|
// Translate logical (x,y) coordinates to physical panel coordinates based on current orientation
|
||||||
// This should always be inlined for better performance
|
// This should always be inlined for better performance
|
||||||
@@ -116,11 +116,6 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
|
|||||||
}
|
}
|
||||||
const auto font = fontMap.at(fontId);
|
const auto font = fontMap.at(fontId);
|
||||||
|
|
||||||
// no printable characters
|
|
||||||
if (!font.hasPrintableChars(text, style)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t cp;
|
uint32_t cp;
|
||||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
||||||
renderChar(font, cp, &xpos, &yPos, black, style);
|
renderChar(font, cp, &xpos, &yPos, black, style);
|
||||||
@@ -853,11 +848,6 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
|
|||||||
}
|
}
|
||||||
const auto font = fontMap.at(fontId);
|
const auto font = fontMap.at(fontId);
|
||||||
|
|
||||||
// No printable characters
|
|
||||||
if (!font.hasPrintableChars(text, style)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For 90° clockwise rotation:
|
// For 90° clockwise rotation:
|
||||||
// Original (glyphX, glyphY) -> Rotated (glyphY, -glyphX)
|
// Original (glyphX, glyphY) -> Rotated (glyphY, -glyphX)
|
||||||
// Text reads from bottom to top
|
// Text reads from bottom to top
|
||||||
@@ -936,11 +926,6 @@ void GfxRenderer::drawTextRotated90CCW(const int fontId, const int x, const int
|
|||||||
}
|
}
|
||||||
const auto font = fontMap.at(fontId);
|
const auto font = fontMap.at(fontId);
|
||||||
|
|
||||||
// No printable characters
|
|
||||||
if (!font.hasPrintableChars(text, style)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For 90° counter-clockwise rotation:
|
// For 90° counter-clockwise rotation:
|
||||||
// Mirror of CW: glyphY maps to -X direction, glyphX maps to +Y direction
|
// Mirror of CW: glyphY maps to -X direction, glyphX maps to +Y direction
|
||||||
// Text reads from top to bottom
|
// Text reads from top to bottom
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "HalPowerManager.h"
|
#include "HalPowerManager.h"
|
||||||
|
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
|
#include <WiFi.h>
|
||||||
#include <esp_sleep.h>
|
#include <esp_sleep.h>
|
||||||
|
|
||||||
#include "HalGPIO.h"
|
#include "HalGPIO.h"
|
||||||
@@ -8,12 +9,27 @@
|
|||||||
void HalPowerManager::begin() {
|
void HalPowerManager::begin() {
|
||||||
pinMode(BAT_GPIO0, INPUT);
|
pinMode(BAT_GPIO0, INPUT);
|
||||||
normalFreq = getCpuFrequencyMhz();
|
normalFreq = getCpuFrequencyMhz();
|
||||||
|
modeMutex = xSemaphoreCreateMutex();
|
||||||
|
assert(modeMutex != nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HalPowerManager::setPowerSaving(bool enabled) {
|
void HalPowerManager::setPowerSaving(bool enabled) {
|
||||||
if (normalFreq <= 0) {
|
if (normalFreq <= 0) {
|
||||||
return; // invalid state
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
if (WiFi.getMode() != WIFI_MODE_NULL) {
|
||||||
|
enabled = false;
|
||||||
|
}
|
||||||
|
xSemaphoreTake(modeMutex, portMAX_DELAY);
|
||||||
|
const LockMode mode = currentLockMode;
|
||||||
|
xSemaphoreGive(modeMutex);
|
||||||
|
if (mode == NormalSpeed) {
|
||||||
|
enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (enabled && !isLowPower) {
|
if (enabled && !isLowPower) {
|
||||||
LOG_DBG("PWR", "Going to low-power mode");
|
LOG_DBG("PWR", "Going to low-power mode");
|
||||||
if (!setCpuFrequencyMhz(LOW_POWER_FREQ)) {
|
if (!setCpuFrequencyMhz(LOW_POWER_FREQ)) {
|
||||||
@@ -31,6 +47,25 @@ void HalPowerManager::setPowerSaving(bool enabled) {
|
|||||||
isLowPower = enabled;
|
isLowPower = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RAII Lock implementation
|
||||||
|
|
||||||
|
HalPowerManager::Lock::Lock() {
|
||||||
|
xSemaphoreTake(powerManager.modeMutex, portMAX_DELAY);
|
||||||
|
powerManager.currentLockMode = NormalSpeed;
|
||||||
|
valid = true;
|
||||||
|
if (powerManager.isLowPower) {
|
||||||
|
powerManager.setPowerSaving(false);
|
||||||
|
}
|
||||||
|
xSemaphoreGive(powerManager.modeMutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
HalPowerManager::Lock::~Lock() {
|
||||||
|
if (!valid) return;
|
||||||
|
xSemaphoreTake(powerManager.modeMutex, portMAX_DELAY);
|
||||||
|
powerManager.currentLockMode = None;
|
||||||
|
xSemaphoreGive(powerManager.modeMutex);
|
||||||
|
}
|
||||||
|
|
||||||
void HalPowerManager::startDeepSleep(HalGPIO& gpio) const {
|
void HalPowerManager::startDeepSleep(HalGPIO& gpio) const {
|
||||||
// Ensure that the power button has been released to avoid immediately turning back on if you're holding it
|
// Ensure that the power button has been released to avoid immediately turning back on if you're holding it
|
||||||
while (gpio.isPressed(HalGPIO::BTN_POWER)) {
|
while (gpio.isPressed(HalGPIO::BTN_POWER)) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <BatteryMonitor.h>
|
#include <BatteryMonitor.h>
|
||||||
#include <InputManager.h>
|
#include <InputManager.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
|
||||||
#include "HalGPIO.h"
|
#include "HalGPIO.h"
|
||||||
|
|
||||||
@@ -10,6 +11,10 @@ class HalPowerManager {
|
|||||||
int normalFreq = 0; // MHz
|
int normalFreq = 0; // MHz
|
||||||
bool isLowPower = false;
|
bool isLowPower = false;
|
||||||
|
|
||||||
|
enum LockMode { None, NormalSpeed };
|
||||||
|
LockMode currentLockMode = None;
|
||||||
|
SemaphoreHandle_t modeMutex = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr int LOW_POWER_FREQ = 10; // MHz
|
static constexpr int LOW_POWER_FREQ = 10; // MHz
|
||||||
static constexpr unsigned long IDLE_POWER_SAVING_MS = 3000; // ms
|
static constexpr unsigned long IDLE_POWER_SAVING_MS = 3000; // ms
|
||||||
@@ -24,4 +29,20 @@ class HalPowerManager {
|
|||||||
|
|
||||||
// Get battery percentage (range 0-100)
|
// Get battery percentage (range 0-100)
|
||||||
int getBatteryPercentage() const;
|
int getBatteryPercentage() const;
|
||||||
|
|
||||||
|
// RAII lock to prevent low-power mode during critical work (e.g. rendering)
|
||||||
|
class Lock {
|
||||||
|
friend class HalPowerManager;
|
||||||
|
bool valid = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Lock();
|
||||||
|
~Lock();
|
||||||
|
Lock(const Lock&) = delete;
|
||||||
|
Lock& operator=(const Lock&) = delete;
|
||||||
|
Lock(Lock&&) = delete;
|
||||||
|
Lock& operator=(Lock&&) = delete;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern HalPowerManager powerManager;
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ bool RecentBooksStore::saveToFile() const {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
RecentBook RecentBooksStore::getDataFromBook(std::string path) const {
|
RecentBook RecentBooksStore::getDataFromBook(const std::string& path) const {
|
||||||
std::string lastBookFileName = "";
|
std::string lastBookFileName = "";
|
||||||
const size_t lastSlash = path.find_last_of('/');
|
const size_t lastSlash = path.find_last_of('/');
|
||||||
if (lastSlash != std::string::npos) {
|
if (lastSlash != std::string::npos) {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class RecentBooksStore {
|
|||||||
bool saveToFile() const;
|
bool saveToFile() const;
|
||||||
|
|
||||||
bool loadFromFile();
|
bool loadFromFile();
|
||||||
RecentBook getDataFromBook(std::string path) const;
|
RecentBook getDataFromBook(const std::string& path) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper macro to access recent books store
|
// Helper macro to access recent books store
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "Activity.h"
|
#include "Activity.h"
|
||||||
|
|
||||||
|
#include <HalPowerManager.h>
|
||||||
|
|
||||||
void Activity::renderTaskTrampoline(void* param) {
|
void Activity::renderTaskTrampoline(void* param) {
|
||||||
auto* self = static_cast<Activity*>(param);
|
auto* self = static_cast<Activity*>(param);
|
||||||
self->renderTaskLoop();
|
self->renderTaskLoop();
|
||||||
@@ -9,6 +11,7 @@ void Activity::renderTaskLoop() {
|
|||||||
while (true) {
|
while (true) {
|
||||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||||
{
|
{
|
||||||
|
HalPowerManager::Lock powerLock;
|
||||||
RenderLock lock(*this);
|
RenderLock lock(*this);
|
||||||
render(std::move(lock));
|
render(std::move(lock));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#include "ActivityWithSubactivity.h"
|
#include "ActivityWithSubactivity.h"
|
||||||
|
|
||||||
|
#include <HalPowerManager.h>
|
||||||
|
|
||||||
void ActivityWithSubactivity::renderTaskLoop() {
|
void ActivityWithSubactivity::renderTaskLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||||
{
|
{
|
||||||
|
HalPowerManager::Lock powerLock;
|
||||||
RenderLock lock(*this);
|
RenderLock lock(*this);
|
||||||
if (!subActivity) {
|
if (!subActivity) {
|
||||||
render(std::move(lock));
|
render(std::move(lock));
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class ClearCacheActivity final : public ActivityWithSubactivity {
|
|||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
void render(Activity::RenderLock&&) override;
|
void render(Activity::RenderLock&&) override;
|
||||||
|
bool skipLoopDelay() override { return true; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum State { WARNING, CLEARING, SUCCESS, FAILED };
|
enum State { WARNING, CLEARING, SUCCESS, FAILED };
|
||||||
|
|||||||
@@ -33,5 +33,6 @@ class OtaUpdateActivity : public ActivityWithSubactivity {
|
|||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
void render(Activity::RenderLock&&) override;
|
void render(Activity::RenderLock&&) override;
|
||||||
|
bool skipLoopDelay() override { return true; }
|
||||||
bool preventAutoSleep() override { return state == CHECKING_FOR_UPDATE || state == UPDATE_IN_PROGRESS; }
|
bool preventAutoSleep() override { return state == CHECKING_FOR_UPDATE || state == UPDATE_IN_PROGRESS; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ void UITheme::setTheme(CrossPointSettings::UI_THEME type) {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case CrossPointSettings::UI_THEME::CLASSIC:
|
case CrossPointSettings::UI_THEME::CLASSIC:
|
||||||
LOG_DBG("UI", "Using Classic theme");
|
LOG_DBG("UI", "Using Classic theme");
|
||||||
currentTheme = new BaseTheme();
|
currentTheme = std::make_unique<BaseTheme>();
|
||||||
currentMetrics = &BaseMetrics::values;
|
currentMetrics = &BaseMetrics::values;
|
||||||
break;
|
break;
|
||||||
case CrossPointSettings::UI_THEME::LYRA:
|
case CrossPointSettings::UI_THEME::LYRA:
|
||||||
LOG_DBG("UI", "Using Lyra theme");
|
LOG_DBG("UI", "Using Lyra theme");
|
||||||
currentTheme = new LyraTheme();
|
currentTheme = std::make_unique<LyraTheme>();
|
||||||
currentMetrics = &LyraMetrics::values;
|
currentMetrics = &LyraMetrics::values;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
@@ -24,7 +25,7 @@ class UITheme {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
const ThemeMetrics* currentMetrics;
|
const ThemeMetrics* currentMetrics;
|
||||||
const BaseTheme* currentTheme;
|
std::unique_ptr<const BaseTheme> currentTheme;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Known theme thumbnail heights to prerender when opening a book for the first time.
|
// Known theme thumbnail heights to prerender when opening a book for the first time.
|
||||||
|
|||||||
Reference in New Issue
Block a user