feat(update): SD-card firmware update + X3 bootloader compatibility (#1786)

This commit is contained in:
eunchurn
2026-05-04 19:51:20 -04:00
committed by GitHub
parent b8a6b58b5e
commit 5717374e4b
13 changed files with 928 additions and 18 deletions
+51 -14
View File
@@ -91,9 +91,14 @@ void FileBrowserActivity::loadFiles() {
files.emplace_back(std::string(name) + "/");
} else {
std::string_view filename{name};
if (FsHelpers::hasEpubExtension(filename) || FsHelpers::hasXtcExtension(filename) ||
FsHelpers::hasTxtExtension(filename) || FsHelpers::hasMarkdownExtension(filename) ||
FsHelpers::hasBmpExtension(filename)) {
if (mode == Mode::PickFirmware) {
// Firmware picker: only show .bin files.
if (FsHelpers::checkFileExtension(filename, ".bin")) {
files.emplace_back(filename);
}
} else if (FsHelpers::hasEpubExtension(filename) || FsHelpers::hasXtcExtension(filename) ||
FsHelpers::hasTxtExtension(filename) || FsHelpers::hasMarkdownExtension(filename) ||
FsHelpers::hasBmpExtension(filename)) {
files.emplace_back(filename);
}
}
@@ -106,6 +111,10 @@ void FileBrowserActivity::onEnter() {
selectorIndex = 0;
// If Confirm was held while this activity opened (typical when launched from a menu), ignore
// its release — otherwise we'd immediately auto-open whatever is at index 0.
lockNextConfirmRelease = mappedInput.isPressed(MappedInputManager::Button::Confirm);
auto root = Storage.open(basepath.c_str());
if (!root) {
basepath = "/";
@@ -141,11 +150,10 @@ void FileBrowserActivity::clearFileMetadata(const std::string& fullPath) {
}
void FileBrowserActivity::loop() {
// Long press BACK (1s+) goes to root folder
// but Long press BACK (1s+) from ReaderActivity sends us here with the MappedInput already set.
// So ignore it the first time.
if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= GO_HOME_MS &&
basepath != "/" && !lockLongPressBack) {
// Long press BACK (1s+) goes to root folder (Books mode only).
// In firmware-pick mode we keep navigation simple: short Back = up dir / cancel.
if (mode == Mode::Books && mappedInput.isPressed(MappedInputManager::Button::Back) &&
mappedInput.getHeldTime() >= GO_HOME_MS && basepath != "/" && !lockLongPressBack) {
basepath = "/";
loadFiles();
selectorIndex = 0;
@@ -162,12 +170,27 @@ void FileBrowserActivity::loop() {
const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false, pathReserved);
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
if (lockNextConfirmRelease) {
lockNextConfirmRelease = false;
return;
}
if (files.empty()) return;
const std::string& entry = files[selectorIndex];
bool isDirectory = (entry.back() == '/');
if (mappedInput.getHeldTime() >= GO_HOME_MS && !isDirectory) {
// Firmware picker: select file -> return path; navigate into directories normally.
if (mode == Mode::PickFirmware && !isDirectory) {
std::string cleanBasePath = basepath;
if (cleanBasePath.back() != '/') cleanBasePath += "/";
ActivityResult res{FilePathResult{cleanBasePath + entry}};
res.isCancelled = false;
setResult(std::move(res));
finish();
return;
}
if (mode == Mode::Books && mappedInput.getHeldTime() >= GO_HOME_MS && !isDirectory) {
// --- LONG PRESS ACTION: DELETE FILE ---
std::string cleanBasePath = basepath;
if (cleanBasePath.back() != '/') cleanBasePath += "/";
@@ -231,6 +254,12 @@ void FileBrowserActivity::loop() {
selectorIndex = findEntry(dirName);
requestUpdate();
} else if (mode == Mode::PickFirmware) {
// Firmware picker at root: cancel back to caller instead of going home.
ActivityResult res;
res.isCancelled = true;
setResult(std::move(res));
finish();
} else {
onGoHome();
}
@@ -286,7 +315,10 @@ void FileBrowserActivity::render(RenderLock&&) {
const auto pageHeight = renderer.getScreenHeight();
const auto& metrics = UITheme::getInstance().getMetrics();
std::string folderName = (basepath == "/") ? tr(STR_SD_CARD) : basepath.substr(basepath.rfind('/') + 1);
std::string folderName =
(mode == Mode::PickFirmware)
? std::string(tr(STR_SELECT_FIRMWARE_FILE))
: ((basepath == "/") ? std::string(tr(STR_SD_CARD)) : basepath.substr(basepath.rfind('/') + 1));
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, folderName.c_str());
const int pathLineHeight = renderer.getLineHeight(SMALL_FONT_ID);
@@ -295,7 +327,8 @@ void FileBrowserActivity::render(RenderLock&&) {
const int contentHeight =
pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing - pathReserved;
if (files.empty()) {
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, tr(STR_NO_FILES_FOUND));
const char* emptyMsg = (mode == Mode::PickFirmware) ? tr(STR_NO_BIN_FILES) : tr(STR_NO_FILES_FOUND);
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, emptyMsg);
} else {
GUI.drawList(
renderer, Rect{0, contentTop, pageWidth, contentHeight}, files.size(), selectorIndex,
@@ -332,9 +365,13 @@ void FileBrowserActivity::render(RenderLock&&) {
}
// Help text
const auto labels =
mappedInput.mapLabels(basepath == "/" ? tr(STR_HOME) : tr(STR_BACK), files.empty() ? "" : tr(STR_OPEN),
files.empty() ? "" : tr(STR_DIR_UP), files.empty() ? "" : tr(STR_DIR_DOWN));
const char* backLabel = (basepath == "/") ? (mode == Mode::PickFirmware ? tr(STR_BACK) : tr(STR_HOME)) : tr(STR_BACK);
// In PickFirmware mode, Confirm on a .bin returns the path to the caller (not "open"); show
// STR_SELECT instead. Directories in the same picker still descend, so keep STR_OPEN there.
const bool selectingFirmwareFile = mode == Mode::PickFirmware && !files.empty() && files[selectorIndex].back() != '/';
const char* confirmLabel = files.empty() ? "" : (selectingFirmwareFile ? tr(STR_SELECT) : tr(STR_OPEN));
const auto labels = mappedInput.mapLabels(backLabel, confirmLabel, files.empty() ? "" : tr(STR_DIR_UP),
files.empty() ? "" : tr(STR_DIR_DOWN));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();