From c8f4870d7c534d958064eb63b4cc8285a2fa6cdc Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Sun, 4 Jan 2026 05:27:34 +0100 Subject: [PATCH 1/5] Improved battery symbol (#228) Rounded corners, smaller positive terminal thingy. --- src/ScreenComponents.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ScreenComponents.cpp b/src/ScreenComponents.cpp index 04265e2d..2900f3e4 100644 --- a/src/ScreenComponents.cpp +++ b/src/ScreenComponents.cpp @@ -15,21 +15,21 @@ void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, // 1 column on left, 2 columns on right, 5 columns of battery body constexpr int batteryWidth = 15; - constexpr int batteryHeight = 10; + constexpr int batteryHeight = 12; const int x = left; - const int y = top + 8; + const int y = top + 6; // Top line - renderer.drawLine(x, y, x + batteryWidth - 4, y); + renderer.drawLine(x + 1, y, x + batteryWidth - 3, y); // Bottom line - renderer.drawLine(x, y + batteryHeight - 1, x + batteryWidth - 4, y + batteryHeight - 1); + renderer.drawLine(x + 1, y + batteryHeight - 1, x + batteryWidth - 3, y + batteryHeight - 1); // Left line - renderer.drawLine(x, y, x, y + batteryHeight - 1); + renderer.drawLine(x, y + 1, x, y + batteryHeight - 2); // Battery end - renderer.drawLine(x + batteryWidth - 4, y, x + batteryWidth - 4, y + batteryHeight - 1); - renderer.drawLine(x + batteryWidth - 3, y + 2, x + batteryWidth - 1, y + 2); - renderer.drawLine(x + batteryWidth - 3, y + batteryHeight - 3, x + batteryWidth - 1, y + batteryHeight - 3); - renderer.drawLine(x + batteryWidth - 1, y + 2, x + batteryWidth - 1, y + batteryHeight - 3); + renderer.drawLine(x + batteryWidth - 2, y + 1, x + batteryWidth - 2, y + batteryHeight - 2); + renderer.drawPixel(x + batteryWidth - 1, y + 3); + renderer.drawPixel(x + batteryWidth - 1, y + batteryHeight - 4); + renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0, y + batteryHeight - 5); // The +1 is to round up, so that we always fill at least one pixel int filledWidth = percentage * (batteryWidth - 5) / 100 + 1; @@ -37,5 +37,5 @@ void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, filledWidth = batteryWidth - 5; // Ensure we don't overflow } - renderer.fillRect(x + 1, y + 1, filledWidth, batteryHeight - 2); + renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4); } From 14972b34cbcfd5d4772967477e50c1f350f0f05c Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Sun, 4 Jan 2026 15:08:32 +1000 Subject: [PATCH 2/5] Remove authentication type from hotspot QR code (#235) ## Summary * Remove the `T` parameter (authentication type), and `P` parameter (password) for the SoftAP wifi config QR code ## Additional Context * It is optional according to the spec: https://www.wi-fi.org/system/files/WPA3%20Specification%20v3.2.pdf#page=25 so should either be omitted or set to nopass * Fixes https://github.com/daveallie/crosspoint-reader/issues/229 --- src/activities/network/CrossPointWebServerActivity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activities/network/CrossPointWebServerActivity.cpp b/src/activities/network/CrossPointWebServerActivity.cpp index a7160137..dde05614 100644 --- a/src/activities/network/CrossPointWebServerActivity.cpp +++ b/src/activities/network/CrossPointWebServerActivity.cpp @@ -381,7 +381,7 @@ void CrossPointWebServerActivity::renderServerRunning() const { renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, "or scan QR code with your phone to connect to Wifi."); // Show QR code for URL - std::string wifiConfig = std::string("WIFI:T:WPA;S:") + connectedSSID + ";P:" + "" + ";;"; + const std::string wifiConfig = std::string("WIFI:S:") + connectedSSID + ";;"; drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 4, wifiConfig); startY += 6 * 29 + 3 * LINE_SPACING; From 881aa2e005b2f66d76a1ec29a308552e3d41d081 Mon Sep 17 00:00:00 2001 From: Justin <41591399+justinluque@users.noreply.github.com> Date: Mon, 5 Jan 2026 03:25:27 -0500 Subject: [PATCH 3/5] Fix scrolling wrap-around in settings menu (#249) ## Summary Fixes a bug in the settings menu, where previously wrap-around only worked when scrolling upwards. Now, scrolling downwards on the last list element wraps around to the top as expected. Resolves #236. --- src/activities/settings/SettingsActivity.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index a242389d..e6bf24f0 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -114,11 +114,9 @@ void SettingsActivity::loop() { updateRequired = true; } else if (mappedInput.wasPressed(MappedInputManager::Button::Down) || mappedInput.wasPressed(MappedInputManager::Button::Right)) { - // Move selection down - if (selectedSettingIndex < settingsCount - 1) { - selectedSettingIndex++; - updateRequired = true; - } + // Move selection down (with wrap around) + selectedSettingIndex = (selectedSettingIndex < settingsCount - 1) ? (selectedSettingIndex + 1) : 0; + updateRequired = true; } } From c76507c93727596cc448ddbc770c9c9df97f2e07 Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Mon, 5 Jan 2026 10:08:39 +0100 Subject: [PATCH 4/5] Add blank sleep screen option (#242) ## Summary Very small change to add a blank ("None") sleep screen option, for those who prefer a clean aesthetic. Tested on X4 device. --- src/CrossPointSettings.h | 2 +- src/activities/boot_sleep/SleepActivity.cpp | 9 +++++++++ src/activities/boot_sleep/SleepActivity.h | 1 + src/activities/settings/SettingsActivity.cpp | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index bb38df68..84235672 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -16,7 +16,7 @@ class CrossPointSettings { CrossPointSettings& operator=(const CrossPointSettings&) = delete; // Should match with SettingsActivity text - enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3 }; + enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4 }; // Status bar display type enum enum STATUS_BAR_MODE { NONE = 0, NO_PROGRESS = 1, FULL = 2 }; diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index cc9fa9d9..8ae42ff5 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -28,6 +28,10 @@ void SleepActivity::onEnter() { Activity::onEnter(); renderPopup("Entering Sleep..."); + if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::BLANK) { + return renderBlankSleepScreen(); + } + if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM) { return renderCustomSleepScreen(); } @@ -234,3 +238,8 @@ void SleepActivity::renderCoverSleepScreen() const { renderDefaultSleepScreen(); } + +void SleepActivity::renderBlankSleepScreen() const { + renderer.clearScreen(); + renderer.displayBuffer(EInkDisplay::HALF_REFRESH); +} diff --git a/src/activities/boot_sleep/SleepActivity.h b/src/activities/boot_sleep/SleepActivity.h index 3a77d33b..283220ce 100644 --- a/src/activities/boot_sleep/SleepActivity.h +++ b/src/activities/boot_sleep/SleepActivity.h @@ -15,4 +15,5 @@ class SleepActivity final : public Activity { void renderCustomSleepScreen() const; void renderCoverSleepScreen() const; void renderBitmapSleepScreen(const Bitmap& bitmap) const; + void renderBlankSleepScreen() const; }; diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index e6bf24f0..39b1b460 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -12,7 +12,7 @@ namespace { constexpr int settingsCount = 14; const SettingInfo settingsList[settingsCount] = { // Should match with SLEEP_SCREEN_MODE - {"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover"}}, + {"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}}, {"Status Bar", SettingType::ENUM, &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}}, {"Extra Paragraph Spacing", SettingType::TOGGLE, &CrossPointSettings::extraParagraphSpacing, {}}, {"Short Power Button Click", SettingType::TOGGLE, &CrossPointSettings::shortPwrBtn, {}}, From 9f95b31de5735780babda9ab7482a2e268826103 Mon Sep 17 00:00:00 2001 From: David Fischer <85546373+fischer-hub@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:29:08 +0100 Subject: [PATCH 5/5] add settings for reader screen margin (#223) ## Summary * **What is the goal of this PR?** * This PR adds a setting to control the top left and right margins of the reader screen in 4 sizes (5, 10, 20, 40 pt?) and defaults to `SMALL` which is equivalent to the fixed margin of 5 that was already in use before. * **What changes are included?** ## Additional Context * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). --------- Co-authored-by: Dave Allie --- src/CrossPointSettings.cpp | 9 ++- src/CrossPointSettings.h | 4 ++ src/activities/reader/EpubReaderActivity.cpp | 8 +-- src/activities/settings/SettingsActivity.cpp | 75 ++++++++++---------- src/activities/settings/SettingsActivity.h | 27 ++++++- 5 files changed, 75 insertions(+), 48 deletions(-) diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index d304c4e4..94764b0f 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -12,7 +12,7 @@ CrossPointSettings CrossPointSettings::instance; namespace { constexpr uint8_t SETTINGS_FILE_VERSION = 1; // Increment this when adding new persisted settings fields -constexpr uint8_t SETTINGS_COUNT = 13; +constexpr uint8_t SETTINGS_COUNT = 14; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; } // namespace @@ -40,6 +40,8 @@ bool CrossPointSettings::saveToFile() const { serialization::writePod(outputFile, paragraphAlignment); serialization::writePod(outputFile, sleepTimeout); serialization::writePod(outputFile, refreshFrequency); + serialization::writePod(outputFile, screenMargin); + outputFile.close(); Serial.printf("[%lu] [CPS] Settings saved to file\n", millis()); @@ -92,6 +94,9 @@ bool CrossPointSettings::loadFromFile() { if (++settingsRead >= fileSettingsCount) break; serialization::readPod(inputFile, refreshFrequency); if (++settingsRead >= fileSettingsCount) break; + serialization::readPod(inputFile, screenMargin); + if (++settingsRead >= fileSettingsCount) break; + } while (false); inputFile.close(); @@ -207,4 +212,4 @@ int CrossPointSettings::getReaderFontId() const { return OPENDYSLEXIC_14_FONT_ID; } } -} +} \ No newline at end of file diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 84235672..2b3f75a3 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -75,6 +75,9 @@ class CrossPointSettings { // E-ink refresh frequency (default 15 pages) uint8_t refreshFrequency = REFRESH_15; + // Reader screen margin settings + uint8_t screenMargin = 5; + ~CrossPointSettings() = default; // Get singleton instance @@ -89,6 +92,7 @@ class CrossPointSettings { float getReaderLineCompression() const; unsigned long getSleepTimeoutMs() const; int getRefreshFrequency() const; + int getReaderScreenMargin() const; }; // Helper macro to access settings diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index d3cd5016..1b5dc777 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -16,8 +16,6 @@ namespace { // pagesPerRefresh now comes from SETTINGS.getRefreshFrequency() constexpr unsigned long skipChapterMs = 700; constexpr unsigned long goHomeMs = 1000; -constexpr int topPadding = 5; -constexpr int horizontalPadding = 5; constexpr int statusBarMargin = 19; } // namespace @@ -253,9 +251,9 @@ void EpubReaderActivity::renderScreen() { int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft; renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom, &orientedMarginLeft); - orientedMarginTop += topPadding; - orientedMarginLeft += horizontalPadding; - orientedMarginRight += horizontalPadding; + orientedMarginTop += SETTINGS.screenMargin; + orientedMarginLeft += SETTINGS.screenMargin; + orientedMarginRight += SETTINGS.screenMargin; orientedMarginBottom += statusBarMargin; if (!section) { diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 39b1b460..963318a1 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -1,6 +1,7 @@ #include "SettingsActivity.h" #include +#include #include "CrossPointSettings.h" #include "MappedInputManager.h" @@ -9,45 +10,31 @@ // Define the static settings list namespace { -constexpr int settingsCount = 14; +constexpr int settingsCount = 15; const SettingInfo settingsList[settingsCount] = { // Should match with SLEEP_SCREEN_MODE - {"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}}, - {"Status Bar", SettingType::ENUM, &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}}, - {"Extra Paragraph Spacing", SettingType::TOGGLE, &CrossPointSettings::extraParagraphSpacing, {}}, - {"Short Power Button Click", SettingType::TOGGLE, &CrossPointSettings::shortPwrBtn, {}}, - {"Reading Orientation", - SettingType::ENUM, - &CrossPointSettings::orientation, - {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}}, - {"Front Button Layout", - SettingType::ENUM, - &CrossPointSettings::frontButtonLayout, - {"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght"}}, - {"Side Button Layout (reader)", - SettingType::ENUM, - &CrossPointSettings::sideButtonLayout, - {"Prev, Next", "Next, Prev"}}, - {"Reader Font Family", - SettingType::ENUM, - &CrossPointSettings::fontFamily, - {"Bookerly", "Noto Sans", "Open Dyslexic"}}, - {"Reader Font Size", SettingType::ENUM, &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}}, - {"Reader Line Spacing", SettingType::ENUM, &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}}, - {"Reader Paragraph Alignment", - SettingType::ENUM, - &CrossPointSettings::paragraphAlignment, - {"Justify", "Left", "Center", "Right"}}, - {"Time to Sleep", - SettingType::ENUM, - &CrossPointSettings::sleepTimeout, - {"1 min", "5 min", "10 min", "15 min", "30 min"}}, - {"Refresh Frequency", - SettingType::ENUM, - &CrossPointSettings::refreshFrequency, - {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}}, - {"Check for updates", SettingType::ACTION, nullptr, {}}, -}; + SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}), + SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}), + SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing), + SettingInfo::Toggle("Short Power Button Click", &CrossPointSettings::shortPwrBtn), + SettingInfo::Enum("Reading Orientation", &CrossPointSettings::orientation, + {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}), + SettingInfo::Enum("Front Button Layout", &CrossPointSettings::frontButtonLayout, + {"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght"}), + SettingInfo::Enum("Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout, + {"Prev, Next", "Next, Prev"}), + SettingInfo::Enum("Reader Font Family", &CrossPointSettings::fontFamily, + {"Bookerly", "Noto Sans", "Open Dyslexic"}), + SettingInfo::Enum("Reader Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}), + SettingInfo::Enum("Reader Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}), + SettingInfo::Value("Reader Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}), + SettingInfo::Enum("Reader Paragraph Alignment", &CrossPointSettings::paragraphAlignment, + {"Justify", "Left", "Center", "Right"}), + SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout, + {"1 min", "5 min", "10 min", "15 min", "30 min"}), + SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency, + {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}), + SettingInfo::Action("Check for updates")}; } // namespace void SettingsActivity::taskTrampoline(void* param) { @@ -57,7 +44,6 @@ void SettingsActivity::taskTrampoline(void* param) { void SettingsActivity::onEnter() { Activity::onEnter(); - renderingMutex = xSemaphoreCreateMutex(); // Reset selection to first item @@ -67,7 +53,7 @@ void SettingsActivity::onEnter() { updateRequired = true; xTaskCreate(&SettingsActivity::taskTrampoline, "SettingsActivityTask", - 2048, // Stack size + 4096, // Stack size this, // Parameters 1, // Priority &displayTaskHandle // Task handle @@ -135,6 +121,15 @@ void SettingsActivity::toggleCurrentSetting() { } else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) { const uint8_t currentValue = SETTINGS.*(setting.valuePtr); SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast(setting.enumValues.size()); + } else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) { + // Decreasing would also be nice for large ranges I think but oh well can't have everything + const int8_t currentValue = SETTINGS.*(setting.valuePtr); + // Wrap to minValue if exceeding setting value boundary + if (currentValue + setting.valueRange.step > setting.valueRange.max) { + SETTINGS.*(setting.valuePtr) = setting.valueRange.min; + } else { + SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step; + } } else if (setting.type == SettingType::ACTION) { if (std::string(setting.name) == "Check for updates") { xSemaphoreTake(renderingMutex, portMAX_DELAY); @@ -193,6 +188,8 @@ void SettingsActivity::render() const { } else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) { const uint8_t value = SETTINGS.*(settingsList[i].valuePtr); valueText = settingsList[i].enumValues[value]; + } else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) { + valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr)); } const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str()); renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), i != selectedSettingIndex); diff --git a/src/activities/settings/SettingsActivity.h b/src/activities/settings/SettingsActivity.h index 83beb9d9..157689e3 100644 --- a/src/activities/settings/SettingsActivity.h +++ b/src/activities/settings/SettingsActivity.h @@ -11,14 +11,37 @@ class CrossPointSettings; -enum class SettingType { TOGGLE, ENUM, ACTION }; +enum class SettingType { TOGGLE, ENUM, ACTION, VALUE }; // Structure to hold setting information struct SettingInfo { const char* name; // Display name of the setting SettingType type; // Type of setting - uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings (for TOGGLE/ENUM) + uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings (for TOGGLE/ENUM/VALUE) std::vector enumValues; + + struct ValueRange { + uint8_t min; + uint8_t max; + uint8_t step; + }; + // Bounds/step for VALUE type settings + ValueRange valueRange; + + // Static constructors + static SettingInfo Toggle(const char* name, uint8_t CrossPointSettings::* ptr) { + return {name, SettingType::TOGGLE, ptr}; + } + + static SettingInfo Enum(const char* name, uint8_t CrossPointSettings::* ptr, std::vector values) { + return {name, SettingType::ENUM, ptr, std::move(values)}; + } + + static SettingInfo Action(const char* name) { return {name, SettingType::ACTION, nullptr}; } + + static SettingInfo Value(const char* name, uint8_t CrossPointSettings::* ptr, const ValueRange valueRange) { + return {name, SettingType::VALUE, ptr, {}, valueRange}; + } }; class SettingsActivity final : public ActivityWithSubactivity {