From 2a1f7873f75e7ebfedaceccebd4f8ffd7a4a1f6c Mon Sep 17 00:00:00 2001 From: Aleksejs Popovs Date: Wed, 21 Jan 2026 20:09:16 -0500 Subject: [PATCH] add autoopen --- .../bluetooth/BluetoothActivity.cpp | 37 +++++++++++++++---- src/activities/bluetooth/BluetoothActivity.h | 12 ++++-- src/main.cpp | 13 ++++++- src/util/StringUtils.cpp | 5 +++ src/util/StringUtils.h | 5 +++ 5 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/activities/bluetooth/BluetoothActivity.cpp b/src/activities/bluetooth/BluetoothActivity.cpp index b00e5d14..83df6462 100644 --- a/src/activities/bluetooth/BluetoothActivity.cpp +++ b/src/activities/bluetooth/BluetoothActivity.cpp @@ -24,11 +24,24 @@ } \ } while (0) -void BluetoothActivity::taskTrampoline(void* param) { +void BluetoothActivity::displayTaskTrampoline(void* param) { auto* self = static_cast(param); self->displayTaskLoop(); } +void BluetoothActivity::reportTaskTrampoline(void* param) { + auto* self = static_cast(param); + self->report(); + vTaskDelete(nullptr); +} + +void BluetoothActivity::report() { + if (state != STATE_DONE) { + return; + } + onFileReceived(OUTPUT_DIRECTORY "/" + filename); +} + void BluetoothActivity::startAdvertising() { NimBLEDevice::startAdvertising(); } @@ -65,7 +78,7 @@ void BluetoothActivity::onEnter() { state = STATE_INITIALIZING; intoState(STATE_WAITING); - xTaskCreate(&BluetoothActivity::taskTrampoline, "BluetoothTask", + xTaskCreate(&BluetoothActivity::displayTaskTrampoline, "BluetoothTask", // TODO: figure out how much stack we actually need 4096, // Stack size this, // Parameters @@ -89,6 +102,16 @@ void BluetoothActivity::intoState(State newState) { // caller sets filename, totalBytes, file, txnId receivedBytes = 0; break; + case STATE_DONE: + // we cannot call onFileReceived here directly because it might cause onExit to be called, + // which calls NimBLEDevice::deinit, which cannot be called from inside a NimBLE callback. + xTaskCreate(&BluetoothActivity::reportTaskTrampoline, "BluetoothReportTask", + 2048, // Stack size + this, // Parameters + 1, // Priority, + nullptr + ); + break; case STATE_ERROR: { // caller sets errorMessage @@ -131,7 +154,7 @@ void BluetoothActivity::loop() { return; } - if (state == STATE_ERROR) { + if (state == STATE_ERROR || state == STATE_DONE) { if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { // restart intoState(STATE_WAITING); @@ -207,7 +230,7 @@ void BluetoothActivity::render() const { // Draw help text at bottom const auto labels = mappedInput.mapLabels( "« Back", - (state == STATE_ERROR) ? "Restart" : "", + (state == STATE_ERROR || state == STATE_DONE) ? "Restart" : "", "", "" ); @@ -227,8 +250,9 @@ void BluetoothActivity::ServerCallbacks::onDisconnect(NimBLEServer* pServer, Nim } void BluetoothActivity::onConnected(bool isConnected) { - if (state == STATE_ERROR) { - // stay in error state so the user can read the error message even after disconnect + if (state == STATE_ERROR || state == STATE_DONE) { + // stay in error state so the user can read the error message even after disconnect. + // stay in done state so the user can see the transfer complete message. return; } @@ -310,7 +334,6 @@ void BluetoothActivity::onRequest(lfbt_message* msg, size_t msg_len) { if (receivedBytes >= totalBytes) { PROTOCOL_ASSERT(receivedBytes == totalBytes, "Got more bytes than expected: %zu > %zu", receivedBytes, totalBytes); PROTOCOL_ASSERT(file.close(), "Couldn't finalize writing the file"); - // TODO: automatically open file in reader intoState(STATE_DONE); } else { intoState(STATE_RECEIVING); diff --git a/src/activities/bluetooth/BluetoothActivity.h b/src/activities/bluetooth/BluetoothActivity.h index bfb7d6e1..c30c3eaf 100644 --- a/src/activities/bluetooth/BluetoothActivity.h +++ b/src/activities/bluetooth/BluetoothActivity.h @@ -45,17 +45,22 @@ typedef struct __attribute__((packed)) { * BluetoothActivity receives files over a custom BLE protocol and stores them on the SD card. * * The onCancel callback is called if the user presses back. + * onFileReceived is called when a file is successfully received with the path to the file. */ class BluetoothActivity final : public Activity { TaskHandle_t displayTaskHandle = nullptr; SemaphoreHandle_t renderingMutex = nullptr; bool updateRequired = false; const std::function onCancel; + const std::function onFileReceived; - static void taskTrampoline(void* param); + static void displayTaskTrampoline(void* param); [[noreturn]] void displayTaskLoop(); void render() const; + static void reportTaskTrampoline(void* param); + void report(); + void onConnected(bool isConnected); void onRequest(lfbt_message *msg, size_t msg_len); @@ -113,8 +118,9 @@ class BluetoothActivity final : public Activity { public: explicit BluetoothActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, - const std::function& onCancel) - : Activity("Bluetooth", renderer, mappedInput), onCancel(onCancel), + const std::function& onCancel, + const std::function& onFileReceived) + : Activity("Bluetooth", renderer, mappedInput), onCancel(onCancel), onFileReceived(onFileReceived), serverCallbacks(this), requestCallbacks(this) {} void onEnter() override; void onExit() override; diff --git a/src/main.cpp b/src/main.cpp index b5db102c..d2b898d6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,6 +26,7 @@ #include "activities/settings/SettingsActivity.h" #include "activities/util/FullScreenMessageActivity.h" #include "fontIds.h" +#include "util/StringUtils.h" #define SPI_FQ 40000000 // Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults) @@ -229,7 +230,17 @@ void onGoToFileTransfer() { void onGoToBluetooth() { exitActivity(); - enterNewActivity(new BluetoothActivity(renderer, mappedInputManager, onGoHome)); + enterNewActivity(new BluetoothActivity( + renderer, + mappedInputManager, + onGoHome, + [](const std::string& filepath) { + Serial.printf("[%lu] [ ] File received over Bluetooth: %s\n", millis(), filepath.c_str()); + if (StringUtils::readableFileExtension(filepath)) { + onGoToReader(filepath, MyLibraryActivity::Tab::Recent); + } + } + )); } void onGoToSettings() { diff --git a/src/util/StringUtils.cpp b/src/util/StringUtils.cpp index 54ef363f..f533544c 100644 --- a/src/util/StringUtils.cpp +++ b/src/util/StringUtils.cpp @@ -61,6 +61,11 @@ bool checkFileExtension(const String& fileName, const char* extension) { return localFile.endsWith(localExtension); } +bool readableFileExtension(const std::string& fileName) { + return (StringUtils::checkFileExtension(fileName, ".epub") || StringUtils::checkFileExtension(fileName, ".xtch") || + StringUtils::checkFileExtension(fileName, ".xtc") || StringUtils::checkFileExtension(fileName, ".txt")); +} + std::pair splitFileName(const std::string& name) { size_t lastDot = name.find_last_of('.'); if (lastDot == std::string::npos) { diff --git a/src/util/StringUtils.h b/src/util/StringUtils.h index 7f36554a..323c1c69 100644 --- a/src/util/StringUtils.h +++ b/src/util/StringUtils.h @@ -19,6 +19,11 @@ std::string sanitizeFilename(const std::string& name, size_t maxLength = 100); bool checkFileExtension(const std::string& fileName, const char* extension); bool checkFileExtension(const String& fileName, const char* extension); +/** + * Check if the given filename ends with an extension we can open. + */ +bool readableFileExtension(const std::string& fileName); + /** * Split a filename into base name and extension. * If there is no extension, the second element of the pair will be an empty string.