Compare commits

...
Author SHA1 Message Date
Justin Mitchell a39feb4a88 Add support for wrapped subtitles
Update freeink-sdk submodule from d6d40be to 58d34b3.
2026-08-13 05:35:47 -04:00
Justin Mitchell b1dec6491d Remove unused BoardPaperS3 include from HalPowerManager 2026-08-13 05:28:55 -04:00
Justin Mitchell 1915457562 Add server-side search support to plugin catalogs
Implements search functionality for plugin catalogs with configurable search URL and body templates. Adds search icon to browsing header, keyboard entry for queries, and URL encoding for query parameters. Search results use the same item format as browse results. Includes navigation improvements to prevent button press fall-through and proper authentication gating for search requests.
2026-08-13 05:27:02 -04:00
Justin Mitchell 54561e8df3 Fix plugin job lease expiration and completion retry
Separate job lease expiration from slot reuse logic. Jobs that exceed the lease timeout are now requeued as PENDING instead of being treated as stale slots. Add retry logic for job completion requests - if completion fails, it will be retried on the next poll. Also allow completion endpoint to be idempotent by accepting already-completed jobs.
2026-08-13 03:47:13 -04:00
Justin Mitchell 06f6ecc27b Report download progress even when total size is unknown
Remove the check for sink.total > 0 before calling progress callback. This allows progress to be reported for downloads where the total size is not known in advance (e.g., chunked transfer encoding).
2026-08-12 16:00:24 -04:00
Justin Mitchell 58932354bd Handle short writes when saving plugin tokens
Check that the full token was written to storage. If a short write occurs (e.g., SD card full or I/O fault), remove the truncated token file and return false to prevent pollAuth from reporting success while later loadToken() calls would fail.
2026-08-12 15:52:05 -04:00
Justin Mitchell 47021955d4 Unify confirm button labels for pager and fetch actions
Standardize the footer hint behavior across OPDS browser and plugin catalog. Pager rows (prev/next) and book/item entries now consistently show 'Fetch' for server operations and 'Open' for local navigation. Track pager row presence with boolean flags to determine correct label.
2026-08-12 15:31:46 -04:00
Justin Mitchell 7a5f12863c Remove arrow symbols from pagination strings
Remove the « and » arrow symbols from STR_NEXT_PAGE and STR_PREV_PAGE translation strings across all languages. This simplifies the pagination UI text to just the page navigation labels without directional indicators.
2026-08-12 15:25:31 -04:00
Justin Mitchell c64b0ceec2 Add shared UI screen builders for catalog activities
Introduces reusable FreeInkUI screen components for OPDS book browser and SD-plugin catalogs. Provides functions for rendering themed headers with optional action buttons, centered message blocks, download progress screens, and list bodies with viewport syncing.
2026-08-12 15:15:42 -04:00
Justin Mitchell 69d7323888 Merge remote-tracking branch 'origin/develop' into feat-sd-web-plugins 2026-08-12 14:58:08 -04:00
Julia e970abae89 feat: skip boot screen on startup (#2989) 2026-08-12 21:45:21 +03:00
Justin Mitchell 9e3cea2e54 feat: convert all lists and tabs to FUI (#2957) 2026-08-12 20:40:09 +03:00
Justin Mitchell 640e421e5d Refactor plugin catalog and webserver IO
Consolidates repeated IO, HTTP and error-handling logic across plugin/catalog and webserver code. Key changes:
- lib/Epub/Epub.cpp: use extractItemToFile for cover extraction instead of manual write/read.
- src/activities/plugins/PluginCatalogActivity.*: add FsHelpers, pick(), use decodeUriEscapes, introduce fail(), beginLoading(), substitutedHeaders(), openApiClient(), and centralize apiRequest; replace many duplicated header builds and error-state code.
- src/activities/plugins/PluginInfoActivity.cpp: reserve() paragraphs/lines to reduce reallocations.
- src/network/CrossPointWebServer.*: add streamFileToClient() and readJsonBody() to centralize file streaming and JSON body parsing; include ArduinoJson in header.
Reduces duplication, improves robustness and minor perf gains.
2026-08-11 23:59:48 -04:00
Justin Mitchell 94400ed2ea Limit random allocation; ensure plugin dirs
Prevent runaway allocations and improve plugin FS handling.

- src/network/CrossPointWebServer.cpp: In handleCrypto() clamp requested random byte length to a new kMaxRandomBytes (4096) and allocate exactly n bytes to avoid large/unbounded allocations. Also, in handlePluginFs() ensure the parent directory for the requested path is created by calling Storage.ensureDirectoryExists(path.substr(0, lastSlash)) instead of only /.crosspoint, covering nested plugin paths.

- src/network/html/shared/PluginHost.js.inc: Replace innerHTML error string with safe DOM construction and container.replaceChildren(...) to avoid HTML injection and ensure consistent DOM updates.
2026-08-11 23:40:32 -04:00
Justin Mitchell b5a9411a8d Merge remote-tracking branch 'origin/develop' into feat-sd-web-plugins 2026-08-11 18:01:17 -04:00
Justin Mitchell 096223f808 Reformat code for improved readability
Break long lines and switch cases onto multiple lines to improve code readability. No functional changes.
2026-08-11 17:59:36 -04:00
Justin Mitchell ec0cd48b76 Improve plugin catalog navigation and labels
Replace hardcoded '> ' prefix with direction markers from translated strings. Change 'Download' button to 'Fetch' for consistency. Add up/down navigation labels when multiple items exist. This allows better localization of navigation UI elements.
2026-08-11 17:05:50 -04:00
Justin Mitchell 45abc21f6d Stream catalog responses to temp file to reduce RAM
Browse responses (JSON/XML) now stream to an SD temp file instead of being buffered in memory, allowing large catalog pages (60+ KB) to work on low-heap devices. XML parsing reads the file in 2KB chunks. Also adds optional browse.lists feature for named sub-catalogs with a picker UI, and keeps TLS session alive across requests to avoid heap fragmentation.
2026-08-11 16:49:51 -04:00
Thiago Kenji Okada 86eb1cbde2 fix: properly link AGENTS.md (#2976) 2026-08-11 22:24:24 +03:00
Uri Tauber 2cac597143 fix symlink AGENTS.md 2026-08-11 08:24:13 +03:00
Julia 7212129318 fix: ignore empty watchdog reports (#2968)
## Summary

* **What is the goal of this PR?**
* Prevent reset-only watchdog events from opening the crash-report flow
with an empty panic reason.

* **What changes are included?**
* Record a marker in RTC memory only when panic diagnostics are
captured.
  * Continue reporting explicit panics and CPU lockups.
* Require that marker before treating watchdog resets as crashes, and
clear it with the existing panic state.


## Scope Check

CrossPoint is intentionally narrow. See
[SCOPE.md](../blob/master/SCOPE.md) and
[ROADMAP.md](../blob/master/ROADMAP.md).
Please confirm:

- [x] I have read SCOPE.md and ROADMAP.md.
- [x] This PR is **not** a new built-in theme (themes are temporarily
closed pending the move to SD-loaded themes).
- [x] This PR is **not** a new external network connector (sync engine,
cloud storage, remote file access, etc.).
- [x] This PR is **not** an interactive app, writing tool,
RSS/news/browser, media playback, or PDF feature.
- [x] The stock firmware does not already handle this well, **and** no
other popular CrossPoint fork already does
      (or, if one does, I explain why CrossPoint still needs it below).
- [x] If this PR touches `freeink-sdk/`, `lib/hal/`, the bootloader,
OTA, or recovery code, I have coordinated with
      the relevant maintainer.

**If this PR was opened against the previous (broader) scope and was
already in flight under Phase 0, link the
relevant Discussion or issue so reviewers can see the history.**

## Additional Context

* This is a focused port of CrossInk commit `c96d4275`.
* The change adds 4 bytes of RTC no-init state; it does not affect
storage formats, normal boot routing, or real panic reporting.
* Hardware follow-up: on an X3/X4, confirm a reset-only watchdog boots
normally without an empty crash report, while a genuine panic still
reaches the crash-report screen.
---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**<YES>**_
2026-08-11 03:02:56 +00:00
Justin Mitchell 9220e5a301 Refactor plugin discovery with structured scanning
Replace inline findPluginDir with scanPlugins() that returns structured Entry objects containing plugin metadata (plugin.js, device.json, manifest.json markers). This centralizes plugin discovery logic and provides a single source of truth for what constitutes a valid plugin across all roots.
2026-08-10 20:32:12 -04:00
Justin Mitchell 28302714df Add multi-file bundle download support to plugin catalog
Enables downloading multiple files from a catalog item instead of just one. Each item can specify a base URL and an array of relative file paths. All files are fetched into a subdirectory with path traversal protection. This supports use cases like plugin installers and theme packs that consist of multiple files.
2026-08-10 19:31:29 -04:00
Justin Mitchell cb22cb5291 Merge remote-tracking branch 'origin/develop' into feat-sd-web-plugins 2026-08-10 16:36:27 -04:00
Justin Mitchell 9b090266cf fix: MAC address reading when WiFi is off (#2960) 2026-08-10 15:38:58 -04:00
William Floyd e9c3a166f5 feat: StarDict .syn synonym lookup support (#2696) 2026-08-10 20:01:28 +03:00
Justin Mitchell d720891b2f feat: Adds dark mode with image polarity preservation (#2705) 2026-08-10 19:58:22 +03:00
e4aae7baef feat: retain custom sleep screen during wake (#2943)
Co-authored-by: Bridger Brundy <bridgerborpcc@gmail.com>
Co-authored-by: Julia <julia@uxj.io>
2026-08-10 17:02:20 +03:00
Uri Tauber 7f8b005ae1 chore: update AI agents instructions 2026-08-10 16:06:51 +03:00
Serena 81028d5849 fix(ui): wrap button hint labels that overflow their button (#2928) 2026-08-10 12:05:27 +03:00
Justin Mitchell 0423276d2c fix: accept any 2xx from KOSync-compatible servers (#2945) 2026-08-10 09:26:37 +03:00
Leopoldo Pla Sempere 596f0d3f71 fix: invalidate stale footnote caches (#2935) 2026-08-09 19:27:48 +03:00
hooligan333andErica Jensen 6af9a04934 perf: cache cumulative spine sizes in RAM (#2441)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
2026-08-09 17:31:42 +03:00
François Chaix 1582e70ef4 fix(epub): bump FOOTNOTE_HREF_LEN 96→256 for long calibre paths (#2722) 2026-08-09 17:23:40 +03:00
Uri Tauber d4733bfc65 chore: split GH build action (#2901)
## Summary

Build action take too much time.
Lets see if splitting it to two seperate action would speed things up.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< PARTIALLY >**_
2026-08-09 03:55:33 -04:00
Justin Mitchell ad30652f52 Add download completion state to plugin catalog
Introduces a DONE state to show download completion message with confirmation before returning to browsing. Refactors layout calculations into a reusable CatLayout structure that uses themed metrics for consistent list geometry across screens. Removes hardcoded visibleRows() method in favor of layout-based page item calculation.
2026-08-07 03:45:53 -04:00
Justin Mitchell dbcdace3cf Add plugin menu to home screen when plugins installed
The home screen now shows a Plugins menu item (instead of OPDS Browser) when any plugin is installed. The library slot dynamically switches between Plugins and OPDS Browser based on plugin presence. Also refactors PluginInfoActivity to use theme-based geometry and defers text wrapping until render time when font metrics are available.
2026-08-07 03:20:54 -04:00
Uri Tauber e00f5958df feat: guard against cross-chip firmware installs (#2880) 2026-08-07 09:42:29 +03:00
Justin Mitchell 41b5c2e498 Add plugin discovery and info screen documentation
Documents the new plugin discovery system where all plugins appear in Settings → System → Plugins with title/description, and selecting one shows an info screen with README and optional Open action. Also adds guidance on choosing between on-device (device.json) vs browser (plugin.js) implementations with concrete examples for each approach.
2026-08-07 02:32:29 -04:00
Justin Mitchell cec96908c2 Add password grant OAuth flow for plugins
Adds support for silent password-based OAuth authentication alongside the existing device code flow. Password grant automatically mints tokens from stored credentials and refreshes them on 401/403 responses, enabling standalone reader operation after initial web-based credential setup.
2026-08-07 01:57:12 -04:00
Justin Mitchell 529c8a6398 Update freeink-sdk submodule
Update freeink-sdk submodule from e52d480 to a485dc4.
2026-08-07 01:37:12 -04:00
Justin Mitchell e7da410a10 Add XML browse format support for SD plugins
Introduces an alternative XML browse format alongside the existing JSON format. XML mode supports folder navigation, WebDAV/OPDS feeds, and custom element selectors. Includes config file templating for user credentials, URL resolution, and file extension filtering. XML responses are limited to 200 entries per folder due to 48KB RAM constraint.
2026-08-07 01:01:41 -04:00
Justin Mitchell 217def55bf Add utility for locating plugins in multiple SD roots
Introduces PluginLocations namespace with findPluginDir() function that searches for plugins across multiple SD card directories. Supports legacy /.crosspoint/plugins path and user-friendly /plugins and /.plugins locations, with earlier roots taking precedence on name collisions.
2026-08-06 22:40:30 -04:00
Justin Mitchell 8bffe852fd Add plugin catalog browser activity
Implements a generic on-device catalog browser driven by plugin device.json manifests. Allows SD card plugins to define browsing/downloading behavior via declarative JSON templates (URL/header/body with token/pagination support) without requiring firmware changes. Includes WiFi checking, catalog browsing, and download states.
2026-08-06 22:34:25 -04:00
Justin Mitchell 000824f5ca Add plugin job queue system with REST API
Implements a fixed-size job queue for SD-plugin execution via external systems. Jobs are submitted via REST API, claimed by browser-hosted plugin runners, and results posted back. Includes stale job detection (10min timeout) and automatic slot recycling for oldest finished jobs. Adds /plugins-run headless executor page and five API endpoints for job lifecycle management.
2026-08-06 21:53:33 -04:00
Justin Mitchell 66ec7dbe4c Update freeink-sdk submodule
Update freeink-sdk submodule from a485dc4 to e52d480.
2026-08-06 21:42:42 -04:00
lranslot 255bab311a fix: read platformio.ini as UTF-8 in git_branch.py (#2897) 2026-08-07 02:05:45 +03:00
Justin Mitchell c507e54478 fix: thread-safety issues in credential store (#2834) 2026-08-07 01:16:22 +03:00
Uri Tauber 5eec70de36 add IPA intervals to SD fonts 2026-08-07 01:08:29 +03:00
+19 28f3260c51 chore: update translations (#2740)
Co-authored-by: Leopoldo Pla Sempere <leopoldo.pla@treelogic.com>
Co-authored-by: muhas <muhas@muhas.ru>
Co-authored-by: Matteo Scopel <matteo@scopel.email>
Co-authored-by: CookieCaptainD <65847023+CookieCaptainD@users.noreply.github.com>
Co-authored-by: Poc031205 <165477165+Poc031205@users.noreply.github.com>
Co-authored-by: Rodrigo Matsuura <matsuura@outlook.com.br>
Co-authored-by: Spigaw <73850535+Spigaw@users.noreply.github.com>
Co-authored-by: Tom-Inge Larsen <tom.inge@gmail.com>
Co-authored-by: Baris Albayrak <80099286+barbarhan@users.noreply.github.com>
Co-authored-by: Barış Albayrak <barisa@pop-os.lan>
Co-authored-by: Hendra Kusumah <h.kusumah87@gmail.com>
Co-authored-by: thehijacker <thehijacker@gmail.com>
Co-authored-by: Andrej Kralj <andrej.kralj@gmail.com>
Co-authored-by: Bastian <391732+dasrecht@users.noreply.github.com>
Co-authored-by: th0m4sek <53303628+th0m4sek@users.noreply.github.com>
Co-authored-by: Dexif <spev@gurtam.team>
Co-authored-by: Mirus <mirusim@gmail.com>
Co-authored-by: Mirza Arnaut <mirza.arnaut45@gmail.com>
Co-authored-by: Husam Younis <youhusam@gmail.com>
Co-authored-by: Ing. Jan Kaláb <jan+github@kalabovi.org>
Co-authored-by: Tadas <tadasss@gmail.com>
Co-authored-by: winst0niuss <chumachenko.vadym@gmail.com>
Co-authored-by: fsocietyipa <fsociety.ipa@gmail.com>
Co-authored-by: Okzhetpes <okzhetpes.valikhanov@gmail.com>
Co-authored-by: KymAndriy <32357131+KymAndriy@users.noreply.github.com>
Co-authored-by: KymAndriy <test@notamail.ua>
Co-authored-by: Martin <35097765+neresus@users.noreply.github.com>
Co-authored-by: Hoang Manh Linh <jetaudio.media@gmail.com>
2026-08-06 23:39:05 +03:00
Phạm Bình An 046827fc31 fix(epub): ruby overflows right border when justifying text, unwanted short lines in Chinese and Japanese (#2781) 2026-08-06 23:26:09 +03:00
Uri Tauber fecb9eb3a4 fix: trim curly quotes and dashes in dictionary lookup (#2877) 2026-08-05 15:37:41 -04:00
Jesse Vincent 53232eefab fix: recognize watchdog-triggered resets as panics (#2830) 2026-08-05 22:02:41 +03:00
winst0niuss cc96e3c538 fix: size translated-label buffers for UTF-8, not ASCII (#2850) 2026-08-05 21:51:37 +03:00
Justin Mitchell 506ce26d05 Fix line formatting in LOG_ERR call
Consolidate multi-line LOG_ERR statement into a single line for better code readability.
2026-08-05 13:14:05 -04:00
Justin Mitchell 2a5f3d2a54 Merge remote-tracking branch 'origin/develop' into feat-sd-web-plugins
# Conflicts:
#	src/network/html/SettingsPage.html
2026-08-05 13:08:13 -04:00
Justin Mitchell 4e61903578 fix: add support for new x3 display, x3 battery drain fix, and crypto support (#2864) 2026-08-04 12:08:14 -04:00
Jesse Vincent ba1b849f3d fix: load Settings page's Wi-Fi/OPDS/settings data sequentially (#2831)
## Summary

* **What is the goal of this PR?** Fix a reproducible crash: opening the
web UI's Settings page reliably crashes the device (silent reboot to
Home) under real-world conditions.
* **What changes are included?** `SettingsPage.html` fired three
unawaited `fetch()` calls on page load (`/api/settings`, `/api/wifi`,
`/api/opds`), opening simultaneous connections to the device's web
server. That server (Arduino's `WebServer`) handles one client at a
time; a genuinely concurrent second/third connection can stall a
response mid-stream for multiple seconds — long enough to trip the
firmware's watchdogs (300ms interrupt WDT / 5s task WDT, both
panic-on-timeout in this project's config). This PR makes the three
initial loads run sequentially via a small async IIFE instead of firing
them concurrently. Same end state for the page, no visible behavior
change.

## Scope Check

CrossPoint is intentionally narrow. See
[SCOPE.md](../blob/master/SCOPE.md) and
[ROADMAP.md](../blob/master/ROADMAP.md).

- [x] I have read SCOPE.md and ROADMAP.md.
- [x] This PR is **not** a new built-in theme.
- [x] This PR is **not** a new external network connector.
- [x] This PR is **not** an interactive app, writing tool,
RSS/news/browser, media playback, or PDF feature.
- [x] This is a bug fix for a reproducible crash, not new functionality.
- [x] Does not touch `freeink-sdk/`, `lib/hal/`, the bootloader, OTA, or
recovery code.

## Testing performed

Verified live on an X4 (driven via the `CMD:WIFI` serial debug command
from the companion PR, since I didn't have physical access to the device
for part of this investigation):

- Root-caused via a temporary per-entry heap/timing log in
`handleGetSettings()` (not included in this diff): under concurrent
load, individual `sendContent()` calls stalled 1-3+ seconds between
simple, static settings entries that should serialize in well under a
millisecond each.
- Reproduced the crash directly: firing `/api/settings`, `/api/wifi`,
and `/api/opds` concurrently (mirroring the page's old JS behavior)
crashed the device in 2/2 attempts. The companion PR (watchdog reset
detection) let the second crash actually get logged: reset reason
`ESP_RST_TASK_WDT`, panic confirmed.
- After the fix: same three endpoints, hit sequentially (matching the
new JS), succeeded cleanly across dozens of repeats (single-shot and a
60-request hammer loop) with no crashes and stable free-heap.
- Confirmed the live device served the updated, gzip-compressed
generated header after a normal `pio run` (source HTML →
`SettingsPageHtml.generated.h` via the existing `scripts/build_html.py`
build step).

## Additional Context

Memory/flash impact: none — same JS logic, just sequenced instead of
concurrent. No new allocations, no change to the generated header's size
beyond the few added bytes of source text.

Risk: low. This only changes the *order* three existing fetches happen
in on page load; no new endpoints, no changed request/response shapes.

---

### AI Usage

YES — diagnosed and fixed by Claude (Sonnet 5), including live
reproduction and verification against real hardware.
2026-08-03 16:46:24 -04:00
Uri Tauber 9c48609f8b Make bookmarks survive re-pagination
Ties bookmarks to the actual text on the page rather than to a page number. Page numbers shift whenever the book gets re-laid-out — changing font size, margins, line spacing, or rotating the screen — which could previously land you on the wrong spot.

This is a followup of #2805, making bookmarks use the same mechanism. Several minor improvments ride along.
2026-08-02 00:06:12 +03:00
Thiago Kenji Okada 0f747b82a1 fix: make EPUB sync positions content-based (#2805) 2026-08-01 23:37:07 +03:00
Justin Mitchell 5acd49fb10 Merge remote-tracking branch 'origin/develop' into feat-sd-web-plugins 2026-07-31 12:33:27 -04:00
Bob Vincent 1a7f5a9e38 perf: share one static bidi scratch buffer to save ~1.5 KB RAM (#2554) 2026-07-31 19:07:39 +03:00
Uri Tauber 5933da8f1d perf: check dictionary sidecar freshness once per open
needsIndex() opens .idx and .qidx and re-reads the sidecar header on every
call, and DictionaryWordSelectActivity called it once per tapped word rather
than once per dictionary open. A lookup therefore cost 4 SD opens and 2
std::string path temporaries, not the 2 and 0 the previous change set out to
achieve.

Ask needsIndex() once, inside the same guarded block that opens the
dictionary, and feed the build result back so a successful build stops
re-triggering while a failed one still retries on the next word.

Reimplement needsIndex() on openSession() so the "is the sidecar usable?"
rule lives in one place rather than two that have to be kept in step. This
is behaviour-preserving for everything buildIndex() can produce: it always
writes at least one sample (entry 0) and removes the file on failure, so
sampleCount == 0 means absent, stale or corrupt, all of which rebuild. The
one divergence is an improvement -- a corrupt sidecar with a valid magic
previously reported fresh and then full-scanned every lookup forever, and
now rebuilds once.

Also make the open()-time length bound a compile-time constant instead of
two runtime strlen() calls, and correct the buildPath() comment, which
claimed a lookup allocates nothing at all -- cleanWord(), stemVariants() and
the matched headword all still do.
2026-07-31 15:46:42 +03:00
William Floyd bf253b15af perf: open dictionary index files once per lookup (#2733) 2026-07-31 15:27:57 +03:00
William Floyd 47466c775d fix: dictionary lookup OOM on .dict.dz definitions (#2791) 2026-07-31 14:16:55 +03:00
Justin Mitchell d0b70b3f9f fix: Replace vector with deque for text token storage (#2814) 2026-07-31 09:42:27 +03:00
Eduard 74065f10a1 fix: avoid duplicate OTA update actions (#2807) 2026-07-31 01:46:29 +03:00
Uri Tauber b215ed86e5 make OptionPopup orientation-aware 2026-07-31 01:30:51 +03:00
Thiago Kenji Okada 5c26fa7c1d fix: orient dictionary navigation buttons (#2749) 2026-07-31 01:18:16 +03:00
Justin Mitchell b19503bfad fix: Properly power down SD power rails on x3 (#2808)
Fixes a regression in the sticky device support that lumped GPIO13 into
a device check that never powered down the SD rails on this device.
2026-07-30 18:13:47 -04:00
Justin Mitchell 13a077dfb3 fix: Save text settings immediately on each change (#2806) 2026-07-30 22:13:19 +03:00
Bastian 3eeb6acd87 fix: order languages by native name instead of language code (#2803) 2026-07-30 14:59:38 +00:00
Justin Mitchell b4b4e8fef2 Add chunked resumable downloads for fetch API
Implement segmented file downloads to prevent mobile browser timeouts on large files. The server now supports offset-based resumption and segment size limits (4MB max). The client automatically chunks requests into 2MB segments and resumes from the last written offset if interrupted. This prevents browsers from terminating long-running downloads while allowing the device to continue fetching in manageable chunks.
2026-07-29 18:46:13 -04:00
Justin Mitchell af688c56ed Reduce heap logging frequency in web server
Increase heap log interval from 64KB to 1MB to reduce logging overhead and improve performance during data transmission.
2026-07-29 18:15:28 -04:00
Justin Mitchell a41c4ae02e Add keep-alive mechanism for long-running fetch requests
Prevents mobile browsers from discarding HTTP responses during long upstream downloads by sending periodic whitespace chunks. Implements chunked transfer encoding that starts after 5 seconds and sends heartbeat data every 5 seconds to maintain the browser connection. Refactors header handling and response sending into helper lambdas for cleaner code.
2026-07-29 18:14:32 -04:00
Justin Mitchell 0ca93a8653 Release WebServer request memory after handling
Introduce CrossPointHttpServer subclass to expose memory release for parsed request arguments and POST bodies. This prevents large JSON request bodies from remaining resident between requests, freeing memory for outbound TLS operations. Add explicit cleanup calls before TLS handshakes and improve heap logging to track max allocatable block size.
2026-07-29 17:49:52 -04:00
Justin Mitchell f3d4702fa8 Merge remote-tracking branch 'origin/develop' into feat-sd-web-plugins 2026-07-29 16:28:26 -04:00
Justin Mitchell 2e9e643ab0 Refactor watchdog pause/restore into class methods
Extract lambda functions for pausing and restoring the web task watchdog into proper class methods. This improves code reusability and adds error checking for watchdog operations. The watchdog is paused during HTTP transactions to prevent timeouts when NetworkClient::write() blocks for up to 10 seconds, which exceeds the 5-second watchdog window.
2026-07-29 15:29:57 -04:00
Justin Mitchell 2ceeeccd5a fix: Restrict CrossPoint position extension to official server (#2790) 2026-07-29 18:22:38 +03:00
Uri Tauber 67e608e828 feat: Access text settings from epub reader (#2788) 2026-07-29 13:03:55 +00:00
Thiago Kenji Okada dadc8ec2eb fix: clean image page after sync return (#2747) 2026-07-29 14:36:20 +03:00
Uri Tauber 9163149adc fix: restore spaces between CJK words (#2768) 2026-07-29 08:29:30 +03:00
Leopoldo Pla Sempere 187feda47d fix: derive file-transfer WebSocket port from HTTP (#2729)
## Summary

* **What is the goal of this PR?**
* Preserve the firmware's adjacent HTTP/WebSocket port contract when the
web
    UI is served from a mapped host port in the desktop simulator.
* **What changes are included?**
  * Use the current page's HTTP port, defaulting to 80 on-device.
* Connect WebSocket uploads to the next port instead of always using
host
    port 81.

## Scope Check

- [x] I have read SCOPE.md and ROADMAP.md.
- [x] This PR is **not** a new built-in theme.
- [x] This PR is **not** a new external network connector.
- [x] This PR is **not** an interactive app, writing tool, browser,
media, or
      PDF feature.
- [x] The current file-transfer page does not handle simulator port
mapping and
      no pending PR fixes it.
- [x] This PR does not touch `freeink-sdk/`, `lib/hal/`, bootloader,
OTA, or
      recovery code.

## Additional Context

On hardware the page is served from port 80 and WebSocket remains port
81, so
device behavior is unchanged. In the simulator, a page served from 8080
now
uses 8081; a configured 18080 listener uses 18081.

Validation:

- Built the real CrossPoint file-transfer server in the simulator.
- Loaded the page through `http://127.0.0.1:18080/`.
- Verified a WebSocket upgrade on port 18081 returns HTTP 101.
- Exercised WebSocket upload progress and the HTTP fallback path.
- `git diff --check`.

Memory impact: no firmware heap allocation is added. The change is
JavaScript
inside the existing compressed web asset.

---

### AI Usage

Did you use AI tools to help write this code? **YES**

AI tools were used to reproduce the browser/simulator port mismatch and
verify
the HTTP and WebSocket listeners together.


## Emulator disclosure

The mapped-port HTTP/WebSocket behavior was reproduced and validated
with the desktop emulator on Intel macOS. The on-device port-80/81
behavior was established from the unchanged firmware contract; no
physical device was used for runtime validation.
2026-07-28 18:17:08 -04:00
William Floyd fe37bf80ab perf: skip ruby scratch allocations on ruby-less lines (#2735) 2026-07-29 00:33:55 +03:00
Justin Mitchell 5713432004 fix: Gyro not powering down on x3 and newer x4 battery latch issues (#2774)
Removes X3ProbeResult struct and associated I2C device detection
functions (probeBQ27220Signature, probeDS3231Signature,
probeQMI8658Signature) along with helper function readI2CReg8 in favor
of an SDK driven device detect API
2026-07-28 16:34:59 -04:00
Leopoldo Pla Sempere 5965dc67a8 chore: remove redundant wolfSSL TLS defines (#2727)
## Summary

* **What is the goal of this PR?**
* Remove the macro-redefinition warning storm from the normal ESP32-C3
build
    without changing enabled TLS features.
* **What changes are included?**
  * Stop repeating four TLS feature macros already enabled by
    Arduino-wolfSSL's `user_settings.h` when `WOLFSSL_TLS13` is set.
  * Document which configuration layer owns those features.

## Scope Check

- [x] I have read SCOPE.md and ROADMAP.md.
- [x] This PR is **not** a new built-in theme.
- [x] This PR is **not** a new external network connector.
- [x] This PR is **not** an interactive app, writing tool, browser,
media, or
      PDF feature.
- [x] No issue or pending PR removes these redundant definitions.
- [x] This PR does not touch `freeink-sdk/`, `lib/hal/`, bootloader,
OTA, or
      recovery code.

## Additional Context

`HAVE_TLS_EXTENSIONS`, `HAVE_SUPPORTED_CURVES`, `HAVE_HKDF`, and
`WC_RSA_PSS` remain enabled by the installed Arduino-wolfSSL
configuration.
This removes duplicate command-line definitions; it does not disable
those
features.

Validation:

- Full ESP32-C3 `default` rebuild passed.
- Reported size: 52,028 bytes RAM and 5,505,149 bytes flash.
- The wolfSSL macro-redefinition warnings are gone.
- The only remaining warning in that build was an unrelated deprecated
  `NetworkClient::flush()` call inside the vendored WebSockets library.
- `git diff --check`.

Memory/flash impact: no intentional change. The effective preprocessor
feature
set is unchanged.

---

### AI Usage

Did you use AI tools to help write this code? **YES**

AI tools were used to trace the duplicate definitions to the library
configuration and compare the full build output before and after
cleanup.


## Emulator disclosure

The ESP32-C3 firmware was compiled locally and the warning output was
checked on Intel macOS. Runtime smoke testing used the desktop emulator;
this PR was not flashed to a physical X3 or X4.
2026-07-28 15:53:20 -04:00
Uri Tauber 52ff942ab5 perf: drop all-empty ruby vectors from TextBlock (#2772) 2026-07-28 14:49:22 -04:00
Tsz Ho Sze b508f75e79 fix: treat <br> in CJK flowing text as a paragraph separator (#2710) 2026-07-28 21:34:55 +03:00
Paul Portocarrero f39d35d748 docs: Add instructions to fix a bricked Xteink (#2622) (#2682) 2026-07-28 20:24:07 +03:00
Justin Mitchell d992bc0ff4 Add methods to suspend/resume transfer services
Introduces suspendTransferServices() and resumeTransferServices() methods to temporarily halt WebSocket server and discovery UDP during fetch operations. This frees up buffer memory for TLS operations, which require approximately 4.4KB of heap space during large transfers.
2026-07-27 23:45:23 -04:00
Justin Mitchell 27ec21dbb8 Feed task watchdog during slow HTTP requests
Add watchdog feeding callback to prevent WDT timeouts when blocking on slow peer responses (e.g., account sign-in >5s). The callback is passed to SecureHttpClient which polls it in wait loops to keep the task watchdog fed without aborting the request.
2026-07-27 23:36:11 -04:00
Justin Mitchell d6600ed0a8 Improve download resilience with smarter retry logic
Replace single retry counter with separate stalled-attempt and total-attempt limits. This allows downloads to recover from early truncations (common in first ~100KB during heap settling) while still protecting against dead servers. Also removes HAVE_MAX_FRAGMENT flag due to memory corruption issues with ADEPT servers and removes unused decrypt method from ContentProtection.
2026-07-27 23:19:26 -04:00
Justin Mitchell ac74a60efb Enable TLS max fragment length negotiation
Configure wolfSSL to negotiate 2KB TLS records instead of default 16KB to avoid memory allocation failures during large SD downloads when heap is constrained to ~20KB free.
2026-07-27 22:32:41 -04:00
Justin Mitchell 7a89475c80 Increase HTTP timeout for fulfillment server downloads
Fulfillment servers can stall mid-body while assembling books on the fly. The default 15s timeout was truncating these downloads, so increase to 60s to allow packaging to complete.
2026-07-27 22:00:01 -04:00
Justin Mitchell 41922b55c5 Detect and report truncated file downloads
Add validation to detect incomplete HTTP responses after successful status codes. When a download is truncated due to transport issues or timeouts, delete the partial file and return a 502 error with details about whether it was an SD write failure or download truncation. This prevents partial files from appearing as successful downloads.
2026-07-27 21:59:12 -04:00
Justin Mitchell 31f2bb71ae Add memory safety checks to prevent OOM aborts
Implement safeguards against out-of-memory conditions in DRM decryption and HTTP relay. Cap rights document size at 64KB with heap check before allocation. Limit HTTP relay responses to 32KB with streaming validation to prevent unbounded memory growth. Use makeUniqueNoThrow for safer allocation. These changes prevent abort() calls from bad_alloc under -fno-exceptions.
2026-07-27 14:29:58 -04:00
Uri Tauber 99c0e504bd fix: TextSettingsActivity default to selected font (#2739) 2026-07-27 19:10:24 +03:00
Leopoldo Pla Sempere eea75d561b fix: bind settings labels to enum values (#2644) 2026-07-27 08:02:20 +03:00
William Floyd 087c3da462 fix: name dictionary lookup failures (low-memory vs decompress vs read) instead of 'Not found' (#2706) 2026-07-27 07:58:32 +03:00
Justin Mitchell c405c223c2 Pre-scan ZIP before initializing crypto for EPUBs
Perform ZIP scan upfront to check for encryption.xml before initializing crypto or loading credentials. This allows plain EPUBs to return immediately through the normal reader path, while protected EPUBs still only scan once by reusing the same ZIP index via openFromScan().
2026-07-27 00:38:48 -04:00
Justin Mitchell b95e0bcd48 Remove redundant ZIP scan in ContentProtection
Delegate encryption detection to ProtectedBook class instead of performing a separate preliminary ZIP scan. ProtectedBook now handles the authoritative scan and determines protection status, including distinguishing plain EPUBs from font-obfuscation-only cases.
2026-07-26 23:28:49 -04:00
Justin Mitchell 90581d1ba4 Stream decryption to avoid buffering in memory
Replace in-memory vector buffering with sink-based streaming for decryption. Adds decryptedSize() and decryptToSink() methods to allow writing decrypted content directly to output buffers or streams, reducing memory usage for large encrypted files.
2026-07-26 23:00:42 -04:00
Justin Mitchell 5987594b2d Remove plugin host allowlist security mechanism
Removes the allowedHosts manifest validation that restricted which external hosts plugins could access via the relay endpoint. Also updates comments throughout to use more generic 'encrypted-entry' terminology instead of 'protected content', and adds NO_WOLFSSL_ESP32_CRYPT_RSA_PRI flag to work around ESP32-C3 hardware RSA limitations with 4096-bit keys.
2026-07-26 22:49:33 -04:00
Justin Mitchell cd09c9c748 Disable TLS peer verification for SecureNet transport
SecureNet transport does not ship with a CA bundle, causing peer verification to fail with wolfSSL error -188. Set connections to insecure mode to maintain TLS encryption without authentication
2026-07-26 21:17:53 -04:00
Justin Mitchell 41f17f1b5c Reformat code and reorganize include statements
Alphabetize include statements and apply consistent formatting to if-else blocks. No functional changes.
2026-07-26 18:56:11 -04:00
Justin Mitchell afd6162e6a Add dedicated color variables for plugin controls
Introduce plugin-control-bg and plugin-control-border CSS variables for both light and dark themes to replace generic card-bg and border-color. Add subtle inset box-shadow for depth.
2026-07-26 18:44:40 -04:00
Justin Mitchell e017d099e2 Allow opening EPUBs with font obfuscation only
Previously, any EPUB with encryption.xml would fail to open without a credential, even if it only used font obfuscation (not DRM). Now credentials are optional until actual content protection is detected. Added user-facing error message for genuinely DRM-protected books and styling for SD-card plugin UI in File Manager.
2026-07-26 18:43:06 -04:00
Justin Mitchell ca8e135276 Add HTML include directive resolution
Implements a function to inline <!--#include file="path" --> directives in HTML files. The function resolves includes relative to a base directory and supports up to 8 levels of nesting. Must be called before HTML minification since minification strips comments.
2026-07-26 18:22:44 -04:00
Emily Marriott ef3d980cb3 feat: Add packages to nix dev shell required for clang-format-fix and… (#2695) 2026-07-27 01:12:28 +03:00
Bastian c731d5187c fix: fixing german translation inconsistencies (#2702) 2026-07-27 00:52:30 +03:00
Justin Mitchell c6a968d0e1 Add content protection SDK binding for SD storage
Implements HAL binding layer that connects the storage-agnostic ContentProtection SDK to the device's SD card storage. Provides SdByteSource for reading encrypted files, credential lookup from provisioned keys, and openProtectedBook() entry point for the reader. This glue code lives in firmware to keep the portable SDK library HAL-independent.
2026-07-26 17:29:02 -04:00
Justin Mitchell cb056d50b6 Add browser-side plugin system with SD card support
Implements a plugin architecture that allows JavaScript bundles stored on SD card (/.crosspoint/plugins/) to extend the web UI. Plugins can mount to Settings or File Manager pages and access device capabilities including HTTPS relay, crypto primitives, SD file operations, and URL downloads. Includes path validation and file serving with proper content types.
2026-07-26 17:17:10 -04:00
Leopoldo Pla Sempere c9164ac54a fix: translate BMP viewer error messages (#2730) 2026-07-26 23:31:00 +03:00
Ing. Jan Kaláb a02cffe2de chore: Update czech.yaml (#2726) 2026-07-26 23:29:39 +03:00
Leopoldo Pla Sempere b25e57dac2 test: repair malformed section-break EPUB fixture (#2728) 2026-07-26 23:28:22 +03:00
Uri Tauber 9cff12b533 docs: Add StarDict sources list 2026-07-26 17:55:11 +03:00
Thiago Kenji Okada 9ec7767111 fix: preserve custom font ligatures (#2673) 2026-07-26 08:23:25 +03:00
Uri Tauber 9190048466 feat: point-size font selection (#2720) 2026-07-25 22:02:49 +00:00
Justin Mitchell 1cf265aa21 fix: Lower TLS minimum free memory threshold to 35KB for kosync (#2719) 2026-07-26 00:48:57 +03:00
Julia dde7e03776 fix: ignore ambiguous EPUB guide text so books open at the right location (#2716) 2026-07-26 00:48:29 +03:00
Julia Nguyen 78c1c8e182 Merge branch 'release/1.5.0' into develop 2026-07-25 17:14:19 -04:00
Matteo Scopel 0701683971 chore: small Italian translation fixes (#2703) 2026-07-25 22:05:42 +03:00
CookieCaptainD 36ac29e284 fix(i18n): add missing strings in PT-PT translation (#2701) 2026-07-25 21:38:55 +03:00
Phạm Bình An d0359edfcc feat(epub): native <ruby> support for Chinese, Japanese (#2665) 2026-07-24 23:31:59 -04:00
Justin Mitchell 1d98ce1ea7 feat: Add UC8279 panel controller detection for X3 devices (#2707) 2026-07-24 21:07:18 -04:00
Totofaki ad5a7a2e2a fix: avoid X3 Quick Resume flashes (#2698) 2026-07-24 12:52:46 -04:00
Phạm Bình An 962d5acc9c fix(epub): split text blocks on closed tags to prevent style leaks (#2679) 2026-07-24 09:59:34 +03:00
Thiago Kenji Okada d7c98adcc8 fix: restore antialiasing after manual refresh (#2683) 2026-07-24 00:33:18 +03:00
Uri Tauber 90785b71ff chore: Bump version to 1.5.0 2026-07-23 22:23:13 +03:00
Uri Tauber 66abde5fc5 fix: STR_RESTARTING_HINT text overflow bug (#2692)
## Summary
fix #2021
2026-07-23 15:18:02 -04:00
Uri Tauber b85580aa2c fix: Warp around in Percent Selection (#2677)
## Summary

Address request
https://github.com/crosspoint-reader/crosspoint-reader/discussions/2659

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
2026-07-23 15:17:02 -04:00
Justin MitchellandUri Tauber f0a50557be feat: deferred refresh, memory work port, and first-open speedups (#2611)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-07-23 22:12:56 +03:00
Leopoldo Pla Sempere c6f6f049a3 chore: update Spanish, Catalan, and Valencian translations (#2651) 2026-07-23 21:36:17 +03:00
Tsz Ho Sze 10db60c4fd feat: cjk UI font fallback (#2521) 2026-07-23 21:26:54 +03:00
Matteo Scopel a39fb39a07 chore: update Italian translation (#2691) 2026-07-23 21:25:01 +03:00
Justin Mitchell bb732479e2 fix: Move sunlight fading fix setting to different group (#2689)
Relocate STR_SUNLIGHT_FADING_FIX from the touch reader controls group to
the front button orientation group in the settings filter logic.
Sunlight fix was accidentally being hidden on non-touch where it should
have been hidden on touch instead. Closes #2030
2026-07-23 12:33:09 -04:00
Paul DelestracandUri Tauber 4e41bf68f7 feat: unified Text Settings screen with live preview (#2605)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-07-22 23:23:51 +03:00
Hendra KusumahandUri Tauber aacdd7e9cf add Bahasa Indonesia Translation (#2666)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-07-22 12:21:17 +03:00
Uri Tauber 63eda54ee9 chore: Migrate Settings/State onto PersistableStore; (#2647) 2026-07-21 17:56:21 -04:00
Phạm Bình An f1206f4d81 chore: update Vietnamese translations (#2667) 2026-07-22 00:32:49 +03:00
Dylan Byars 973a902332 fix: duplicate User-Agent header on wolfSSL requests breaks strict servers (aiohttp 400) (#2661) 2026-07-21 01:48:26 -04:00
Justin Mitchell cb9b971c26 Refactor SCOPE.md for improved formatting
Updated SCOPE.md to remove unnecessary line breaks and improve readability.
2026-07-21 01:20:26 -04:00
Justin MitchellandJulia Nguyen f42fab1c66 feat: Add touch coordinate mapping and RTOS task yielding (#2481)
Co-authored-by: Julia Nguyen <julia@uxj.io>
2026-07-20 16:31:07 -04:00
Justin Mitchell c9188a7347 chore: Clarify project scope and development priorities (#2149)
Expand scope to support multiple e-ink devices beyond X4. Add guiding
principle to fill gaps in stock firmware rather than duplicate
functionality. Establish current focus on memory/flash optimization and
code cleanup. Temporarily freeze new themes and network connectors to
consolidate codebase before multi-device expansion.
2026-07-20 16:02:02 -04:00
Leopoldo Pla Sempere e03aa16330 fix: select strongest AP for matching WiFi SSID (#2655) 2026-07-20 18:17:16 +03:00
Phạm Bình An 3319aa1720 fix(epub): preserve word continuation when splitting CJK text on MAX_WORD_SIZE (#2652) 2026-07-20 15:02:08 +03:00
CookieCaptainD 3dfbc0383f fix(i18n): add missing strings in PT-PT translation (#2632) 2026-07-20 14:12:06 +03:00
Thiago Kenji Okada 64364bec5d feat: add Nix development shell (#2645) 2026-07-20 00:57:37 +03:00
Stefan Blixten Karlsson 556b8aee5b fix: swedish translation (#2649) 2026-07-19 22:12:10 +03:00
b1d1d757ba feat: Slim dictionary (#2583)
Co-authored-by: Ryan Hitchman <hitchmanr@gmail.com>
Co-authored-by: Kurtis Grant <kurtis.b.grant@gmail.com>
Co-authored-by: kemonine <kemonine@kemonine.info>
Co-authored-by: DustinHu <hu.dustin@gmail.com>
Co-authored-by: Justin Mitchell <justin@jmitch.com>
2026-07-19 17:57:16 +03:00
Uri Tauber 5ba1d5747f fix: EndOfBookOptions fails to compile
ActivityManager.h forward-declares Activity but holds
std::unique_ptr<Activity> members. Instantiating that unique_ptr's
destructor requires the complete type, so any TU including
ActivityManager.h without Activity.h fails to compile.

Include Activity.h directly. Adding it to ActivityManager.h instead does
not work: Activity.h depends on HomeMenuItem, which ActivityManager.h
defines.
2026-07-19 16:45:42 +03:00
Thomas Symalla 9fe4dc5e38 feat: Add option to switch behavior for "back to browser / home" in Reader activity (#2366)
## Summary

It would be nice to switch back to the file list from an Reader activity
via a short back button press. This change adds an Reader option to
switch the default behavior, so a short back button press in the Reader
activity can now go back to the file list, and a long press on back goes
back to the home view. This does a fair bit of refactoring, introducing
a new constant for the ms limit.

* **What changes are included?**

- Changes to the translation
- Additional global Reader option 
- Refactoring of the back button behavior in the Reader activity
2026-07-19 08:29:13 +03:00
Justin Mitchell b1d037569b feat: Add kosync user registration and switch to crosspoint-sync server (#2587)
Implements createUser() endpoint to allow account creation via the
KOSync protocol. Changes default sync server from sync.koreader.rocks to
sync.crosspointreader.com with migration logic to preserve existing
users' server settings. Extends sync protocol to include position data
(spine index, page numbers, xpath) that crosspoint-sync supports while
remaining compatible with standard kosync servers.
2026-07-18 14:50:51 -04:00
Julia 9737cb335c fix: correct the settings enums for "blank" and "cover + custom" sleep screens (#2635) 2026-07-17 12:35:51 -04:00
Phạm Bình An fdffc2e5d9 fix: reduce CSS parse-time OOM risk in chapter layout (#2606) 2026-07-16 07:21:08 +03:00
a2db43d235 feat: enable CORS headers in the HTTP API (#2594)
Closes #2558.

Enables the Arduino WebServer's built-in CORS support
(`enableCORS(true)`), which adds
`Access-Control-Allow-Origin/Methods/Headers: *` to every response, and
answers preflight `OPTIONS` requests with `204` in `handleNotFound()` —
routes are registered per-method, so OPTIONS always lands there. The
AP-mode captive-portal redirect is untouched (the OPTIONS check runs
before it, and browsers don't send preflights for captive-portal
probes).

This lets web-based clients and PWAs served from other origins call the
JSON API (`/api/status`, `/api/files`, `/api/settings`, ...) directly
from the browser.

Overhead is three static response headers; no behavior change for the
built-in web UI.

Note: not yet tested on hardware.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: metoli <metoli@metoli-Mac-mini.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 18:35:10 -04:00
rxmmahandkira 63093e606c feat: Back on home menu opens the most recent book (#2619)
Co-authored-by: kira <rammah@tuta.io>
2026-07-15 23:50:22 +03:00
a4ac3b2788 feat: add smart KOReader progress sync (#2192)
## Summary

Make KOReader progress sync a one-click flow for common cases while
keeping the existing manual mode available.

### Changes

- Add a **Sync Behavior** setting:
  - **Smart sync** for new configurations
  - **Ask every time** for manual control
- Preserve **Ask every time** when migrating an existing credential file
that predates this setting.
- In Smart mode:
  - upload local progress when no remote record exists;
  - show a short confirmation when already synced;
  - upload when local progress is further ahead;
  - apply remote progress when remote progress is further ahead.
- Probe both KOReader document-matching hashes before making the Smart
decision, while keeping uploads on the user's configured matching
method.
- Auto-return after successful Smart terminal states, with Back/Confirm
still available.
- Persist the setting alongside the current credential, matching-method,
and send-metadata fields.
- Document both behaviors in the user guide.

## Additional context

This pairs well with #2189 (smart Wi-Fi auto-connect), but does not
depend on it. It does not add background Wi-Fi or passive network
detection; sync is still triggered by the user from the reader menu.

Smart sync uses the furthest progress because CrossPoint does not
currently persist local progress timestamps. Users who prefer explicit
conflict resolution can select **Ask every time**.

## Verification

- `git diff --check`
- `platformio check --fail-on-defect low --fail-on-defect medium
--fail-on-defect high` — no defects
- `platformio run -e default` — firmware build successful
- The original implementation was manually smoke-tested on an X4 device.

---

### AI Usage

Did you use AI tools to help write this code? _**YES**_

AI tools were used to inspect the codebase, draft and review the
implementation, resolve the rebase against current `develop`, and
prepare the PR text. The resulting firmware was built locally.

---------

Co-authored-by: Alexander Hoffer <git@alexanderhoffer.com>
Co-authored-by: Alexander Hoffer <contact@alexanderhoffer.com>
2026-07-15 14:59:15 -04:00
Víctor Fernández 35a45f9c1e feat: add options to remember web upload settings & rename ebooks to {title} - {author} (#2534) 2026-07-15 14:43:42 -04:00
William Floyd f3cad2f9e1 fix(kosync): reload Epub before reading upload metadata (#2608) 2026-07-15 11:46:10 -04:00
Oscar Nogueira Neto 95d8cb712b feat: configurable OPDS download folder and filename format (#2571) 2026-07-15 10:53:27 -04:00
Mirza Arnaut b9867b0d09 feat(i18n): add Bosnian translation (#2616) 2026-07-15 10:49:42 -04:00
Uri Tauber 3dac4446b7 fix: remove duplicate sleep logic (#2492) 2026-07-15 10:48:08 -04:00
Uri Tauber f180069643 fix: ignore open-x4-sdk and fs_ (#2609) 2026-07-14 21:34:28 +03:00
Husam Younis a0ec5812b1 feat: Arabic translation YAML - PR 3/3 (#2599) 2026-07-14 20:33:42 +03:00
Phạm Bình An e84840b473 fix(css-parser): don't save unusable rules to RAM (#2604) 2026-07-14 17:00:13 +03:00
Tom-Inge Larsen 94ae750e89 feat(i18n): add Norwegian Bokmål translation (#2113) 2026-07-14 16:54:43 +03:00
metoli86andmetoli 8c5cd67f38 feat: complete Turkish translation (390/390) (#2592)
Co-authored-by: metoli <metoli@metoli-Mac-mini.local>
2026-07-14 16:50:03 +03:00
Justin Mitchell c5787d1cc1 fix: Use HALF_REFRESH for sleep and boot instead of FULL (#2588)
Changes all sleep screen display refreshes and boot screens from
FULL_REFRESH to HALF_REFRESH to match OEM firmware behavior. The stock
firmware uses a single-pass 0xD7 waveform for sleep screens, not the
multi-flash GC waveform (0xF7) that FULL_REFRESH triggers. For grayscale
mode, HALF_REFRESH is required because the gray nudge LUT is calibrated
against the pixel charge state left by the single-pass waveform; using
FULL_REFRESH causes blotchy noise in gray areas. Also adds X3 wb_gc
passive byte (the one-byte 0x54 to 0x00, matching stock's passive WB
table in the gc nudge bank): Every X3 grayscale render, reader AA,
covers, and the sleep-cover nudge, gets it.
2026-07-13 17:06:09 -04:00
Husam Younis 2a19eb9e0d feat: Arabic/Farsi/Urdu glyphs in built-in UI fonts - PR 2/3 (#2596) 2026-07-13 22:35:47 +03:00
Uri Tauber fae3423dec feat: add portuguese-PT.yaml (#2597) 2026-07-13 22:27:23 +03:00
Husam YounisandUri Tauber 932a472835 feat: Arabic/Farsi/Urdu bidi reordering and contextual shaping — PR 1/3 (#2541)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-07-13 16:02:21 +03:00
Jan Steinke 552b2683e6 fix: keep list item bullet inline with nested paragraph text (#2589) 2026-07-13 12:06:43 +03:00
Mr.Catfood bfa1d706b1 feat: Hidden wifi ssid support (#2360) 2026-07-13 02:33:48 -04:00
Jacob Latonis 73ab9ab20d feat: implement captive portal redirects for auto-loading the management page on hotspot mode (#2550) 2026-07-13 00:10:23 -04:00
Nick Perez 3a97f3e63e feat: Send optional document metadata with KOSync progress uploads (#1820) 2026-07-12 23:50:38 -04:00
Matthías Páll Gissurarson 39ea4b045f feat: render placeholders while waiting for images to render (#1003) 2026-07-12 21:40:07 +03:00
Justin Mitchell 444d87de82 fix: Add framebuffer release/realloc and improved lazy indexing (#2563) 2026-07-12 13:16:48 -04:00
Leopoldo Pla Sempere 859f6cb0d5 chore: update Spanish, Catalan, and Valencian Wi-Fi strings (#2578) 2026-07-12 12:13:06 +03:00
hooligan333andErica Jensen d8139e9605 perf: release CSS rule map after warm open (#2439)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
2026-07-11 22:59:31 -04:00
hooligan333andErica Jensen b596f21e61 perf: stream NCX/NAV TOC into parser, drop temp-file round-trip (#2440)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
2026-07-11 22:57:48 -04:00
hooligan333andErica Jensen 5fbc657aeb perf: always binary-search idref lookups in content.opf (#2433)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
2026-07-11 22:56:28 -04:00
Jason Huebel bb06219c9c docs: add script to generate EPUB from USER_GUIDE.md (#2152) 2026-07-11 22:54:32 -04:00
Alexander Hoff ❍andAlexander Hoffer 5202bbf911 feat: auto-connect saved Wi-Fi networks (#2189)
Co-authored-by: Alexander Hoffer <git@alexanderhoffer.com>
2026-07-11 22:47:26 -04:00
hooligan333andErica Jensen e0253a8664 perf: reserve CSS rule map before loading from cache (#2435)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
2026-07-11 22:24:43 -04:00
hooligan333andErica Jensen e142e88ce7 perf: drop per-image delay(50) on chapter build, retry getDimensions (#2434)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
2026-07-11 22:23:52 -04:00
Timo 6add0b91bc feat: Add Finnish hyphenation (#2084) 2026-07-11 22:16:42 -04:00
Leopoldo Pla Sempere 287457f7dd fix: handle low-bit-depth, upscaled, and SVG EPUB images (#2503) 2026-07-11 21:45:53 -04:00
Justin Mitchell 3e627112f6 fix: oom exceptions for OPDS, KOSync, and OTA via wolfssl (#2475) 2026-07-12 01:53:26 +03:00
Stefan Blixten Karlsson 1f5669a08a fix: Swedish translation (#2577) 2026-07-11 23:20:20 +03:00
Ing. Jan Kaláb 0dc92c142c fix: update czech.yaml (#2574) 2026-07-11 21:09:09 +03:00
Leopoldo Pla Sempere 4b34a576eb chore: update Spanish, Catalan, and Valencian translations (#2566) 2026-07-10 17:45:40 +03:00
4fb843ef92 perf: skip redundant progress writes when position is unchanged (#2436)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-07-10 10:37:43 +03:00
Matteo Scopel a43061305a chore: update the Italian translation (#2559) 2026-07-08 22:22:47 +03:00
Uri Tauber 7ec07b4d9d fix: show "Failed to index" when failing to parse epub (#2556) 2026-07-08 21:22:35 +03:00
Uri Tauber c2c1badc7e chore: Refactor stores to use PersistableStore CRTP template (#2464) 2026-07-08 12:44:52 +03:00
Nick 5776911af0 fix: FontDecompressor OOM aborts on the render path (-fno-exceptions makes vector resize fatal) (#2526) 2026-07-07 09:06:18 +03:00
Pietro Campagnano 3c5c5e8aa7 feat: preview image files inline in web file browser (#2429) 2026-07-06 16:45:22 -04:00
Justin Mitchell 6f5c5a0900 fix: Flatten TextBlock word storage into single allocation (#2547) 2026-07-06 23:41:00 +03:00
Uri Tauber f2b00f6b93 chore: Replace product link with affiliate tracking link (#2401) 2026-07-06 23:13:27 +03:00
Uri TauberandBrooks Ilg 44ff313740 fix: render <br> between paragraphs as a visible section break (#2548)
Co-authored-by: Brooks Ilg <brooksmilg@gmail.com>
2026-07-06 20:10:31 +03:00
Jacob Latonis ca1b833126 fix: follow spec for zxing qr code generation (#2540) 2026-07-06 00:33:34 +03:00
Tom-Inge Larsen 3e1dd31e53 feat(reader): End of Book next-book suggestions (#2499) (#2532) 2026-07-04 23:12:59 +03:00
685d4e88f9 feat: Lazy incremental EPUB section indexing (#2452)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
Co-authored-by: Julia Nguyen <julia@uxj.io>
2026-07-04 21:21:24 +03:00
Jakub Sypiański 79b4d63ee2 fix: settings persist on font clear, reserve() before push_back, minor (#2519) 2026-07-03 18:58:24 +03:00
Leopoldo Pla SempereandJiangoJ 57447a56a6 feat(epub): improve text-decoration support (#2397)
Co-authored-by: JiangoJ <jiangj2620@gmail.com>
2026-07-02 12:35:24 +03:00
Rahat Ahmed 5c86bfe1fd feat(network): add device serial number to web UI (#2506) 2026-07-02 00:53:55 +03:00
Justin Mitchell 4cdefb137c Update README with firmware flashing warnings and crosspet status
Removed warning about flashing Papyrix on USB-locked devices and updated crosspet status to unmaintained.
2026-07-01 14:22:27 -04:00
Justin Mitchell e7f3406704 fix: Add socket module import to build-sd-fonts script (#2504) (#2505) 2026-07-01 12:10:15 -04:00
Bastian 6f08a88b12 fix: changing translation in czech to fix overflowing navigation (#2502) 2026-07-01 11:58:24 -04:00
Tom-Inge LarsenandClaude Opus 4.8 e4d3981469 fix(reader): correct slider side-button direction and legend on X3 (#2402) (#2428)
## Summary

* **What is the goal of this PR?** Fixes #2402. On the X3, the "Time to
Sleep"
picker's 5-minute side buttons were inverted (left increased, right
decreased)
  and the on-screen legend didn't match the physical buttons.
* **What changes are included?**
  * Flip the large-step (±5 min) direction on X3 so the left side button
decreases and the right increases, matching the layout. X4 is unchanged.
* Add a device-specific step-hint string
(`STR_SLEEP_TIMER_STEP_HINT_X3`)
selected via `gpio.deviceIsX3()`. X3 shows `+/-: 1 min Side buttons: 5
min`;
    X4 keeps the original `Left/Right: 1 min  Up/Down: 5 min`.
* Same fix applied to both slider pickers that read the raw up/down side
    buttons: the Time-to-Sleep picker and the "Go to %" picker
(`EpubReaderPercentSelectionActivity`), each with its own X3 hint string
    (`STR_SLEEP_TIMER_STEP_HINT_X3`, `STR_PERCENT_STEP_HINT_X3`).

## Additional Context

* **Root cause:** the X3's side buttons sit one on each edge of the
screen
(power on top), whereas the X4 has a vertical up/down rocker on the
right
edge. So `BTN_UP` is physically the *left* button on X3 but the *top*
button
  on X4. The picker mapped up→+5 / down→−5 unconditionally, which reads
naturally on the X4 rocker but inverted on the X3's left/right buttons.
The
  static legend ("Up/Down: 5 min") had the same X3-only mismatch.
* The codebase already detects the device at runtime and handles this
layout
difference elsewhere (e.g. `LyraTheme::drawSideButtonHints` draws "Up on
left,
Down on right" for X3), so this reuses the same `gpio.deviceIsX3()`
signal.
* Swept the codebase for other side-button sliders:
`ClockOffsetActivity` already
uses the `Next/Previous` (`ButtonNavigator`) abstraction, which resolves
to
left=decrement / right=increment on X3, so it needs no change. List/page
navigation (Wi-Fi, KOReader sync, BMP viewer) and the keyboard cursor
are also
unaffected. The two slider pickers above were the only ones using raw
up/down.
* **X4 is untouched** — same direction, same legend wording as before.
* **Tested on X3 hardware:** left side button now decreases, right
increases, and
  the legend matches. X4 not yet tested on device (no unit on hand); its
  behaviour and strings are unchanged from master.
* **Translations:** the new `STR_SLEEP_TIMER_STEP_HINT_X3` and
`STR_PERCENT_STEP_HINT_X3` were added to all 26 languages, but the
non-English
  ones are AI-generated and would benefit from a native-speaker pass —
particularly Hebrew (RTL ordering with a leading `+/-`), Kazakh, and
Vietnamese.

---

### AI Usage

Did you use AI tools to help write this code? _**YES**_

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:06:31 -04:00
Bastian c9d27826f9 docs: update TOC in userguide (#2479) 2026-06-30 08:48:21 +03:00
Bastian 13bd00bf9d fix: sort sleep screen menu options to be more logically consistent (#2480) 2026-06-30 08:47:25 +03:00
Uri Tauberandpablohc fef39da23c feat: Selection Popup (#2358)
Co-authored-by: pablohc <pablonoviello@outlook.com>
2026-06-29 16:59:25 -04:00
Uri Tauber fd43ca2fe1 chore: Initial multi-core compatibility (#2294) 2026-06-29 21:57:21 +03:00
Pietro Campagnano 28255061fb fix(icons): align home menu icons with their labels (#2470) 2026-06-29 13:59:54 -04:00
Julia Nguyen 43ff1b6644 Merge remote-tracking branch 'origin/master' into develop 2026-06-29 12:29:58 -04:00
Егор Мартынов 2754a5ff01 feat: add Vollkorn font (#2473) 2026-06-29 15:54:01 +03:00
Justin Mitchell 6edeed73c5 fix: x4 sleep/boot ghosting (#2471) 2026-06-29 12:04:33 +03:00
Justin Mitchell 8ac302018c fix: x4 ghosting by bumping sdk (#2469) 2026-06-28 22:38:35 +03:00
Rodrigo Matsuura a11517a62d fix: update Portuguese (Brasil) translations (#2458) 2026-06-28 15:07:52 +03:00
388 changed files with 52800 additions and 14576 deletions

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
blank_issues_enabled: false
contact_links:
- name: Scope or Roadmap Question (start here if unsure)
url: https://github.com/crosspoint-reader/crosspoint-reader/discussions
about: |
Not sure if your idea fits CrossPoint's scope? Start a Discussion before filing an issue.
See SCOPE.md and ROADMAP.md for what is in, out, and currently paused.
- name: Read the Scope document
url: https://github.com/crosspoint-reader/crosspoint-reader/blob/master/SCOPE.md
about: The authoritative list of what CrossPoint will and will not accept.
- name: Read the Roadmap
url: https://github.com/crosspoint-reader/crosspoint-reader/blob/master/ROADMAP.md
about: Current phase, what is being closed out, and what comes next.
@@ -0,0 +1,80 @@
name: Feature Request
description: Propose a new feature, enhancement, or change to CrossPoint
title: "Short, descriptive title of the request"
labels: ["enhancement", "needs-scope-review"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to propose a change to CrossPoint!
**Before you continue, please read [SCOPE.md](../blob/master/SCOPE.md) and [ROADMAP.md](../blob/master/ROADMAP.md).**
CrossPoint is intentionally narrow. Most rejected proposals are rejected for scope reasons that are already
documented. The checklist below exists to save both of us time.
If you are not sure whether your idea fits, open a [Discussion](../../discussions) first instead of filing
this issue.
- type: checkboxes
id: scope-check
attributes:
label: Scope Self-Check (required)
description: Please confirm each of the following. If any are unchecked, your issue will likely be closed.
options:
- label: I have read SCOPE.md and ROADMAP.md.
required: true
- label: This is **not** a new theme or theming change (themes are temporarily closed pending the move to SD-loaded themes).
required: true
- label: This is **not** a new external network connector (sync engine, cloud storage, remote file access, OPDS extensions beyond what exists, or any new "talk to a server" feature).
required: true
- label: This is **not** an interactive app (game, calculator, notepad), writing/authoring tool, RSS/news/browser feature, media playback feature, or PDF rendering.
required: true
- label: The stock firmware does **not** already handle this well.
required: true
- label: No other popular CrossPoint fork already handles this well (or, if one does, I explain below why CrossPoint still needs it).
required: true
- type: textarea
id: problem
attributes:
label: Problem this solves
description: What user-facing problem or reading-experience gap does this address? Be concrete.
placeholder: e.g., "When reading in landscape, paragraph breaks are inconsistent because..."
validations:
required: true
- type: textarea
id: stock-gap
attributes:
label: Why the stock firmware (and other forks) do not already solve this
description: Explain which existing solutions you checked and why they fall short. This is the core scope filter.
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed change
description: A short description of what you would build or change. Focus on user impact; implementation details can come later.
validations:
required: true
- type: textarea
id: tradeoffs
attributes:
label: Memory / flash / complexity cost
description: |
CrossPoint runs on 380KB of RAM. Roughly how much DRAM, flash, or code complexity does this add?
"Don't know" is a valid answer, but please attempt an estimate.
placeholder: e.g., "Adds ~2KB flash for the new font tables, no DRAM impact at runtime."
validations:
required: false
- type: textarea
id: additional-context
attributes:
label: Additional context
description: Anything else relevant (links, screenshots, related discussions).
validations:
required: false
+20 -2
View File
@@ -3,16 +3,34 @@
* **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.)
* **What changes are included?**
## Scope Check
CrossPoint is intentionally narrow. See [SCOPE.md](../blob/master/SCOPE.md) and [ROADMAP.md](../blob/master/ROADMAP.md).
Please confirm:
- [ ] I have read SCOPE.md and ROADMAP.md.
- [ ] This PR is **not** a new built-in theme (themes are temporarily closed pending the move to SD-loaded themes).
- [ ] This PR is **not** a new external network connector (sync engine, cloud storage, remote file access, etc.).
- [ ] This PR is **not** an interactive app, writing tool, RSS/news/browser, media playback, or PDF feature.
- [ ] The stock firmware does not already handle this well, **and** no other popular CrossPoint fork already does
(or, if one does, I explain why CrossPoint still needs it below).
- [ ] If this PR touches `freeink-sdk/`, `lib/hal/`, the bootloader, OTA, or recovery code, I have coordinated with
the relevant maintainer.
**If this PR was opened against the previous (broader) scope and was already in flight under Phase 0, link the
relevant Discussion or issue so reviewers can see the history.**
## Additional Context
* Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks,
* Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks,
specific areas to focus on).
* Memory / flash impact, if known.
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it
While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO >**_
+52 -4
View File
@@ -63,7 +63,8 @@ jobs:
- name: Run cppcheck
run: pio check --fail-on-defect low --fail-on-defect medium --fail-on-defect high
build:
build-default:
name: Build default
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
@@ -90,10 +91,10 @@ jobs:
key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini') }}
restore-keys: pio-${{ runner.os }}-
- name: Build CrossPoint
- name: Build default firmware
run: |
set -euo pipefail
pio run | tee pio.log
pio run -e default | tee pio.log
- name: Extract firmware stats
@@ -116,6 +117,52 @@ jobs:
path: .pio/build/default/firmware.bin
if-no-files-found: error
build-sticky:
name: Build sticky
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions/setup-python@v6
with:
python-version: '3.14'
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"
enable-cache: false
- name: Install PlatformIO Core
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
- name: Cache PlatformIO packages
uses: actions/cache@v4
with:
path: ~/.platformio
key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini') }}
restore-keys: pio-${{ runner.os }}-
- name: Build sticky firmware
run: |
set -euo pipefail
pio run -e sticky | tee pio.log
- name: Extract firmware stats
run: |
set -euo pipefail
ram_line="$(grep -E "RAM:\\s" -m1 pio.log || true)"
flash_line="$(grep -E "Flash:\\s" -m1 pio.log || true)"
echo "ram_line=${ram_line}" >> "$GITHUB_OUTPUT"
echo "flash_line=${flash_line}" >> "$GITHUB_OUTPUT"
{
echo "## Sticky firmware build stats"
if [ -n "$ram_line" ]; then echo "- ${ram_line}"; else echo "- RAM: not found"; fi
if [ -n "$flash_line" ]; then echo "- ${flash_line}"; else echo "- Flash: not found"; fi
} >> "$GITHUB_STEP_SUMMARY"
unit-tests:
runs-on: ubuntu-latest
steps:
@@ -148,7 +195,8 @@ jobs:
test-status:
name: Test Status
needs:
- build
- build-default
- build-sticky
- clang-format
- cppcheck
- unit-tests
-47
View File
@@ -1,47 +0,0 @@
name: Compile Release
on:
push:
tags:
- '*'
jobs:
build-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions/setup-python@v6
with:
python-version: '3.14'
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"
enable-cache: false
- name: Install PlatformIO Core
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
- name: Cache PlatformIO packages
uses: actions/cache@v4
with:
path: ~/.platformio
key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini') }}
restore-keys: pio-${{ runner.os }}-
- name: Build CrossPoint
run: pio run -e gh_release
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: CrossPoint-${{ github.ref_name }}
path: |
.pio/build/gh_release/bootloader.bin
.pio/build/gh_release/firmware.bin
.pio/build/gh_release/firmware.elf
.pio/build/gh_release/firmware.map
.pio/build/gh_release/partitions.bin
+10
View File
@@ -2,6 +2,8 @@
.idea
.DS_Store
.vscode
open-x4-sdk
fs_
lib/EpdFont/fontsrc
lib/I18n/I18nKeys.h
lib/I18n/I18nStrings.h
@@ -23,3 +25,11 @@ lib/EpdFont/scripts/output/
# (worktrees, scheduled-task locks, settings.local, scout CLEANUP.md) out.
.claude/*
!.claude/skills/
/managed_components
/.dummy
/CMakeLists.txt
/dependencies.lock
/sdkconfig.*
sdkconfig.sticky
sdkconfig.x4pro
flake.*
+179 -68
View File
File diff suppressed because it is too large. Load diff
Symlink
+1
View File
@@ -0,0 +1 @@
.skills/SKILL.md
+23 -8
View File
@@ -8,9 +8,11 @@ CrossPoint is open-source e-reader firmware - community-built, fully hackable, f
![CrossPoint Reader running on Xteink device](./docs/images/cover.jpg)
> If you're planning to buy an Xteink device, consider purchasing an **X3/X4 Developer Edition** through https://crosspointreader.com. CrossPoint receives a small share of each sale, helping fund development costs.
## What can CrossPoint do?
- **Reader engine**: EPUB 2/3 rendering with embedded-style option, image handling, hyphenation, kerning, chapter navigation, footnotes, bookmarks, go-to-percent, auto page turn, orientation control, focus reading, KOReader progress sync and more.
- **Reader engine**: EPUB 2/3 rendering with embedded-style option, image handling, hyphenation, kerning, chapter navigation, footnotes, bookmarks, dictionary lookups ([StarDict](docs/dictionary.md)), go-to-percent, auto page turn, orientation control, focus reading, KOReader progress sync and more.
- **Various formats**: native handling for `.epub`, `.xtc/.xtch`, `.txt`, and `.bmp`.
@@ -40,8 +42,6 @@ CrossPoint is open-source e-reader firmware - community-built, fully hackable, f
### Coming soon:
- Dictionary lookup — inline word lookup without leaving the reader.
- More themes.
- Much more! stay tuned.
@@ -67,10 +67,6 @@ USB port or browser before assuming the device is locked. Only reach for the unl
> Flashing any other firmware on a USB-locked device may **permanently brick the device** or leave it **permanently
> stuck on that firmware with no recovery path**. Once USB flashing is re-locked, your only way back is via OTA, and if
> the firmware you flashed doesn't support OTA, **there is no way out**.
>
> **The Papyrix fork has removed OTA update support from its code.** If you flash Papyrix onto a
> USB-locked unit, you will have **zero update or recovery path** and will be stuck on it forever. **Do not flash
> Papyrix (or any other unsupported firmware) on a locked device.**
## Install firmware
@@ -140,6 +136,7 @@ Conversion runs the firmware repo's `lib/EpdFont/scripts/fontconvert_sdcard.py`
- [Web server endpoints](./docs/webserver-endpoints.md)
- [Project scope](./SCOPE.md)
- [Contributing docs](./docs/contributing/README.md)
- [Touch and UI development](./docs/contributing/touch-and-ui.md) - how to build new screens on the FreeInkUI activity bases (UiListActivity and friends), plus build envs for the non-Xteink touch devices
---
@@ -162,6 +159,24 @@ cd crosspoint-reader
git submodule update --init --recursive
```
### Nix/NixOS
Nix/NixOS users can enter the development shell with either `nix develop` (flakes) or `nix-shell`:
```bash
nix develop -f nix
# or
nix-shell nix
```
To flash a connected ESP32-C3 device, enable PlatformIO's udev rules in your NixOS configuration:
```nix
services.udev.packages = with pkgs; [ platformio-core.udev ];
```
After rebuilding the system configuration, reconnect the device or reload udev rules.
### Build / flash / monitor
```bash
@@ -249,7 +264,7 @@ One of the best things about open source is that anyone can take the code in a d
- [papyrix-reader](https://github.com/bigbag/papyrix-reader) — Adds FB2 and MD format support. Actively maintained with Arabic script support. Custom themes via SD card.
- [crosspet](https://github.com/trilwu/crosspet) — A Vietnamese fork that adds a Tamagotchi-style virtual chicken that grows based on your reading milestones (pages read, streaks, care). Also: Flashcards, Weather, Pomodoro timer, and mini-games.
- ~~[crosspet](https://github.com/trilwu/crosspet) — A Vietnamese fork that adds a Tamagotchi-style virtual chicken that grows based on your reading milestones (pages read, streaks, care). Also: Flashcards, Weather, Pomodoro timer, and mini-games.~~ (Unmaintained)
- [crosspoint-reader-cjk](https://github.com/aBER0724/crosspoint-reader-cjk) — Purpose-built for Chinese, Japanese, and Korean reading.
+88
View File
@@ -0,0 +1,88 @@
# CrossPoint Reader Roadmap
This roadmap describes how CrossPoint is moving through the tighter scope defined in [SCOPE.md](SCOPE.md). It is
intentionally phased: Phase 0 closed out the commitments already in flight before locking down to the stricter
"fill gaps the stock firmware leaves" delineator.
Phases are sequential. We do not start the next phase until the prior one is wrapped or explicitly carried over.
---
## Phase 0 - Close Out Legacy Scope Items — **COMPLETE**
**Goal:** Land the work that was already in motion under the prior, broader scope so contributors are not left
hanging, and so we enter the stricter phases with a clean slate.
**Landed in Phase 0:**
* **RTL support PRs.** The in-flight right-to-left work was reviewed, iterated, and merged.
* **Dictionary PR.** The offline dictionary lookup work was reviewed and merged.
* **Bookmarks** feature. First-class navigation markers in EPUBs.
* ~~**Transparent sleep screens.**~~ Shelved; not picked back up under the stricter phases.
Phase 0 is closed. The tighter scope in [SCOPE.md](SCOPE.md) is now fully enforced. "But it was on the old roadmap"
is not a valid argument for accepting a PR.
---
## Phase 1 - Consolidation, Footprint, and Multi-Device Support — **IN PROGRESS**
**Goal:** Reduce memory and flash usage, clean up the codebase, and land the SDK / HAL generalization work so
CrossPoint runs cleanly on ESP32-based e-reader hardware beyond Xteink (X3 / X4), including ESP32-S3 class devices.
**Focus areas:**
* DRAM and heap fragmentation reduction across the reader core.
* Flash footprint reduction (dead code, redundant strings, oversized tables).
* Refactors that tighten the HAL / SDK boundary.
* Pluggable per-device SDK layers (display, input, storage, battery) and per-device build configuration without
forking the reader core.
* Documentation for adding a new ESP32 e-reader target.
* E-ink driver refinement (ghosting, partial update behavior).
**Closed during this phase:** new themes built into firmware, new external network connectors (sync engines, cloud
storage, remote file access).
---
## Phase 2 - Languages, Fonts, and Themes
**Goal:** With the codebase smaller and portable, make reading great in every language: multi-language support,
better font support with custom fonts, UI translations, and themes loaded from the SD card instead of consuming
flash.
**Focus areas:**
* Multi-language reading support (underserved languages, complex script support where realistic on ESP32 hardware).
* Better font support and custom fonts.
* UI languages and localization.
* Moving themes off-firmware to SD-loaded assets (see SCOPE.md Section 6).
* **Moving hyphenation files off-firmware.** Hyphenation rules vary per language and the files are large (German
alone is ~200KB). Today these eat flash budget that should be available for the reader core. The plan is to build
a downloader analogous to the existing font downloader and store the dictionaries on SD / SPIFFS, loading on
demand. This unlocks better hyphenation for long-word languages (German, Finnish, Norwegian, etc.) without paying
the flash cost up front.
This phase depends on Phase 1 cleanup landing first; otherwise we generalize a moving target.
---
## Out of Roadmap
The following are explicitly *not* on the roadmap. They may live in other CrossPoint forks; they will not be picked
up here:
* Interactive apps (games, calculators, notepads).
* Writing / authoring tools.
* Active connectivity features (RSS, news, browsers).
* PDF rendering as a first-class format.
See [SCOPE.md](SCOPE.md) for the full rationale.
---
## How This Roadmap Changes
* Phase boundaries are decided by maintainers, not by individual PRs.
* If a phase needs to be extended or an item carried over, that is documented here with a short note.
* Proposals for new phases or reordering should go through a Discussion first.
+80 -42
View File
@@ -1,62 +1,100 @@
# Project Vision & Scope: CrossPoint Reader
The goal of CrossPoint Reader is to create an efficient, open-source reading experience for the Xteink X4. We believe a
dedicated e-reader should do one thing exceptionally well: **facilitate focused reading.**
The goal of CrossPoint Reader is to create an efficient, open-source reading experience for ESP32-based e-reader devices. Xteink hardware (X3, X4) is where the project started and remains a primary target, but CrossPoint is explicitly broadening to support the wider ecosystem of small ESP32 e-ink readers. We believe a dedicated e-reader should do one thing exceptionally well: **facilitate focused reading.**
## 1. Core Mission
To provide a lightweight, high-performance firmware that maximizes the potential of the X4, prioritizing legibility and
usability over "swiss-army-knife" functionality.
To provide a lightweight, high-performance firmware that maximizes the potential of ESP32-based e-reader hardware, prioritizing legibility, performance, and usability over "swiss-army-knife" functionality.
## 2. Scope
CrossPoint is **not** a kitchen-sink firmware, and it is **not** Xteink-only. We want clean, maintainable code that the community can build on, and that runs across the range of ESP32 e-reader devices (ESP32-C3, ESP32-S3, and adjacent variants). Every accepted change should make that goal easier, not harder. Device-specific code should live behind the HAL / SDK boundary so the reader core stays portable.
## 2. Guiding Principle: Fill Gaps the Stock Firmware Leaves
CrossPoint exists to do the things the stock firmware does poorly or not at all. New work is evaluated against that delineator:
* **Does the stock firmware already do this well?** We should hit that bar or surpass it
* **Is another popular CrossPoint fork already solving this well?** If yes, we generally defer to that fork if it's not part of the core reading experience. e.g. stats
* **Does this directly improve the reading experience or the firmware's long-term maintainability?** If no, it is out of scope.
## 3. Current Focus
We are intentionally narrowing scope to consolidate the codebase as we open it up to more ESP32 e-reader devices.
During this period, the priorities are:
* **Memory footprint:** Reducing DRAM usage and heap fragmentation. The ESP32-C3 is the tightest target and sets the ceiling, but the gains benefit every ESP32 variant we run on.
* **Flash footprint:** Trimming binary size to leave room for additional device targets and features.
* **Code cleanup:** Refactoring, removing dead code, tightening abstractions, and improving readability.
* **Reading experience:** EPUB parsing and rendering, typography, hyphenation, line spacing, font handling, and legibility improvements.
### Temporarily Closed Areas
PRs in the following areas will be closed until this notice is lifted. Adding these now makes the cleanup and multi-device work materially harder:
* **New themes.** The existing theming surface is frozen.
* **New external network connectors.** This includes sync engines, cloud storage clients, OPDS extensions beyond what exists, remote file access, and any new "talk to a server" feature. We now have our own CrossPoint KOSync server which gives us a way to sync to 3rd party systems like Hardcover at an API level instead of bloating the firmware. If you're interested in helping here, the sync server is also open source.
If you are unsure whether your idea falls into one of these categories, open a Discussion first.
## 4. Scope
### In-Scope
*These are features that directly improve the primary purpose of the device.*
*Features that directly improve the core reading experience or the firmware's maintainability.*
* **User Experience:** E.g. User-friendly interfaces, and interactions, both inside the reader and navigating the
firmware. This includes things like button mapping, book loading, and book navigation like bookmarks.
* **Document Rendering:** E.g. Support for rendering documents (primarily EPUB) and improvements to the rendering
engine.
* **Format Optimization:** E.g. Efficiently parsing EPUB (CSS/Images) and other documents within the device's
capabilities.
* **Typography & Legibility:** E.g. Custom font support, hyphenation engines, and adjustable line spacing.
* **E-Ink Driver Refinement:** E.g. Reducing full-screen flashes (ghosting management) and improving general rendering.
* **Library Management:** E.g. Simple, intuitive ways to organize and navigate a collection of books.
* **Local Transfer:** E.g. Simple, "pull" based book loading via a basic web-server or public and widely-used standards.
* **Language Support:** E.g. Support for multiple languages both in the reader and in the interfaces.
* **Reference Tools:** E.g. Local dictionary lookup. Providing quick, offline definitions to enhance comprehension
without breaking focus.
* **Clock Display (device dependent):**
| Device | Scope |
| -- | -- |
| X3 | The X3 uses a dedicated DS3231 RTC, which maintains accurate time across sleep cycles and can be treated as a reliable wall clock. |
| X4 | The X4 relies on the ESP32-C3's internal RTC, which drifts significantly during deep sleep. NTP sync could correct this, with an appropriate user experience around connecting to the internet on wake or on demand. This causes some tension with the **Active Connectivity** section below, so please open a discussion about this UX if it's a feature you would find useful. |
* **EPUB Rendering & Optimization:** Improvements to the rendering engine, CSS/image handling, and parsing performance.
* **Typography & Legibility:** Custom font support, hyphenation, line and paragraph spacing, margins.
* **E-Ink Driver Refinement:** Reducing full-screen flashes (ghosting management) and improving general rendering.
* **Reading UX:** Bookmarks, progress tracking, button mapping, page navigation, and other in-reader interactions.
* **Library Management:** Simple, intuitive ways to organize and navigate a local book collection.
* **Memory, Flash, and Code Quality:** Refactors and cleanups that reduce resource use or improve maintainability, even without a user-visible feature.
### Out-of-Scope
*These items are rejected because they compromise the device's stability or mission.*
*Rejected because they compromise the device's stability, maintainability, or core mission.*
* **Interactive Apps:** No Notepads, Calculators, or Games. This is a reader, not a PDA.
* **Active Connectivity:** No RSS readers, News aggregators, or Web browsers. Background Wi-Fi tasks drain the battery
and complicate the single-core CPU's execution.
* **Media Playback:** No Audio players or Audio-books.
* **Complex Annotation:** No typed out notes. These features are better suited for devices with better input
capabilities and more powerful chips.
* **Interactive Apps:** No notepads, calculators, or games. These belong in other forks and are not part of CrossPoint's focus.
* **Writing / Authoring Tools:** No typed notes, journals, or editors. Input hardware and RAM are wrong for this, and other forks already explore this space.
* **Active Connectivity:** No RSS readers, news aggregators, or web browsers. Background Wi-Fi drains the battery and complicates the single-core CPU.
* **PDF Rendering:** PDFs are fixed-layout documents, so rendering them requires displaying pages as images rather than reflowable text, resulting in constant panning and zooming that makes for a poor reading experience on e-ink. Out of scope on the current hardware class.
### In-scope — Technically Unsupported
## 5. Calls to Action
*These features align with CrossPoint's goals but are impractical on the current hardware or produce poor UX.*
These are the areas where contributor help is most valuable right now. If you want to take one of these on, open a Discussion or issue first so we can coordinate.
* **PDF Rendering:** PDFs are fixed-layout documents, so rendering them requires displaying pages as images rather than reflowable text — resulting in constant panning and zooming that makes for a poor reading experience on e-ink.
### Theme System: Move Themes Off-Firmware
## 3. Idea Evaluation
We want to abstract themes out of the firmware entirely so they no longer consume flash, and instead load from the SD card. This directly supports the current focus on flash footprint and code cleanup.
While I appreciate the desire to add new and exciting features to CrossPoint Reader, CrossPoint Reader is designed to be
a lightweight, reliable, and performant e-reader. Things which distract or compromise the device's core mission will not
be accepted. As a guiding question, consider if your idea improve the "core reading experience" for the average user,
and, critically, not distract from that reading experience.
* **Status:** [@itsthisjustin](https://github.com/itsthisjustin) plans to take this on eventually but is very open to someone else claiming it sooner.
* **Why it matters:** Every built-in theme costs flash that we would rather spend on rendering, fonts, or future device support. SD-loaded themes also let users customize without rebuilding firmware. It also leads to SD font loading for better language support in the UI.
* **How to claim:** Comment on the relevant Discussion (or open one) before starting.
> **Note to Contributors:** If you are unsure if your idea fits the scope, please open a **Discussion** before you start
> coding!
### Identifying Other Stock-Firmware Gaps
We want help cataloguing things the stock firmware (and other popular CrossPoint forks) handle poorly or not at all, so future work has a clear target list. Particularly interested in:
* **RTL (right-to-left) text support:** Arabic, Hebrew, Persian, and similar scripts.
* **Languages with poor stock and fork coverage:** Especially those that need shaping, complex layout, or non-Latin font work that nobody is handling well today.
* **Other gaps:** Rendering edge cases, accessibility issues, input quirks, anything stock does badly and existing forks have not fixed.
If you can read or use the device in one of these languages, your feedback (even without code) is genuinely useful. Open a Discussion with concrete examples (screenshots, sample EPUBs, expected vs actual behavior) and we will prioritize from there.
## 6. Funding and Contributor Sustainability
CrossPoint uses [Royalty.dev](https://royalty.dev) (yes, a product built by [@itsthisjustin](https://github.com/itsthisjustin)) to fund contributors. There has been some tension in the community around this, so the intent is being clarified here directly.
**Why we do this:**
* To maintain long-term interest from contributors and maintainers, in direct response to substantial community requests for a way to give back.
* To motivate contributors to invest in the *core* project rather than spinning up competing forks.
* To help pay for new ESP32 devices so we can port CrossPoint to additional hardware.
* To give the project a credible long-term path to sustainability.
**How it works:**
* Funds are distributed automatically to contributors based on impact to the codebase and tenure on the project.
* Over **$600** was raised in the first few hours after opening up funding, which is a signal the demand is real.
* The exact scoring methodology is published at <https://app.royalty.dev/transparency>.
**This is not fixed in stone.** The weighting, eligibility, and distribution rules can be tweaked as we learn what works for this project. If you have concerns or suggestions about how funds are allocated, open a Discussion. The goal is a system that fairly recognizes the people doing the work, not a perfect one on day one.
+66 -40
View File
@@ -5,6 +5,7 @@ Welcome to the **CrossPoint** firmware. This guide outlines the hardware control
- [CrossPoint User Guide](#crosspoint-user-guide)
- [1. Hardware Overview](#1-hardware-overview)
- [Button Layout](#button-layout)
- [Taking a Screenshot](#taking-a-screenshot)
- [2. Power \& Startup](#2-power--startup)
- [Power On / Off](#power-on--off)
- [First Launch](#first-launch)
@@ -14,7 +15,11 @@ Welcome to the **CrossPoint** firmware. This guide outlines the hardware control
- [3.3 Browse Files Screen](#33-browse-files-screen)
- [3.4 Recent Books Screen](#34-recent-books-screen)
- [3.5 File Transfer Screen](#35-file-transfer-screen)
- [3.5.1 Calibre Wireless Transfers](#351-calibre-wireless-transfers)
- [3.5.1 Calibre Wireless Transfers](#351-calibre-wireless-transfers)
- [Installing the Plugin in Calibre](#installing-the-plugin-in-calibre)
- [Configuring the CrossPoint Plugin in Calibre](#configuring-the-crosspoint-plugin-in-calibre)
- [Uploading Books](#uploading-books)
- [Removing a Book](#removing-a-book)
- [3.6 Settings](#36-settings)
- [3.6.1 Display](#361-display)
- [3.6.2 Reader](#362-reader)
@@ -23,21 +28,27 @@ Welcome to the **CrossPoint** firmware. This guide outlines the hardware control
- [3.6.5 OPDS Servers (Multiple Libraries)](#365-opds-servers-multiple-libraries)
- [3.6.6 Web Settings (Wi-Fi + OPDS)](#366-web-settings-wi-fi--opds)
- [3.6.7 KOReader Sync Quick Setup](#367-koreader-sync-quick-setup)
- [Option A: CrossPoint Sync Server (`sync.crosspointreader.com`, default)](#option-a-crosspoint-sync-server-synccrosspointreadercom-default)
- [Option B: Legacy Public KOReader Server (`sync.koreader.rocks`)](#option-b-legacy-public-koreader-server-synckoreaderrocks)
- [Option C: Self-Hosted Server (Docker Compose)](#option-c-self-hosted-server-docker-compose)
- [Syncing While Reading](#syncing-while-reading)
- [3.7 Sleep Screen](#37-sleep-screen)
- [Cover settings](#cover-settings)
- [Custom images](#custom-images)
- [3.8 Custom Fonts (SD Card)](#38-custom-fonts-sd-card)
- [4. Reading Mode](#4-reading-mode)
- [Page Turning](#page-turning)
- [Chapter Navigation](#chapter-navigation)
- [Auto Page Turn](#auto-page-turn)
- [Tilt Page Turn (X3 only)](#tilt-page-turn-x3-only)
- [Footnote Navigation](#footnote-navigation)
- [System Navigation](#system-navigation)
- [Supported Languages](#supported-languages)
- [Page Turning](#page-turning)
- [Chapter Navigation](#chapter-navigation)
- [Auto Page Turn](#auto-page-turn)
- [Tilt Page Turn (X3 only)](#tilt-page-turn-x3-only)
- [Footnote Navigation](#footnote-navigation)
- [System Navigation](#system-navigation)
- [Supported Languages](#supported-languages)
- [5. Reader Menu](#5-reader-menu)
- [5.1 Chapter Selection](#51-chapter-selection)
- [5.2 Bookmarks](#52-bookmarks)
- [6. Current Limitations & Roadmap](#6-current-limitations--roadmap)
- [7. Troubleshooting Issues & Escaping Bootloop](#7-troubleshooting-issues--escaping-bootloop)
- [5.1 Chapter Selection](#51-chapter-selection)
- [5.2 Bookmarks](#52-bookmarks)
- [6. Current Limitations \& Roadmap](#6-current-limitations--roadmap)
- [7. Troubleshooting Issues \& Escaping Bootloop](#7-troubleshooting-issues--escaping-bootloop)
## 1. Hardware Overview
@@ -252,6 +263,8 @@ The Settings screen allows you to configure the device's behavior. There are a f
- "ON" - Vertical space will be added between paragraphs in Reading Mode
- "OFF" - Paragraphs will not have vertical space added, but will have first-line indentation
- **Dictionary**: Select the StarDict dictionary used for word lookups while reading, or "None" to disable lookups. *(Only shown when at least one dictionary folder exists under `/dictionaries/` on the SD card — see [docs/dictionary.md](docs/dictionary.md) for setup and usage.)*
- **Text Anti-Aliasing**: Whether to show smooth grey edges (anti-aliasing) on text in reading mode. Note this slows down page turns slightly.
- **Images**: Whether to display embedded images (JPG/PNG) found in EPUB files; options are "ON" (default) or "OFF".
@@ -271,6 +284,7 @@ The Settings screen allows you to configure the device's behavior. There are a f
- **Long-press Menu**: Selects the function bound to holding the menu button (Confirm) while reading an EPUB. **Cycles through the available functions** each time the setting is selected — additional functions may be added in future releases, so this is not a binary on/off toggle. A short press of Confirm always opens the reader menu as normal:
- "Bookmark" (default) - Hold Confirm (~0.4 second) to drop a bookmark at the current page.
- "KOSync" - Hold Confirm (~1 second) to launch KOReader sync directly.
- "Dictionary" - Hold Confirm (~0.4 second) to start dictionary word selection on the current page (see [docs/dictionary.md](docs/dictionary.md)).
- "Disabled" - Long-press is ignored; only short-press opens the reader menu.
- **Short Power Button Click**: Controls the effect of a short click of the power button:
@@ -288,7 +302,7 @@ The Settings screen allows you to configure the device's behavior. There are a f
- **Wi-Fi Networks**: Connect to Wi-Fi networks for file transfers and firmware updates.
- **KOReader Sync**: Options for setting up KOReader for syncing book progress.
- **KOReader Sync**: Options for setting up KOReader for syncing book progress. **Smart sync** is the default for new configurations and auto-resolves simple push/pull decisions. Existing credential files retain **Ask every time** when migrated; you can switch Sync Behavior at any time if you prefer manual confirmation.
- **OPDS Servers**: Manage one or more OPDS [(Open Publication Distribution System)](https://en.wikipedia.org/wiki/Open_Publication_Distribution_System) libraries for browsing and downloading books. See [OPDS Servers (Multiple Libraries)](#365-opds-servers-multiple-libraries) below.
@@ -351,9 +365,37 @@ Behavior notes:
CrossPoint can sync reading progress with KOReader-compatible sync servers.
It also interoperates with KOReader apps/devices when they use the same server and credentials.
##### Option A: Free Public Server (`sync.koreader.rocks`)
##### Option A: CrossPoint Sync Server (`sync.crosspointreader.com`, default)
1. Register a user once (only if needed):
When **Sync Server URL** is left empty, CrossPoint uses the free CrossPoint sync server at `https://sync.crosspointreader.com`. It speaks the standard KOReader sync protocol (so KOReader apps can use it too). CrossPoint records page starts as chapter-content offsets and sends the corresponding standard KOReader XPath, so devices with different fonts or layouts can return to the same text.
1. On each CrossPoint device:
- Go to **Settings -> System -> KOReader Sync**.
- Set **Username** and **Password** (enter the plain password; CrossPoint computes MD5 internally, and use the same values on all devices).
- Leave **Sync Server URL** empty (or set it to `https://sync.crosspointreader.com`).
- On the first device, run **Sign Up** once to create the account directly from the device. On every other device, just run **Authenticate**.
Accounts are per server. Existing `sync.koreader.rocks` credentials do not exist on the CrossPoint server; either sign up again with the same username/password or use Option B to keep using the legacy server.
##### Option B: Legacy Public KOReader Server (`sync.koreader.rocks`)
Use this if you already sync KOReader devices against the official public server.
1. On each CrossPoint device:
- Go to **Settings -> System -> KOReader Sync**.
- Set **Sync Server URL** to `https://sync.koreader.rocks` (required; an empty URL now points at the CrossPoint server instead).
- Set **Username** and **Password** to your existing KOReader Sync credentials.
- Run **Authenticate**.
2. If you do not have an account yet, run **Sign Up** on the device, or register once with curl:
```bash
USERNAME="user"
@@ -366,27 +408,9 @@ curl -i "https://sync.koreader.rocks/users/create" \
--data "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD_MD5\"}"
```
Already have KOReader Sync credentials? Skip registration; basic sync only requires using the same existing username/password on all devices.
When this returns `HTTP 402` with `{"code":2002,"message":"Username is already registered."}`, pick a different username or use that existing account.
2. On each CrossPoint device:
- Go to **Settings -> System -> KOReader Sync**.
- Set **Username** and **Password** (enter the plain password; CrossPoint computes MD5 internally, and use the same values on all devices).
- Set **Sync Server URL** to `https://sync.koreader.rocks`, or leave it empty (both use the same default KOReader sync server).
- Run **Authenticate**.
3. While reading, press **Confirm** to open the reader menu, then select **Sync Progress**.
- Choose **Apply Remote** to jump to remote progress.
- Choose **Upload Local** to push current progress.
##### Option B: Self-Hosted Server (Docker Compose)
##### Option C: Self-Hosted Server (Docker Compose)
1. Start a sync server:
@@ -459,11 +483,12 @@ If this returns `HTTP 402` with `{"code":2002,"message":"Username is already reg
If you use the HTTPS listener, use `https://<server-ip>:7200` (`curl -k` only for self-signed certificate testing).
5. While reading, press **Confirm** to open the reader menu, then select **Sync Progress**.
- Choose **Apply Remote** to jump to remote progress.
- Choose **Upload Local** to push current progress.
##### Syncing While Reading
Once any of the options above is set up, press **Confirm** while reading to open the reader menu, then select **Sync Progress**. Alternatively, set **Settings -> Controls -> Long-press Menu** to **KOSync** and hold Confirm to launch sync directly.
- With **Sync Behavior** set to **Ask every time**, choose **Apply Remote** to jump to remote progress or **Upload Local** to push current progress.
- With **Sync Behavior** set to **Smart sync**, CrossPoint auto-resolves simple cases: upload when no remote progress exists, confirm and leave both unchanged when local and remote progress are already synchronized, upload when local progress is further ahead, or apply remote when remote progress is further ahead.
### 3.7 Sleep Screen
@@ -561,7 +586,7 @@ If the device goes to sleep or you close the book while viewing a footnote, the
* **Return to Home:** Press the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.
* **Return to Browse Files:** Press and hold the **Back** button to close the book and return to the **[Browse Files](#33-browse-files-screen)** screen.
* **Reader Menu:** Press **Confirm** to open the **[Reader Menu](#5-reader-menu)**, which includes chapter navigation, reading options, and more.
* **Long-press Confirm (configurable):** Holding **Confirm** runs the function chosen by the **Long-press Menu** setting in **[Controls Settings](#363-controls)** — "Bookmark" (default) drops a bookmark, "KOSync" launches KOReader Sync, "Disabled" does nothing. A short press always opens the Reader Menu.
* **Long-press Confirm (configurable):** Holding **Confirm** runs the function chosen by the **Long-press Menu** setting in **[Controls Settings](#363-controls)** — "Bookmark" (default) drops a bookmark, "KOSync" launches KOReader Sync, "Dictionary" starts a word lookup, "Disabled" does nothing. A short press always opens the Reader Menu.
### Supported Languages
@@ -583,6 +608,7 @@ Available options include:
- **Select Chapter** – Open the table of contents to jump to a specific chapter (see [Chapter Selection](#51-chapter-selection) below).
- **Footnotes** – Navigate to the footnotes for the current section *(only shown in books that contain footnotes)*.
- **Look Up** – Select a word on the current page and show its dictionary definition (see [docs/dictionary.md](docs/dictionary.md)). Requires a dictionary to be selected in **Settings → Reader → Dictionary**.
- **Reading Orientation** – Cycle through screen orientations without leaving the reader.
- **Auto Turn (Pages Per Minute)** – Cycle through automatic page turn speed options for hands-free reading.
- **Go to %** – Jump to a specific position in the book by percentage.
+1
View File
@@ -47,6 +47,7 @@ git ls-files --exclude-standard ${GIT_LS_FILES_FLAGS} \
| grep -v -E '^lib/EpdFont/builtinFonts/' \
| grep -v -E '^lib/Epub/Epub/hyphenation/generated/' \
| grep -v -E '^lib/uzlib/' \
| grep -v -E '^lib/miniz/third_party/' \
| xargs -r "${CLANG_FORMAT_BIN}" -style=file -i
# Restore strict pipeline failure handling for the rest of the script.
set -o pipefail
+1
View File
@@ -7,5 +7,6 @@ It is written for software developers who may be new to embedded development.
- [Architecture Overview](./architecture.md)
- [Development Workflow](./development-workflow.md)
- [Testing and Debugging](./testing-debugging.md)
- [Touch and UI Development](./touch-and-ui.md)
If you are new, start with [Getting Started](./getting-started.md).
+5 -3
View File
@@ -154,9 +154,11 @@ Typical persisted areas on SD:
state.json
```
`sections/*.bin` contains rendered pages plus anchor, paragraph, and list-item
lookup tables used for TOC/footnote jumps and KOReader sync refinement. For
binary cache formats, see `docs/file-formats.md`.
`sections/*.bin` contains rendered pages plus anchor, paragraph, list-item, and
page-start visible-text-offset lookup tables. The offset table makes reading
positions content-based: KOReader XPaths resolve to an exact chapter offset,
and the current layout derives the corresponding page. For binary cache
formats, see `docs/file-formats.md`.
## Networking architecture
+202
View File
@@ -0,0 +1,202 @@
# Touch and UI Development
CrossPoint runs on touch devices (Seeed Sticky, M5Paper, LilyGo T5) alongside the button-only Xteink X3/X4. Every screen must work with both input styles.
**There is one supported way to build a new screen: FreeInkUI, hosted through the firmware base classes below.** Touch hit-testing, tap highlighting, long-press, swipe scrolling, and button focus navigation all come from the shared stack; you never hand-roll coordinate math.
The old bridge helpers (`rowTouch`, `colTouch`, `wasTapInRect`, manual rect `contains()` checks) are legacy. They survive only for the two remaining hand-rolled surfaces (the theme-driven home screen and the reader page) and must not appear in new code. PRs that add new uses will be asked to convert.
---
## Picking the right host
| Your screen is... | Use | In-tree reference |
|---|---|---|
| A single list of rows | subclass `UiListActivity` | [`LanguageSelectActivity`](../../src/activities/settings/LanguageSelectActivity.cpp) (minimal), [`RecentBooksActivity`](../../src/activities/home/RecentBooksActivity.cpp) (long-press) |
| Tabbed lists | subclass `UiTabListActivity` | [`SettingsActivity`](../../src/activities/settings/SettingsActivity.cpp) |
| Custom FUI layout (sliders, prompts, state machines) | inherit `UiAppHost` directly | [`EpubReaderPercentSelectionActivity`](../../src/activities/reader/EpubReaderPercentSelectionActivity.cpp), [`WifiSelectionActivity`](../../src/activities/network/WifiSelectionActivity.cpp) |
| A modal picker or confirm inside a legacy activity | `OptionPopup` (or push `ConfirmationActivity`) | [`OtaUpdateActivity`](../../src/activities/settings/OtaUpdateActivity.cpp) |
| A Yes/No prompt inside a FUI screen | build `fui::optionDialog` into the screen | `WifiSelectionActivity::buildPromptDialog` |
All four paths route input through the same SDK interaction table, so touch and physical buttons fire the same actions with no per-screen coordinate code.
## The hosting stack
`UiAppHost` ([src/components/UiAppHost.h](../../src/components/UiAppHost.h)) owns what every FUI screen shares: the font-bound render target, the `FreeInkApp`, and the `uiReady` handshake that lets the loop task route touch snapshots against the interaction table the render task rebuilds. Never re-implement that handshake; it is a cross-task protocol and lives in exactly one place.
`UiListActivity` ([src/activities/UiListActivity.h](../../src/activities/UiListActivity.h)) layers the list protocol on top: swipes scroll the viewport without moving the selection, buttons move the selection and pull the viewport along (`fui::ListNav`), plus the render skeleton (header chrome, app, footer hints). `UiTabListActivity` is the tab-bar sibling; its selection ring puts the tab bar at position 0 and the rows after it.
### New list screen skeleton
Subclasses supply the data; the base owns the loop. The whole contract:
```cpp
class MyListActivity final : public UiListActivity {
public:
MyListActivity(GfxRenderer& r, MappedInputManager& in) : UiListActivity("MyList", r, in) {}
private:
std::vector<Entry> entries; // the activity's data, loaded in onEnter
std::vector<fui::ListItem> rows; // activity-owned row cache: rebuilt only when entries changes, reused every render
int listCount() const override { return entries.size(); }
const char* headerTitle() const override { return tr(STR_MY_TITLE); }
void onEnter() override {
UiListActivity::onEnter();
entries = /* ... load from wherever ... */;
rebuildRows();
}
// Called whenever entries changes (here, only onEnter; a mutable list would
// also call this after any add/remove). NOT called from buildScreen().
void rebuildRows() {
rows.clear();
rows.reserve(entries.size());
// ... one fui::ListItem per entry (label, actionValue = index) ...
}
void buildScreen(UiScreen& screen) override {
// set content margin from the theme safe area, then:
fui::ListProps props;
props.items = rows.data();
props.count = rows.size();
props.action = ACTION_ROW;
props.inputMask = fui::InputTouch; // physical buttons stay in the base loop
syncListViewport(screen, props); // selection/viewport handoff, always right before list()
screen.list(props);
}
void activateIndex(int index) override {
app.clearTapFlash(); // leaving the screen: a lingering flash would gray the next render
// ... open the thing ...
}
};
```
See [`FileBrowserActivity`](../../src/activities/home/FileBrowserActivity.cpp)'s `rebuildRowItems()` for this pattern applied to a directory listing that can run into the hundreds of entries, and `LanguageSelectActivity.cpp` for the rest of the skeleton (content-margin math, footer, etc.) — note that file still builds its row vector locally inside `buildScreen()`; match `FileBrowserActivity`, not that file, for the row cache. Optional overrides: `onRowLongPress(index)`, `drawFooter()`, `handleButtons()` for extra physical-button handling, and `ACTION_USER`-and-up action ids for non-row elements (register handlers in `onEnter` after the base's).
### Rules that apply to every FUI screen
- Register actions with `app.on(...)` in `onEnter` (after `resetUi()` for direct `UiAppHost` users). Handlers receive a `void* user` you cast back to the activity.
- Handlers that leave the current screen call `app.clearTapFlash()` first.
- Theme tokens are shared and bound by `resetUi()`; never call `app.setTheme` yourself. Metrics flow from the active UITheme through [`UIThemeTokens.h`](../../src/components/UIThemeTokens.h), including the per-board bezel insets that keep scrollbars visible.
- `TextStyle.maxLines` defaults to 1 and truncates with an ellipsis. Set `maxLines` explicitly on any dialog headline or message that can wrap.
- Everything stays allocation-free in steady state. A local `std::vector` inside `buildScreen()` is **not** allocation-free even with `reserve()` first: it starts at zero capacity on every call, `reserve()` allocates, and the destructor frees that storage before the call returns — real allocator work and fragmentation risk on every repaint (cursor move, tap flash, ...), not just on data changes. Build `ListItem` rows into activity-owned storage instead, reserved once when the underlying data loads (`onEnter()`/a `load*()` — see the skeleton above and `FileBrowserActivity::rebuildRowItems()`), and reused unchanged by every `buildScreen()` call. Use a fixed-capacity array (e.g. `ListItem rows[MAX]`, as `OptionPopup` and `KOReaderSyncActivity`'s action rows do) when the count is small and bounded. Do not hold FUI `props` across renders — only the row storage they point into.
### Component inventory
All under `freeink-sdk/libs/ui/FreeInkUI/include/components/`:
| Category | Components |
|---|---|
| Controls | `button`, `checkbox`, `slider`, `progress-bar`, `header` |
| Lists | `list` (virtualized), `table`, `dropdown`, `radio-group`, `setting-row`, `toggle-row`, `stepper-row` |
| Keyboard | `keyboard` (QWERTY/AZERTY/QWERTZ/ES layouts), `key-grid` |
| Overlays | `popup`, `option-dialog`, `context-menu`, `message-panel`, `toast` |
| Bars | `status-bar`, `tab-bar`, `reader-chrome`, `battery-indicator`, `gesture-bar`, `tap-zones` |
| Media | `book-card`, `cover-grid`, `cover-carousel`, `metric-card` |
| Text | `text-field`, `text-area` |
One exception to "always go through a host": `KeyboardEntryActivity` drives the keyboard component with a raw `fui::Frame` and `TouchHoldRouter` because per-key hold repeat needs its own routing. If you think your screen needs that, raise it in the PR first.
---
## Global gestures: do not reimplement these
Three gestures are handled once, for every screen. Activities must not add their own edge-swipe handling:
| Gesture | Trigger | Where it is handled |
|---|---|---|
| Back | Right-swipe starting in the left 25% of the screen | Folded into `Button::Back`, so the existing `wasPressed(Button::Back)` in your activity already fires |
| Home | Up-swipe starting in the bottom 14% | `ActivityManager::loop()`; pops to Home (activities can override via `handleHomeGesture()`) |
| Menu | Down-swipe starting in the top 14% | Activities that have a menu check `wasMenuGesture()` themselves (the reader does this) |
Because the back gesture arrives as `Button::Back`, most button-era activities gain back-swipe support with zero changes.
---
## Legacy bridge helpers (do not use in new code)
`MappedInputManager` still exposes raw touch accessors. The FUI stack consumes them internally (via `touchSnapshotFrom` in [`UiAppHelpers.h`](../../src/components/UiAppHelpers.h)); activities should not.
| Helper | Status |
|---|---|
| `wasScreenTapped` / `wasScreenTouchDown` / `isScreenTouchHeld` | Consumed by the FUI snapshot builder. Direct use only in the two legacy surfaces |
| `wasTapInRect(x, y, w, h)` | Legacy one-off hit test |
| `rowTouch` / `colTouch` | Legacy row/column band math. Sole remaining user: `HomeActivity` (theme-driven layout) |
| `wasSwipe()` | Raw swipe direction, for behavior beyond the global gestures (reader page turns) |
| `hasTouch()` | Still fine anywhere: gate touch-only chrome (on-screen Cancel/OK pairs) on it |
The `wasListItemTapped` / `wasListItemTouchedDown` helpers from the button-era bridge have been removed; every themed list is a `UiListActivity` now. If you are porting a branch that still calls them, convert the screen to `UiListActivity` rather than resurrecting the helpers.
As with all input: never call the SDK `InputManager` or read GPIO directly. The HAL rule from the main guide applies to touch too.
---
## Building and testing on non-Xteink devices
Each MCU family is its own binary: X3/X4 are ESP32-C3, Sticky and LilyGo T5 are ESP32-S3, M5Paper v1.1 is a classic ESP32. The Sticky env ships in `platformio.ini` (`pio run -e sticky`). Envs for other devices go in **`platformio.local.ini`**, a gitignored file that PlatformIO merges over `platformio.ini` (see `extra_configs`). Create it next to `platformio.ini`; personal envs, ports, and debug flags live there and never get committed.
Both envs below extend the repo's `[base]`, so they build against the `freeink-sdk` submodule with all the normal deps and scripts.
### M5Paper v1.1 (classic ESP32, IT8951 panel)
```ini
[env:m5paper_v11]
extends = base
board = esp32dev
board_build.mcu = esp32
board_build.flash_mode = qio
; CP2104 UART bridge: 921600 drops out on macOS after the stub baud switch
upload_speed = 460800
build_unflags =
${base.build_unflags}
; classic ESP32 has UART serial, not USB CDC; Logging.h keys off these
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1
build_flags =
${base.build_flags}
-DFREEINK_DEVICE_M5PAPER=1
; the 63KB 540x960 framebuffer lives in PSRAM (FREEINK_FB_PSRAM auto-on)
-DBOARD_HAS_PSRAM
-DCROSSPOINT_VERSION=\"${crosspoint.version}-m5paper\"
-DENABLE_SERIAL_LOG
-DLOG_LEVEL=2
; touch-first device: hide front-button hint labels
-DCROSSPOINT_SHOW_BUTTON_HINTS=0
; archive-scan-order workaround: without these a full relink drops Wire's i2c symbols
-Wl,-u,i2cInit
-Wl,-u,i2cSlaveInit
```
### LilyGo T5 S3 (ESP32-S3, controller-less panel via LovyanGFX)
```ini
[env:lilygo_t5s3]
extends = base
board = esp32-s3-devkitc1-n16r8
board_build.mcu = esp32s3
build_flags =
${base.build_flags}
-DFREEINK_DEVICE_LILYGO=1
; board injects the parallel-bus pins + PMIC power hooks (BoardT5S3)
-DFREEINK_LGFX_EPD_CONFIG=lilygoT5S3LgfxConfig
-DCROSSPOINT_VERSION=\"${crosspoint.version}-lilygo\"
-DENABLE_SERIAL_LOG
-DLOG_LEVEL=2
-DCROSSPOINT_SHOW_BUTTON_HINTS=0
lib_deps =
${base.lib_deps}
; LgfxEpdConfig for the T5 S3 (pins, PCA9535/TPS65185 power sequence)
BoardT5S3=symlink://freeink-sdk/libs/hardware/BoardT5S3
; LovyanGFX Panel_EPD drives the controller-less ED047TC1 panel
m5stack/M5GFX @ 0.2.20
```
Then `pio run -e m5paper_v11 -t upload` (or `-e lilygo_t5s3`). Gotchas worth knowing:
- **Flash mode matters.** The M5Paper is `qio`; the X4-family standalone envs need `dio`. A wrong flash-mode header boots into a `partition 0 invalid magic number 0xffff` loop even though esptool verified the write.
- **One `FREEINK_DEVICE_*` flag per env** selects the board profile (pins, panel, touch controller) from the SDK's `BoardConfig`. See `freeink-sdk/platformio.sample.ini` for reference envs of every supported device.
- **Serial logs:** `[base]` does not enable logging; without `-DENABLE_SERIAL_LOG` a non-default env prints nothing.
- No touch hardware on your desk? The X4 build still exercises the same code paths through buttons; touch-specific behavior (tap zones, gestures) needs a real device.
+69
View File
@@ -0,0 +1,69 @@
# Dictionary
Look up words while reading an EPUB using an offline StarDict dictionary stored on the SD card.
## Supported Format
The reader supports **StarDict** dictionaries. When searching for dictionaries online, look for "StarDict format" or files with `.dict`, `.idx`, and `.ifo` extensions.
A dictionary folder must contain:
- `.idx` — word index (required, **must be uncompressed** — a `.idx.gz` will not work; decompress it on your computer with `gzip -d` first)
- `.dict` or `.dict.dz` — definition data (`.dict.dz` is supported as-is; entries are decompressed on the fly during lookup)
- `.syn` — synonym index (optional; maps alternate spellings and irregular forms to their headword)
- `.ifo` — metadata (optional)
Not supported: dictionaries with 64-bit index offsets (`idxoffsetbits=64` in the `.ifo` — rare, and rejected with an error), and HTML-formatted definitions render as raw markup rather than styled text.
## Setting Up a Dictionary
1. Copy your dictionary folder(s) to `/dictionaries/` on the SD card — one dictionary per folder, e.g. `/dictionaries/webster/webster.idx` + `webster.dict.dz`. A hidden `/.dictionaries/` folder (dot-prefixed) works the same way, for keeping it out of the file browser.
2. Open **Settings → Reader → Dictionary** on the device.
3. Select a dictionary from the list, or **None** to disable lookups.
The Dictionary setting only appears when at least one usable dictionary folder exists. Folders containing more than one dictionary (multiple `.idx` stems) are skipped as ambiguous.
## Looking Up a Word
Two ways to start a lookup while reading:
- Open the reader menu (**Confirm**) and choose **Look Up**.
- Or set **Settings → Controls → Long-press Menu** to "Dictionary", then hold **Confirm** (~0.4s) on the reading page.
One word on the page becomes highlighted:
1. Use **Left/Right** to move between words in reading order, and the side **Up/Down** buttons to jump between lines.
2. Press **Confirm** to look up the highlighted word.
3. Press **Back** to return to the reader.
On the very first lookup with a dictionary (and again whenever the `.idx` or `.syn` source file changes), the reader shows *"Indexing dictionary…"* while it builds small sidecar files next to them — a `.qidx` for the word index, and a `.sidx` when a `.syn` synonym file is present. Each sidecar is rebuilt independently, only when its own source changes. This takes a few seconds for large dictionaries and makes all subsequent lookups fast. The sidecars can be deleted safely at any time — they will simply be rebuilt.
### How Lookup Works
1. **Direct match** — the word is found as-is (case-insensitive) in the dictionary index. Surrounding punctuation is ignored.
2. **Synonyms** — on a miss, if the dictionary ships a `.syn` file, alternate spellings and irregular forms recorded there are resolved to their headword (e.g. `oxen` → `ox`, `colour` → `color`). This step is skipped if the `.sidx` sidecar could not be built (e.g. transient low memory during indexing); the dictionary otherwise stays usable, and the build is retried the next time it is opened.
3. **Stemming** — still no match: common English word forms are retried automatically: possessives and plurals (`dogs` → `dog`, `stories` → `story`) and verb endings (`walked` → `walk`, `running` → `run`, `making` → `make`).
4. **Not found** — a short popup appears and you return to word selection.
## The Definition Screen
When a word is found, the definition screen shows the matched headword at the top and the definition text below, with a page counter for long definitions.
- **Left/Right** or side **Up/Down** — previous / next page
- **Back** — return to word selection
## Where to find dictionaries
> credit to https://github.com/koreader/koreader/wiki/Dictionary-support for the list.
- The [reader.dict](https://www.reader-dict.com) (ex "BoboTiG/ebook-reader-dict") project provides StarDict version of daily dumps of [Wiktionary](https://www.wiktionary.org/) monolingual dictionaries for a variety of languages. It also provides [non-free multilingual](https://www.reader-dict.com) dictionaries.
- The [WikDict](https://www.wikdict.com) project provides free bilingual dictionaries based on [Wiktionary](https://www.wiktionary.org/) for a lot of language pairs. StarDict versions can be [downloaded from here](https://download.wikdict.com/dictionaries/stardict/).
- The [`Vuizur/Wiktionary-Dictionaries`](https://github.com/Vuizur/Wiktionary-Dictionaries) repository contains dictionaries based on [Wiktionary](https://www.wiktionary.org/) from many languages to English, including English-English.
- The [DictInfo](https://www.dictinfo.com/) website provides outdated monolingual dictionaries based on [Wiktionary](https://www.wiktionary.org/).
- The [Firedict site](https://tuxor1337.frama.io/firedict/dictionaries.html) contains a list of freely available dictionaries.
- [wiktionary_stardict](https://xxyzz.github.io/wiktionary_stardict/): update monthly.
- [Fictionaries](https://fictionary.gumroad.com/) provides dictionaries for various speculative fiction books and series.
- [World Factbooks Archive](https://github.com/MilkMp/CIA-World-Factbooks-Archive-1990-2025) provides 36 years of CIA's World Factbook dictionaries in StarDict format.
- [StarDict-Hebrew](https://github.com/Uri-Tauber/StarDict-Hebrew) Hebrew-English StarDict versions of Babylon dictionaries.
+54 -14
View File
@@ -6,7 +6,7 @@ All POD fields are written in the ESP32 little-endian representation used by
## `book.bin`
### Version 7
### Version 10
`book.bin` stores EPUB metadata plus lookup tables for spine and TOC entries.
The current firmware writes this version from `BookMetadataCache`.
@@ -18,7 +18,7 @@ import std.mem;
import std.string;
import std.core;
#define EXPECTED_VERSION 7
#define EXPECTED_VERSION 10
#define MAX_STRING_LENGTH 65535
struct String {
@@ -90,21 +90,50 @@ if (parsedSize != fileSize) {
## `section.bin`
### Version 25
### Version 37
Each file in `sections/*.bin` stores one laid-out spine section. The header is
also the cache-busting key: if any layout-affecting setting differs from the
current reader settings, the section is discarded and rebuilt.
Version 25 includes:
Version 37 increases the fixed-size footnote href field from 96 to 256 bytes.
This changes each serialized footnote record from 128 to 288 bytes, so older
section caches must be discarded and rebuilt.
Version 36 invalidates cached word positions after ruby and CJK justification
layout changes.
Version 35 adds a header offset and a `uint32_t` entry per page for the
visible-text offset LUT. The other section LUTs remain unchanged.
Version 34 is binary-identical to version 33. The version was bumped because
word-gap suppression was narrowed to tokens glued together in the source: v33
dropped the gap between any two words meeting at a CJK break opportunity, which
collapsed the spaces between Hangul words, so v33 word positions no longer match
what the layout engine now produces.
Version 30 is binary-identical to version 29. The version was bumped because
Arabic contextual shaping changed text measurement (`getTextAdvanceX` now
measures the shaped visual text), so word positions cached by v29 no longer
match what `drawText` renders.
Version 28 introduced serialized word style bits for underline, strikethrough,
superscript, and subscript. The format also includes:
- cache-busting fields for paragraph alignment, hyphenation, embedded CSS,
image rendering mode, and Focus Reading
- page offset LUT
- per-page visible-text offset LUT (zero-based Unicode codepoints in `<body>`)
- anchor-to-page map for fragment and footnote navigation
- paragraph and list-item LUTs used by KOReader sync page refinement
- paragraph and list-item LUTs retained for navigation and legacy sync fallback
- optional per-word Focus Reading split metadata
- per-page footnote entries
- serialized word style bits for underline, strikethrough, superscript, and
subscript
- flat TextBlock word storage (v29): per-word arrays plus one shared
NUL-terminated text blob, replacing v28's length-prefixed word strings. The
on-disk order mirrors the in-RAM arena so the firmware reads a whole block
payload with a single allocation and a single SD read
ImHex pattern:
@@ -113,10 +142,10 @@ import std.mem;
import std.string;
import std.core;
#define EXPECTED_VERSION 25
#define EXPECTED_VERSION 37
#define MAX_STRING_LENGTH 65535
#define FOOTNOTE_NUMBER_LEN 32
#define FOOTNOTE_HREF_LEN 96
#define FOOTNOTE_HREF_LEN 256
struct String {
u32 length [[hidden, comment("String byte length")]];
@@ -174,14 +203,20 @@ struct BlockStyle {
struct TextBlock {
u16 wordCount;
String words[wordCount];
s16 wordXPos[wordCount];
WordStyle wordStyle[wordCount];
u8 hasFocus;
if (hasFocus != 0) {
u8 wordFocusBoundary[wordCount] [[comment("UTF-8 byte boundary between bold prefix and suffix")]];
u16 wordFocusSuffixX[wordCount] [[comment("Suffix x offset from word start")]];
u16 textBytes [[comment("Total size of text[], including one NUL per word")]];
if (wordCount > 0) {
u16 textOff[wordCount] [[comment("Byte offset of word i's text within text[]")]];
s16 wordXPos[wordCount];
if (hasFocus != 0) {
u16 wordFocusSuffixX[wordCount] [[comment("Suffix x offset from word start")]];
}
WordStyle wordStyle[wordCount];
if (hasFocus != 0) {
u8 wordFocusBoundary[wordCount] [[comment("UTF-8 byte boundary between bold prefix and suffix")]];
}
char text[textBytes] [[comment("All words back to back, each NUL-terminated")]];
}
BlockStyle blockStyle;
@@ -275,6 +310,7 @@ struct SectionBin {
u32 anchorMapOffset;
u32 paragraphLutOffset;
u32 listItemLutOffset;
u32 visibleTextLutOffset;
Page pages[pageCount];
@@ -296,6 +332,10 @@ struct SectionBin {
if (listItemLutOffset != 0 && paragraphLutOffset != 0) {
u16 listItemIndex[paragraphLut.count] @ listItemLutOffset;
}
if (visibleTextLutOffset != 0) {
u32 visibleTextOffset[pageCount] @ visibleTextLutOffset;
}
};
SectionBin section @ 0x00;
+131
View File
@@ -0,0 +1,131 @@
# Recovering a Bricked Xteink
![proceed at your own risk](./images/spiflash/fix_bricked.jpg)
This guide covers installing CrossPoint on an Xteink that's bricked, or stuck on firmware with no path to flash a replacement. It works by writing firmware directly to the SPI flash chip with an external programmer, bypassing the ESP32-C3 entirely.
If your device isn't USB-locked, flash it over USB instead — it's safer and much less invasive. What follows should only be a last resort.
*Example: a device stuck on Biscuit firmware, unresponsive to a normal USB flash.*
![biscuit](./images/spiflash/stuck_on_biscuit.jpg)
## Required Tools
- Acetone or another nail polish remover.
- A SPI flash programmer. On this guide we use a CH341a; A Bus Pirate, Raspberry Pi/Pico or suitable Arduino, also works.
- Programmer software. flashrom (with libftdi) work on all OSs.
- esptool.py, but only if you intend to dump firmware from another device to use as your source image.
## Before You Start
- Lithium batteries are dangerous, do not short or puncture. Be careful.
- The display is fragile, don't bend or flex it. do not force the ZIF connector.
- There is no undo here. A mis-wired clip or a short circuit can turn a bricked device into a dead one. Proceed at your own risk.
## Procedure
### 1. Obtain a Firmware Image
Two options:
- Use the backup provided by @Uri-Tauber at `crosspoint-reader/docs/images/spiflash/crosspoint_spiflash_backup.tar.xz`, and extract the `.bin` file.
- Dump one yourself from a working, unlocked device over USB:
- Turn the device on, connect the USB cable and do:
```bash
~$ pio pkg exec -p tool-esptoolpy -- esptool.py --chip esp32c3 -p /dev/ttyACM0 -b 921600 read_flash 0x000000 0x1000000 crosspoint_backup.bin
# or
~$ esptool.py --chip esp32c3 -p /dev/ttyACM0 -b 921600 read_flash 0x000000 0x1000000 crosspoint_backup.bin
```
### 2. Disconnect Power Sources and Accessories
- Remove and set the SD card aside for reinsertion later.
- Remove any USB cable.
- Confirm nothing else is supplying power to the board.
### 3. Remove the Screen
Ordinary nail polish remover softens the adhesive under the screen's edges — it takes some patience, and there's no clean way around that.
- In a well ventilated space, free of any ignition source, apply acetone or nail polish remover to the borders of the screen.
- Check it from time to time, make sure the borders of the screen stay wet for around 2 hours.
- Start to pry open from a lower corner, and move around.
- If necessary, use a non-conductive tool to help you.
- Flip open the ZIF connector to release the screen from the board.
![screen](./images/spiflash/remove_screen.jpg)
![inside](./images/spiflash/inside.jpg)
### 4. Disconnect the Battery
Cut a single battery wire close to the board, then cover the cut end with tape to prevent shorting.
![battery](./images/spiflash/disconnect_battery_and_sd_card.jpg)
### 5. Hold the Reset Button Down
Keeping reset held prevents the ESP32-C3 from interfering with SPI communication during the read/write. A piece of pointed plastic works well as a holder.
![reset](./images/spiflash/press_and_hold_reset.jpg)
### 6. Attach the Test Clip
Connect the test clip to the flash chip before connecting the programmer to USB. Notes:
- The clip's red wire aligns with pin 1, marked with a dot on both the chip and the board silkscreen.
- Confirm each lead is making contact with a chip pin and not the epoxy body.
- If the programmer has a voltage selector, set it to 3.3V.
- Verify no other power source is connected — use a multimeter if there's any doubt.
![chip](./images/spiflash/spi_flash_chip.jpg)
![connect](./images/spiflash/connect_clip_to_spiflash_chip.jpg)
### 7. Connect the Programmer to the PC
![ready](./images/spiflash/ready.jpg)
### 8. Verify the Connection by Reading Twice
Read the chip twice and compare hashes. If they don't match, something in the clip connection is off — fix that before writing anything to the chip.
```bash
~$ sudo flashrom --programmer ch341a_spi -r backup_0.bin
[...]
Reading flash... done.
~$ sudo flashrom --programmer ch341a_spi -r backup_1.bin
[...]
Reading flash... done.
# lets compare the hashes from the back ups
~$ md5sum backup_0.bin
211522e56616ea46ac9bcf82d3451eb2 backup_0.bin
~$ md5sum backup_1.bin
211522e56616ea46ac9bcf82d3451eb2 backup_1.bin
```
### 9. Flash the Chip
```bash
~$ sudo flashrom --programmer ch341a_spi -w crosspoint_backup.bin
[...]
Reading old flash chip contents... done.
Erasing and writing flash chip... Erase/write done.
Verifying flash... VERIFIED.
```
### 10. Power and Test The Device
- Disconnect the programmer from the USB port.
- Remove the test clip.
- Release the reset button.
- Reinsert the SD card.
- Reconnect the screen.
- Power the device via USB cable, no need to solder the battery yet.
- Press the power button for a couple seconds.
![success 0](./images/spiflash/success_0.jpg)
![success 1](./images/spiflash/success_1.jpg)
If CrossPoint boots successfully, the device can be fully reassembled.
+3 -2
View File
@@ -236,8 +236,9 @@ Older firmware versions used:
/.crosspoint/language.bin
```
On load, current firmware migrates that legacy file into `settings.json` and
renames it to `language.bin.bak`.
That legacy file is no longer read. Upgrading from a firmware old enough to
have written it resets the language to the default; re-select it once and it
persists in `settings.json`.
---
Loaded 20 of 388 files, more files were not shown because too many files have changed in this diff. Show more