diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..211fb77 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/kobalt"] + path = external/kobalt + url = https://github.com/42lizard/kobalt.git diff --git a/Dockerfile.qt6 b/Dockerfile.qt6 index 5daa86a..409e36c 100644 --- a/Dockerfile.qt6 +++ b/Dockerfile.qt6 @@ -4,11 +4,11 @@ FROM ghcr.io/fstanis/pocketbook-sdk-qt6-builder@sha256:1878e90fd4c9b8e736cd49b61 # Native build tools/QML runtime support offscreen host tests; armel libraries # build the device app. OpenSSL 3 replaces the legacy SDK crypto archive. RUN apt-get update && apt-get install -y --no-install-recommends \ - curl python3 pkg-config ninja-build \ + curl python3 pkg-config ninja-build patch qemu-user \ libssl-dev:armel libcurl4-openssl-dev:armel libxml2-dev:armel \ - libsqlite3-dev:armel libjson-c-dev:armel \ - qt6-base-dev qt6-declarative-dev \ - libssl-dev libcurl4-openssl-dev libxml2-dev libsqlite3-dev libjson-c-dev \ + libsqlite3-dev:armel libjson-c-dev:armel libpng-dev:armel \ + qt6-base-dev qt6-declarative-dev qt6-sensors-dev libqt6sql6-sqlite \ + libssl-dev libcurl4-openssl-dev libxml2-dev libsqlite3-dev libjson-c-dev libpng-dev \ qml6-module-qtquick qml6-module-qtquick-window qml6-module-qtqml-workerscript \ && rm -rf /var/lib/apt/lists/* diff --git a/README.md b/README.md index 30d46e7..051763c 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,12 @@ Other devices, including Verse Pro, need separate validation. ## Build +The experimental [Kobalt reader port](apps/kobalt/README.md) targets **InkPad +Color 3 (PB743K3), U743k3.6.10.2854**. It reuses Kobalt's EPUB renderer and +reading interface through the `external/kobalt` fork. It has separate build +and packaging commands; the normal app builds below remain unchanged. +Host reading tests and firmware import checks pass; device acceptance is pending. + With Docker and Compose running, from the repository root: ```sh diff --git a/apps/kobalt/README.md b/apps/kobalt/README.md new file mode 100644 index 0000000..8963583 --- /dev/null +++ b/apps/kobalt/README.md @@ -0,0 +1,44 @@ +# Kobalt on PocketBook + +The experimental PocketBook InkPad Color 3 port is maintained in +[42lizard/kobalt](https://github.com/42lizard/kobalt/tree/pocketbook-port), included +here as the `external/kobalt` submodule. Its buildchain is now self-contained: +the fork includes the SDK recipe, firmware preparation, ABI/runtime checks, +packaging, registration helper and tests. A standalone clone does not require +this monorepo. + +## Build and install + +Follow [PocketBook build and installation](../../external/kobalt/pocketbook/README.md) +for the complete workflow. From this repository: + +If this checkout has old monorepo build output, move +`external/kobalt/build/pocketbook` aside once; the new container uses a different +source mount, which invalidates the old CMake caches. + +```sh +git submodule update --init external/kobalt +cd external/kobalt +./build-pocketbook.sh --firmware /absolute/path/SWUPDATE.BIN --jobs 4 --test +``` + +The firmware input must be the official PB743K3 U743k3.6.10.2854 +`SWUPDATE.BIN`; its checksum is verified. The outputs are +`external/kobalt/build/pocketbook/Kobalt-PB743K3.zip` and its `pocketbook.json` +provenance record. Firmware libraries and user data are never packaged. +Installation and registration are documented in the fork; the build does not +write to a connected device. + +## Fork maintenance + +Keep the fork's `main` aligned with upstream. Rebase `pocketbook-port` onto new +upstream revisions instead of merging them, preserving a linear patch series +for a future upstream contribution. Before rewriting published history, archive +device-confirmed and tested source revisions, use an explicit force-with-lease, +and update this repository's submodule pin. + +The historical runtime evidence and device acceptance status are recorded in +[the firmware report](../../docs/research/kobalt-color3-runtime.md). Startup and +EPUB opening were confirmed with `6de0711`; the rebased upstream synchronization +`1a17e2f` has been installed but still awaits physical acceptance. Remaining +native integration limitations are listed in the fork's documentation. diff --git a/docs/research/kobalt-color3-runtime.md b/docs/research/kobalt-color3-runtime.md new file mode 100644 index 0000000..477fe28 --- /dev/null +++ b/docs/research/kobalt-color3-runtime.md @@ -0,0 +1,355 @@ +# Kobalt target runtime: InkPad Color 3 + +Inspected on 2026-10-13. Target: PocketBook InkPad Color 3, **PB743K3**, +user-reported firmware **U743k3.6.10.2854**. These findings supersede the +InkPad 4 runtime assumption in the initial feasibility study. No application +was run on the device during this investigation, and no device files were written. + +## Result + +The exact firmware ships **Qt 6.8.2**, including Core, Gui, Widgets and Network, +and the native **pocketbook2** Qt platform plugin. The existing Qt 6.8.2 +PocketBook SDK is therefore a suitable starting point. There is no observed +Qt-major-version blocker requiring a Qt 5 rewrite or bundling a foreign Qt +runtime. This is static compatibility evidence; successful application launch, +touch, refresh, suspend and shutdown still require device testing. + +## Primary source and artifact identity + +The [official support page](https://pocketbook.ch/en-ch/support) loads the +[product API for model 2256](https://pocketbook.ch/front-api/api/en-ch/product?product_id=2256). +Its `6.10.2854` entry links to the +[official firmware archive](https://download.pocketbook-int.com/fw/Yitoa/743k3/user/20260105/sw_20260105_U743k3_6.10.2854_user.zip). +The archive contains `fwinfo.xml` with version `U743k3.6.10.2854` and +`SWUPDATE.BIN`; the binary header also identifies `PocketBook743k3`. + +SHA-256 digests of inspected artifacts: + +| Artifact | SHA-256 | +| --- | --- | +| Downloaded ZIP | `769fb0ed98446fb134f6baf177504e1ab2e9552b2944f2a4ec55fab059d305cf` | +| `SWUPDATE.BIN` | `0ca8343a41e2497e0d038ed8f105be2f4d7f60ce9747e8ada24b164c5d5fd444` | +| `libQt6Core.so.6.8.2` | `1f8d2a814dbf64857ad7fbd4232b254267fd33e7c0434d95e71a8bb730b87f69` | +| Main-root `libc.so.6` | `a2e901366e98b866ec3521e5ebff30cc89387212be2d1deeb68fc093048adde3` | +| `libstdc++.so.6.0.32` | `a7b28dc0bb197ae4ee79ec6c0ec6af596dc88e60ad97553ef5351b0fcff819ef` | +| `libinkview.so` | `dbb87acdfeb18e246adb2abc265bb04786e2bb3eacd30c43f617a39987f69d7c` | +| `libqpocketbook2.so` | `e4e69c99932f7a62e7cfc11c502dc2b5177181eb71978cacfe6747306bf0a795` | + +## Shipped runtime observations + +Evidence comes from the downloaded firmware, inspected with 7-Zip 26.04, +GNU ARM `readelf`/`objdump` in the existing build container, and `strings`. +These are shell-tool observations, not REA Evidence IDs. REA's firmware tool +reported `capability_unavailable`: its Binwalk integration requires Linux and +an explicitly configured Binwalk executable, unavailable in the connected +macOS REA service. No REA setup changes were necessary for this bounded inspection. + +| Question | Observation | +| --- | --- | +| Qt generation/version | Real Core binary contains `Qt 6.8.2 (arm-little_endian-ilp32-eabi shared (dynamic) release build; by Clang 15.0.3)`. | +| Qt modules needed for standalone Widgets host | Real Core, Gui, Widgets and Network libraries are present, all version 6.8.2. | +| Qt Sensors | No Qt Sensors library appears in the main root, boot cramfs, ebrmain or nested runtime inventories. Make sensor support optional; do not assume the SDK omission alone establishes runtime absence. | +| ARM ABI | Qt Core ELF header: ELF32, little endian, ARM, EABI5, **soft-float ABI** (`0x5000200`). Attributes: ARMv7 application profile, Thumb-2, VFPv3, NEONv1. This is incompatible with an ARM hard-float calling convention. | +| Loader | Main root supplies `/lib/ld-linux.so.3`; Qt Core lists `ld-linux.so.3` as a dependency. | +| C runtime | Main-root `libc.so.6` identifies GNU C Library / Buildroot 2.39; highest numeric exported version definition is `GLIBC_2.39`. | +| C++ runtime | Firmware `libstdc++.so.6.0.32` exports through `GLIBCXX_3.4.32` and `CXXABI_1.3.14`; also includes ARM-specific ABI tags. | +| Native Qt platform | `plugins/platforms/libqpocketbook2.so`, plugin key `pocketbook2`; links Qt6 Gui/Core, InkView, `libhwconfig.so` and `libcommon_utilities.so`. | +| Refresh connection | The platform plugin imports InkView `FullUpdate`, `FullUpdateHQ`, `PartialUpdate`, `PartialUpdateHQ`, `PartialUpdateBW`, `PartialUpdateBlack` and `PartialUpdateDU4`. This establishes an existing firmware refresh integration, not its behavior for every application repaint. | + +Version ceilings are an initial filter, not a substitute for comparing the final +executable's undefined symbols, exact symbol versions and direct dependencies +against the extracted firmware exports. Qt private API compatibility is not +established merely by the public Qt version. + +The native platform plugin should own the hardware presentation path initially. +For an eventual direct framebuffer implementation, the +[KOReader PB743K3 adapter](https://github.com/koreader/koreader/blob/master/frontend/device/pocketbook/device.lua) +is useful independent implementation evidence: it sets RGB24 despite a reported +8-bit framebuffer, forced landscape-counterclockwise orientation, and viewport +`x=3, y=2, w=1398, h=1864`. These values were not independently measured on this +device and should not be copied into a native Qt host without a demonstrated need. + +## InkView exports + +All requested names exist as global defined function symbols in this firmware's +`libinkview.so`: + +| Symbol | ELF virtual address | Static observation | +| --- | --- | --- | +| `GetBatteryPower` | `0x75afc` | Tail branch to `hw_power@plt`. | +| `IsCharging` | `0x75ad0` | Tail branch to `hw_ischarging@plt`. | +| `GetFrontlightState` | `0x79abc` | Defined, 96 bytes. | +| `SetFrontlightState` | `0x79b78` | Defined, 88 bytes. | +| `OpenKeyboard` | `0x80b50` | Defined, 4536 bytes; `OpenKeyboardEx` also exists. | +| `FullUpdate` | `0x553d4` | Defined, 60 bytes; `FullUpdateHQ` also exists. | +| `BanSleep` | `0x75d18` | Tail branch to `hw_ban_suspend@plt`, not an empty stub. | +| `iv_sleepmode` | `0x783b8` | Defined, 20 bytes. | + +These are linked virtual addresses, not process runtime addresses. Symbol presence +does not verify SDK header declarations, function signatures, argument semantics +or power-management behavior. Use the corresponding API declaration and test the +chosen lifecycle on the actual device. + +## Retained local evidence and extraction + +Artifacts are ignored build outputs under `build/kobalt/firmware-color3/`: + +| Path relative to that directory | Contents | +| --- | --- | +| `fwinfo.xml`, `SWUPDATE.BIN` | Original ZIP members. | +| `rootfs.raw` | Gzip member at byte **15336448** in SWUPDATE, decompressed to a 32 MiB ext2 image. | +| `ebrmain.raw` | Gzip member at byte **20579328**, decompressed to a 517820416-byte ext2 image. | +| `cramfs.img` | Boot cramfs at byte **8193024**, length 6156288; separate from the main root. | +| `ebrmain/cramfs.img` | Member extracted from ebrmain; despite its filename this is **SquashFS 4, LZO**, not cramfs. Contains the real Qt/InkView libraries. | +| `rootfs/lib/libc.so.6`, `rootfs/lib/ld-linux.so.3` | Main-root C runtime and loader for final-binary checks. | +| `runtime/lib/libQt6Core.so.6.8.2` | Real Qt Core library. | +| `runtime/lib/libQt6Gui.so.6.8.2` | Real Qt Gui library. | +| `runtime/lib/libQt6Widgets.so.6.8.2` | Real Qt Widgets library. | +| `runtime/lib/libQt6Network.so.6.8.2` | Real Qt Network library. | +| `runtime/lib/libstdc++.so.6.0.32`, `runtime/lib/libinkview.so` | Real C++ runtime and InkView library. | +| `ebrmain/plugins/platforms/libqpocketbook2.so` | Native QPA plugin. | +| `files.txt`, `rootfs-files.txt`, `cramfs-files.txt`, `runtime-files.txt` | `7zz l` inventories of each respective filesystem. | + +The ebrmain `lib/libQt6*.so.6.8.2` entries are symlinks to +`../cramfs/lib/`. Merely extracting those links does not extract the actual +libraries. The runtime paths above refer to real files extracted from the nested +SquashFS image. 7-Zip declined unsafe/out-of-tree links during the preliminary +link inventory extraction; the required real files were subsequently extracted +directly without following those links. + +Recheck a selected library using the existing container, for example: + +```sh +docker compose run --rm qt6 arm-linux-gnueabi-readelf -h -A -d \ + /workspace/build/kobalt/firmware-color3/runtime/lib/libQt6Core.so.6.8.2 +docker compose run --rm qt6 arm-linux-gnueabi-readelf --version-info \ + /workspace/build/kobalt/firmware-color3/rootfs/lib/libc.so.6 +docker compose run --rm qt6 arm-linux-gnueabi-readelf --wide --dyn-syms \ + /workspace/build/kobalt/firmware-color3/runtime/lib/libinkview.so +``` + +## Qt Widgets startup compatibility + +The first device launches reached the start screen and then crashed in +`QRegion::operator+=`, called by `QApplication::compressEvent`. The native GDB +capture is retained locally in `build/kobalt/startup-debug/device-attempt-4.log`. +The QPA caller's return address maps to ELF virtual address `0x22548` in +`libqpocketbook2.so`. ARM disassembly immediately before that address shows a +12-byte allocation, `QEvent::QEvent` with type 78 (`UpdateLater`), and +`QCoreApplication::postEvent`. This is a plain event without a region payload. + +[Qt 6.8.2's QWidget event compression](https://github.com/qt/qtbase/blob/v6.8.2/src/widgets/kernel/qapplication.cpp#L785-L835) +assumes that duplicate `UpdateLater` events carry a private `QUpdateLaterEvent` +region. Posting two plain events to one QObject reproduces a segmentation fault +on host Qt 6.8.2 as well. The PocketBook application subclass delegates these +non-widget updates to the QGuiApplication path; QWidget receivers retain their +normal compression. The public event-queue regression test fails before the +change and verifies delivery of both updates afterward. The EPUB reopen test +also passes. The user subsequently confirmed successful startup on the device. + +The same device capture reports a 1404×1735 window on a 1404×1872 screen, with +device pixel ratio 1. The subsequent screenshot showed Android's logical-pixel +start page at native pixel scale. The port now uses the native library as its +entry point and the shared reader's display-sized opening view instead. + +## ARM EPUB rendering and native library entry + +The ARM workflow test reproduced a second failure with the extracted firmware +Qt and C runtime: SIGBUS in CREngine's `newItem`, while capturing the first +rendered page. GDB identified a `vst1.64 ... [r0 :128]` store to an address ending +in `0x568`. `LVFontGlyphCacheItem` declares `alignas(16)` but its two allocation +paths used `malloc`, which returned an address aligned to only eight bytes. +`crengine-glyph-alignment.patch` makes both paths use `posix_memalign` with the +structure's alignment. The same ARM test then passed: open, rendered page, +page turn, close, reopen at the same position, and failed-open recovery. + +`apps/kobalt/tools/check_arm_runtime.py` runs this check under QEMU using the +actual firmware loader, libc, Qt and offscreen QPA. Only hardware display, task +and power calls are stubbed. A test image is written to +`build/kobalt/arm-runtime-test/first-page.png`. It is not evidence that the +PocketBook QPA or native task switching works on the physical device. +Local before/after traces remain under `build/kobalt/arm-runtime/`. + +The firmware's EPUB association is +`epub:@EPUB_file:1:eink-reader_with_blink.app,eink-reader_with_epub2.app:ICON_EPUB`. +The registration helper appends `kobalt.app` while preserving existing user +handlers and their order. This follows the native `extensions.cfg` format also +used by [KOReader's PocketBook integration](https://github.com/koreader/koreader/blob/master/frontend/device/pocketbook/device.lua). +The no-argument launcher uses the firmware's `/ebrmain/bin/bookshelf.app`. +Successful reading calls InkView's `SetSubtaskInfo`, `BookReady` and `AddLastOpen`; +the reader close action drains work and exits. Progress and bookmarks still +use Kobalt storage; this is not complete parity with the Kobo integration. + +## Native library handler lookup + +The first library-entry package omitted `system/bin/kobalt.app`. Selecting +Kobalt in **Open with** consequently never reached the launcher in +`applications`, including after a reboot. An instrumented launcher produced no +new entry, while the device's `handlers.cfg` recorded the Kobalt selection. + +Static inspection of the exact firmware's `libinkview.so` (SHA-256 +`dbb87acdfeb18e246adb2abc265bb04786e2bb3eacd30c43f617a39987f69d7c`) +traced `OpenBook2` through `iv_OpenBook` at `0x42d70`. The multitasking launch +branch at `0x438a0` constructs `/mnt/ext1/system/bin/`, checks execute +access and falls back to `/ebrmain/bin/`. It does not use the +Applications directory for this reader lookup. REA evidence: +`ev_ecb92b7d2f48b19551b58a1713c1fddc6da7c217b81398c373ecc9fa38b195e1`. +This is static control-flow evidence, not an observed successful device launch. +The installed KOReader uses the same two-entry arrangement. + +The package now supplies an executable `system/bin/kobalt.app` that forwards +all arguments to `applications/kobalt.app`. Registration refuses an installation +missing either entry. The package regression failed on the missing handler +before the fix and now checks its presence, executable mode, argument forwarding +and exit status. The temporary diagnostic launcher is removed during the repair. +The subsequent device run reached Kobalt's opening view and failure dialog, +confirming that the corrected library handler launched the reader. + +## Reading without an IP loopback interface + +The next device log recorded `pocketbook-book-requested`, followed by +`native-resource-failed` with stage `loopback-listen` and Qt socket error 9. +The publication had passed inspection; the localhost resource listener failed +before rendering. The PocketBook adapter's loopback setup stub returns +`ENOTSUP`, so the shared TCP transport had assumed a configured loopback address. + +The real ARM workflow reproduced the failed open in a fresh Linux network +namespace (`unshare --net`), where loopback has no address. The test now reports +an opening failure immediately, reducing this negative check to 2.27 seconds. +`check_arm_runtime.py --without-network` retains this environment as a regression +check; `--book PATH` exercises a supplied local EPUB with the same workflow. + +Fork commit `6de0711` selected `QLocalServer`/`QLocalSocket` for the shared resource +protocol. Its Linux abstract socket name contains the process ID and an opening +counter; it does not expose the authentication secret. Existing authentication, +limits, cancellation and drain ordering are retained. The socket disappears +with the process and needs neither network configuration nor elevated rights. +Other targets retain their loopback TCP transport. See Qt's +[local server options](https://doc.qt.io/qt-6.8/qlocalserver.html#SocketOption-enum) +and [local socket options](https://doc.qt.io/qt-6.8/qlocalsocket.html#SocketOption-enum). + +The same isolated-network ARM workflow passed after the change (8.88 seconds). +Two existing device EPUBs also passed opening, page turn, close and restored +position under the exact firmware runtime (13.89 and 23.55 seconds). Books and +captures remain private local test inputs under `build/kobalt/epub-open-debug/`. +The new executable's 13 direct libraries and 1,678 strong imports match the +firmware. These checks do not establish physical display/input acceptance. + +The full host regression passed 72 of 73 targets. `reader-window` failed its +existing `closeWithPendingBookmark` timing assertion, including an isolated +rerun (97 cases passed, one failed, two skipped). The same assertion also fails +in the saved baseline build without this transport change. This remains a +known test limitation; the suite is not reported as fully passing. + +The user subsequently confirmed that this installed version opened an EPUB on +the InkPad. Its package, `Kobalt-PB743K3-local-socket.zip`, has SHA-256 +`22b7e813c0288501eb30bdbc44ebc1265a57d084acd9747678dcbd549875735c`. + +## Upstream synchronization + +Upstream `f2268b52c125cf65646f7b93f250d00901378324` adds 66 commits since the +fork point, including direct local EPUB resource access. The shared controller +now uses `openLocalPublication`, which inspects the archive and reads bounded +ZIP streams on the engine worker. The PocketBook-only local socket adapter is +therefore removed. The source lease, resource limits, cancellation and drain +ordering are supplied by the upstream implementation. + +The merge retains PocketBook launch, InkView integration, glyph alignment and +Qt 6.8 compatibility changes. Integration adjustments cover the standalone +font library, sensor-free orientation and dependency caching with system PNG +and explicit cross-compiler configuration. The installed, device-confirmed +`6de0711` package remains separate from this synchronized build. + +Host and isolated-network ARM workflows pass with the fixture and both device +books. The workflow now waits for `ReaderWindow::readyForPresentation()` rather +than a fixed 300 ms delay: with the larger book, the old test captured the +temporary "Updating page layout" overlay as its expected page. Saved before/ +after-turn/reopened images established that race; the corrected comparison +still requires a changed page after PageDown and identical content on reopen. + +For reference, complete ARM workflow times were 8.88/8.71 seconds for the +fixture, 13.89/15.04 seconds for the first device book and 23.55/24.11 seconds +for the second, before/after synchronization. These use the same EPUBs, fresh +profiles, Release ARM softfp configuration, firmware Qt/libc, QEMU offscreen +display and isolated network namespace. They include two openings, a page turn, +shutdown, image comparisons and failure recovery. The readiness change and +varying host build load prevent attributing the differences to ZIP access or +claiming a speedup. Peak memory and native paint/refresh counts were not measured; +these runs do not establish physical-device performance. + +The synchronized executable passes firmware checks with 13 direct libraries +and 1,653 strong imports. The 117-file package includes upstream's ten additional +hyphenation patterns and is retained locally as +`Kobalt-PB743K3-upstream-f2268b5.zip` (SHA-256 +`8fbdc5496e72d731a38f0baf04dec2f3d0be6e82487c8b1a9cd6286aff9474d4`). +It was subsequently installed on the connected reader after the history rebase +described below. All 117 package files match the archive; hashes of Kobalt user +state and native reader associations remain unchanged. Replaced files and logs +are backed up locally under `build/kobalt/epub-open-debug/before-upstream/`. + +The synchronized full host suite passes all 107 test groups in 258.56 seconds, +including the reader-window groups. All six package/registration tests, the UI +architecture check and the 379-file C++ formatting check pass. Follow-up +Standards and Spec reviews report no source-level findings. Physical acceptance +of this synchronized build remains outstanding. + +## Linear port history + +At the user's request, the port was rebased onto upstream `f2268b5` as six +linear commits, ending at `1a17e2f`. There are no merge commits in the port delta. +The obsolete socket workaround is omitted; the upstream integration adjustments +remain in the final commit. Both `1a17e2f` and the previously tested `6ecab6a` +have tree `3aa5ea8079d53b4146f0f7e3d047406e046cdb29`, so source and packaged binary +are unchanged by the history rewrite. The 107-test result applies to this exact +tree; no additional full test run was needed for the rebase. + +Non-release archive tags preserve both histories on the fork: +`archive/pocketbook-pre-rebase-20261015` and +`archive/pocketbook-device-confirmed-20261015`. The published port branch was +updated with a lease pinned to its previous exact commit. Future upstream +updates should use rebases to keep the port suitable for upstream review. + +## Standalone fork buildchain + +Fork commit `c1e884e` now owns the PocketBook SDK recipe, firmware preparation, ABI/runtime +checks, packaging, registration and Python tests. Its root `build-pocketbook.sh` +uses the existing Kobalt container-engine, dependency, font and provenance +helpers. The parent repository only pins the fork and links its instructions. +Duplicate parent helpers were removed. Existing monorepo CMake outputs must be +moved aside once because the container source mount changed to `/work`. + +Validation used an independent clone with no compiled outputs, mounting only +the fork checkout and the separately supplied, checksum-verified firmware file. +The SDK image was built from the fork's recipe. Pinned dependency archives and +font inputs were reused, then ARM and host dependencies were compiled afresh. +The full standalone command passed, including all ten Python tooling checks, +the host EPUB workflow and the exact-firmware ARM workflow with no IP network. +The final ARM workflow took 6.259 seconds; this is not device startup timing. +All 13 direct libraries and 1,653 strong imports match the firmware. The full +host regression suite passed all 108 groups in 213.24 seconds. + +The normal command also passed from clean commit `c1e884e`, without +`--allow-dirty`; its provenance record reports `dirty: false`. The 117-file ZIP +has SHA-256 `06c582119d9d5a151c29f5400b7343dedb93dc1a634a426f3317264a632a1e13`. +The generated ZIP remains 31 MiB and excludes firmware and user data. A source +change during the build fails validation before replacing a prior verified ZIP; +the regression was checked against the faulty early-publication behavior. +Firmware inputs are reused only after checksum verification; a corruption test +checks re-extraction. Standards and Spec reviews have no open findings. + +This validates Docker on an ARM64 host. The pinned SDK image also supplies +AMD64, but AMD64 and Podman builds have not been exercised. Kobo build entry +points were unchanged; no full Kobo cross-build was run. This tooling change +does not install a new device package or change the pending physical acceptance +status below. See the fork's `pocketbook/README.md` for commands and outputs. + +## Remaining validation + +- Validate the synchronized build on the device; its imports and emulated + workflows pass, while the preceding `6de0711` build has confirmed EPUB opening. +- Touch and page keys, local position persistence, + menus, color refresh, Home/exit, USB handling and suspend/wake remain runtime + acceptance work. +- Native library registration and task return need physical acceptance. + PocketBook-native progress/annotation interoperability, frontlight, dictionary + integration and Readest synchronization remain separate work. diff --git a/docs/research/kobalt-pocketbook-port.md b/docs/research/kobalt-pocketbook-port.md new file mode 100644 index 0000000..8be3a7c --- /dev/null +++ b/docs/research/kobalt-pocketbook-port.md @@ -0,0 +1,130 @@ +# Kobalt on PocketBook: source feasibility + +Date: 2026-10-13. Reviewed source: +[`cbc0f4e9118c0e791056e36bc0b66d90e2af027c`](https://github.com/42lizard/kobalt/tree/cbc0f4e9118c0e791056e36bc0b66d90e2af027c). +This is source research, not evidence of a working PocketBook port. Build and +device results must be recorded separately. The initial target is the current +Qt 6 InkPad 4 environment; older PocketBook compatibility is not implied. + +## Assessment + +A standalone PocketBook port looks feasible without replacing the reader UI or +CREngine. The strongest evidence is the existing desktop host: it already uses +the shared Qt Widgets reader and Qt-only Android controller without Nickel or +the Android runtime. Shared controls support both Kobo Qt generations. This is +an engineering inference from the existing +[source boundaries](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/docs/development.md#shared-reader-controls) +and [desktop target](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/desktop/CMakeLists.txt). +Linux alone does not establish binary, firmware or display compatibility. + +The Kobo installation package is the wrong starting point. Its services depend +on Nickel keyboard, light, battery, lifecycle and presentation adapters. Its +refresh implementation uses Kobo Qt attributes, native symbol lookup and +backing-store flush hooks. Those mechanisms need a PocketBook counterpart. +[Kobo host](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/platform/nickel/common/reader_host_services.cpp), +[Kobo display](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/platform/nickel/qt5/refresh.cpp). + +## Reuse and adaptation boundaries + +- **Reader and engine:** preserve publication loading, rendering, shared Widgets + and reading state. The standalone controller already opens plain EPUB archives + and constructs the shared reader window. + [Controller](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/platform/android/reader.cpp). +- **Host services:** implement the existing callbacks for clock, battery, page + buttons, fonts, suspend state, frontlight, dictionary, keyboard, presentation + and browser. Unsupported services still need valid callbacks/factories; + `complete()` checks their presence. Desktop provides examples. + [Interface](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/platform/api/reader_host_services.h), + [desktop services](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/platform/desktop/services.cpp). +- **Display:** supply `textRefresh`, `pageRefresh`, and + `setReaderRefreshWindow`. Desktop's implementations are no-ops; host rendering + therefore cannot establish actual e-ink refresh quality. Device testing must + check page turns, menu cleanup and repaint completion. + [Refresh API](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/platform/api/kobo_refresh.h), + [desktop adapter](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/platform/desktop/display.cpp). +- **Input:** the existing canvas converts touch to mouse events for embedded + Widgets; host services map page keys. Verify PocketBook's delivered events + before choosing a direct full-screen QWidget or reusing the canvas, avoiding + duplicate translation. The canvas uses Qt 6 touch APIs and is not a drop-in + Qt 5 host. + [Canvas](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/platform/android/canvas.h). +- **Rotation:** the standalone controller passes a null sensor and host rotation + callback. Automatic rotation is optional at runtime, but the shared + `ReaderOrientation` still includes/calls Qt Sensors and inherits + `QOrientationFilter`. Simply omitting the Sensors link is insufficient. If + the SDK lacks it, make sensor support optional in this narrow module. + [Header](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/platform/api/reader_orientation.h), + [implementation](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/platform/api/reader_orientation.cpp). +- **Local progress:** keep independent Kobalt state initially. `ReadingBook` + supports a null native bridge, saves local checkpoints, and skips identical + checkpoint writes. Hosts choose directories. Use a dedicated document cache: + CREngine deletes unrelated files found in that directory. + [Book state](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/reading/reading_book.cpp), + [storage paths](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/storage/reader_storage.h). +- **Native progress and Readest:** `ReadingBridge` models a platform database + and activity recorder, while `PositionCodec` maps engine/platform positions. + These are extension points, not proof of compatible position formats. A + local-only port will not automatically participate in Readest Sync. + [Bridge](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/reading/reading_bridge.h), + [codec](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/platform/api/position_codec.h). +- **Launch and assets:** adapt desktop bundle paths to PocketBook directories. + Desktop initializes bundled fonts/assets and accepts a book path as its first + argument. Reuse these responsibilities; a new library UI is unnecessary for + the first experiment. + [Desktop entry point](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/src/platform/desktop/main.cpp). + +## Proposed minimal stages + +1. Pin the user's fork as a submodule. Keep Kobalt platform changes in that fork + and PocketBook build/package integration in this repository. Preserve shared + renderer/UI sources, dependency pins, patches and notices. +2. Cross-build a standalone target around the existing controller and a small + PocketBook host. Start with one explicit DRM-free EPUB path, independent + state/cache directories, no native database bridge and no page animations. +3. Validate shared reader behavior on the host, then on InkPad 4: visible page, + touch/page keys, menus, checkpoint/reopen, Home/exit, suspend/wake and USB + lifecycle. Hardware must establish refresh quality, memory use and power + behavior. +4. Add device services incrementally. Library/default-reader registration, + native positions and Readest sync are later capabilities with separate tests. + +These are proposed stages, not completed implementation. Upstream also +explicitly distinguishes portable tests from firmware integration checks in its +[test documentation](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/docs/development.md#tests). + +## Initial local evidence + +The fork is pinned, unmodified, under `external/kobalt` on the separate +`experiment/kobalt-pocketbook` branch. The reproducible +[compatibility probe](../../tools/kobalt-probe/README.md) links Kobalt's publication +component, Qt Widgets and InkView with the existing PocketBook toolchain. + +On 2026-10-13 the ARM build and existing ABI check passed: ARM softfp, +`/lib/ld-linux.so.3`, no runtime search path, and the existing firmware +symbol-version ceilings. This establishes compilation/linkage of that component, +not runtime compatibility of the full reader. No binary was installed on a device. + +The host CTest also passed: an EPUB from Kobalt's fixture generator was inspected, +compressed chapter text read, invalid ZIP input rejected and a Qt Widgets window +painted. CREngine, Kobalt's reader controls and hardware behavior are outside +this probe's coverage. + +The current SDK contains Qt Widgets and Network 6.8.2, but no Qt Sensors module. +The retained `build/readest-sync/firmware-6.11.1683/` file inventories list +`libQt6Widgets.so.6.10.3` and `libQt6Network.so.6.10.3`. These local observations +support using the native Qt runtime for this target, not all PocketBook models. +The existing SDK deliberately declares imported Qt targets instead of using +Debian's normal Qt package configuration, which requires OpenGL. Kobalt's +standalone CMake wiring therefore also needs an adaptation; changing the target +name or the compiler alone is insufficient. + +## Distribution inventory + +Upstream labels its own source MIT, CREngine patches GPL v2 or later, and Qt +patches under Qt's licenses. It describes the combined package as AGPL v3 and +supplies corresponding source/dependency sources with releases. Preserve this +inventory instead of treating the whole executable as MIT-only. This records +upstream's stated distribution model, not a separate legal assessment. +[License statement](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/README.md#license), +[credits](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/ACKNOWLEDGEMENTS.md), +[source-bundle procedure](https://github.com/42lizard/kobalt/blob/cbc0f4e9118c0e791056e36bc0b66d90e2af027c/docs/development.md#ci-and-tagged-releases). diff --git a/external/kobalt b/external/kobalt new file mode 160000 index 0000000..c1e884e --- /dev/null +++ b/external/kobalt @@ -0,0 +1 @@ +Subproject commit c1e884e60293491986783a68fc8d62ee1f6eb012 diff --git a/licenses/README.md b/licenses/README.md index 171e267..46343f5 100644 --- a/licenses/README.md +++ b/licenses/README.md @@ -35,6 +35,14 @@ not copies of the firmware controls. ## Notice provenance and scope +The optional `external/kobalt` submodule retains Nico Verbruggen's +[Kobalt license](../external/kobalt/LICENSE), +[acknowledgements](../external/kobalt/ACKNOWLEDGEMENTS.md) and dependency notices. +The compatibility probe compiles its publication component and links Qt Widgets; +it does not bundle CREngine. A complete reader port must preserve the separate +license/source inventory described in Kobalt's README; the root MIT license does +not relicense Kobalt's combined reader package. + The `*-copyright.txt` files are unmodified Debian package notices retrieved from the project's existing Qt build image on 2026-09-14. Standard license texts are copied from that image's `/usr/share/common-licenses`; references to that directory diff --git a/tools/kobalt-probe/CMakeLists.txt b/tools/kobalt-probe/CMakeLists.txt new file mode 100644 index 0000000..26cc76c --- /dev/null +++ b/tools/kobalt-probe/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.21) +project(kobalt-probe LANGUAGES CXX) +option(KOBALT_PROBE_HOST "Run the compatibility probe on the build host" OFF) +set(KOBALT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../external/kobalt") +if(NOT EXISTS "${KOBALT_ROOT}/cmake/ReaderComponents.cmake") + message(FATAL_ERROR "Run git submodule update --init external/kobalt first") +endif() +set(KOBALT_QT_MAJOR 6) +if(KOBALT_PROBE_HOST) + find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + add_executable(kobalt-probe main.cpp) + target_compile_definitions(kobalt-probe PRIVATE KOBALT_PROBE_HOST) +else() + include(../../mk/pocketbook-qt6.cmake) + pocketbook_add_app(kobalt-probe SOURCES main.cpp) +endif() +include("${KOBALT_ROOT}/cmake/Strings.cmake") +include("${KOBALT_ROOT}/cmake/ReaderComponents.cmake") +kobalt_publication_library(kobalt-publication) +target_compile_options(kobalt-publication PRIVATE "-iquote${KOBALT_ROOT}/src") +target_include_directories(kobalt-probe PRIVATE "${KOBALT_ROOT}/src") +target_link_libraries(kobalt-probe PRIVATE kobalt-publication Qt6::Widgets Qt6::Gui Qt6::Core) +if(KOBALT_PROBE_HOST) + enable_testing() + execute_process(COMMAND "${Python3_EXECUTABLE}" -c + "import runpy,sys; runpy.run_path(sys.argv[1])['book'](sys.argv[2], chapter_count=1, paragraphs=1)" + "${KOBALT_ROOT}/tests/fixtures/reader_fixture.py" "${CMAKE_CURRENT_BINARY_DIR}/probe.epub" + COMMAND_ERROR_IS_FATAL ANY) + add_test(NAME kobalt-publication-widgets COMMAND kobalt-probe --self-test "${CMAKE_CURRENT_BINARY_DIR}/probe.epub") + set_tests_properties(kobalt-publication-widgets PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen") +endif() diff --git a/tools/kobalt-probe/README.md b/tools/kobalt-probe/README.md new file mode 100644 index 0000000..87b0fcf --- /dev/null +++ b/tools/kobalt-probe/README.md @@ -0,0 +1,56 @@ +# Kobalt PocketBook compatibility probe + +This is an isolated port experiment, **not a working Kobalt reader**. It compiles +the fork's unchanged publication/archive component with Qt Widgets and the +existing PocketBook startup adapter. It does not include CREngine, Kobalt's +reader UI, native reading-state integration or a device installer. + +The source is pinned by the `external/kobalt` submodule to the user's +[fork](https://github.com/42lizard/kobalt). Original project: +[Nico Verbruggen's Kobalt](https://github.com/nicoverbruggen/kobalt). +Do not run Kobalt's Kobo installer on PocketBook. + +## Reproduce the checks + +From the repository root: + +```sh +git submodule update --init external/kobalt + +# Cross-compile for the current InkPad 4 target and check the ARM ABI. +docker compose run --rm qt6 sh -ec ' + cmake -S tools/kobalt-probe -B build/kobalt/probe-arm -DCMAKE_BUILD_TYPE=Release + cmake --build build/kobalt/probe-arm -j2 + python3 tools/check_qt6_app.py build/kobalt/probe-arm/kobalt-probe.app +' + +# Run the same publication component and a Widgets paint check on the host. +docker compose run --rm qt6 sh -ec ' + unset CMAKE_TOOLCHAIN_FILE + cmake -S tools/kobalt-probe -B build/kobalt/probe-host -DKOBALT_PROBE_HOST=ON -DCMAKE_BUILD_TYPE=Release + cmake --build build/kobalt/probe-host -j2 + ctest --test-dir build/kobalt/probe-host --output-on-failure +' +``` + +The host test generates an EPUB with Kobalt's own fixture generator, inspects +its archive, reads compressed chapter text, rejects invalid ZIP input and grabs +a painted Widgets window. It does not test EPUB layout or actual e-ink output. + +The ARM binary is `build/kobalt/probe-arm/kobalt-probe.app`. If launched manually, +it shows a clearly labelled probe screen and Close button. An optional first +argument supplies a read-only EPUB archive to inspect. No native databases or +book files are written. The probe is excluded from normal app builds/releases. + +## Next port milestone + +Reuse the shared reader/CREngine and standalone controller through a small +PocketBook host. Build the pinned engine dependencies with the PocketBook +toolchain, make Qt Sensors optional, adapt startup/assets/state paths and connect +the existing display/host-service interfaces. Start with one explicit EPUB path +and local Kobalt progress, then validate page turns, touch, exit and suspend on +the device. Native library registration and Readest integration are separate work. + +See the [source research and evidence](../../docs/research/kobalt-pocketbook-port.md). +All upstream notices remain in the submodule; a complete reader package has +additional dependency/source-distribution requirements described there. diff --git a/tools/kobalt-probe/main.cpp b/tools/kobalt-probe/main.cpp new file mode 100644 index 0000000..fcf20ba --- /dev/null +++ b/tools/kobalt-probe/main.cpp @@ -0,0 +1,70 @@ +#include "publication/publication.h" +#include "publication/zip_resource.h" +#include +#include +#include +#include +#include +#include +#ifndef KOBALT_PROBE_HOST +#include +#endif + +// A build/input prerequisite check, not the Kobalt reader or a device installer. +int main(int argc, char **argv) { +#ifndef KOBALT_PROBE_HOST + qputenv("QT_PLUGIN_PATH", "/ebrmain/plugins"); + qputenv("QT_QPA_PLATFORM", "pocketbook2"); + QCoreApplication::setSetuidAllowed(true); + InitInkview(TASK_MAKEACTIVE); +#endif + QApplication app(argc, argv); + app.setStyle("Fusion"); + QWidget window; + QVBoxLayout layout(&window); + QLabel message("Kobalt compatibility probe\nQt Widgets + unchanged publication code\nThis is not the EPUB reader."); + message.setWordWrap(true); + layout.addWidget(&message); + QPushButton close("Close"); + layout.addWidget(&close); + QObject::connect(&close, &QPushButton::clicked, &app, &QApplication::quit); + + if (app.arguments().contains("--self-test")) { + QFile book(app.arguments().value(2)); + QString error; + if (!book.open(QIODevice::ReadOnly)) return 1; + kobalt::Publication reader; + if (!reader.inspect(book, &error) || !reader.names().contains("c1.xhtml") || reader.protectedBook()) { + qCritical("Publication inspection failed: %s", qPrintable(error)); + return 2; + } + kobalt::ZipResource chapter; + if (!chapter.open(book, "c1.xhtml") || !chapter.next().contains("A reader keeps")) return 5; + kobalt::Publication invalid; + if (invalid.inspect(QByteArray("not a ZIP"), &error)) return 3; + window.resize(600, 800); + window.show(); + app.processEvents(); + return window.grab().isNull() ? 4 : 0; + } + if (app.arguments().size() > 1) { + QFile book(app.arguments().at(1)); + kobalt::Publication publication; + QString error; + if (!book.open(QIODevice::ReadOnly)) { + message.setText(book.errorString()); + } else if (!publication.inspect(book, &error)) { + message.setText(error); + } else { + message.setText(QString("Archive inspected: %1 entries\nProtected: %2\nNo rendering or reading-state changes.") + .arg(publication.names().size()).arg(publication.protectedBook() ? "yes" : "no")); + } + } +#ifdef KOBALT_PROBE_HOST + window.resize(600, 800); +#else + window.setGeometry(0, 0, ScreenWidth(), ScreenHeight() - PanelHeight()); +#endif + window.show(); + return app.exec(); +}