Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
96a3b1b
Add binary parity gate
MRoiban Jul 16, 2026
9656443
Split plugin into ordered source fragments
MRoiban Jul 16, 2026
3611958
Add on-device README previews
MRoiban Jul 16, 2026
15bd61d
Move firmware ABI declarations to a header
MRoiban Jul 16, 2026
77c3b87
Consolidate resolved firmware bindings
MRoiban Jul 16, 2026
fdd8fa5
Own mutable plugin state explicitly
MRoiban Jul 16, 2026
e7be625
Publish firmware callbacks before hook install
MRoiban Jul 16, 2026
86d5ff5
Extract filesystem utilities into a translation unit
MRoiban Jul 16, 2026
51f90f2
Extract settings storage into a translation unit
MRoiban Jul 16, 2026
2103fcc
Audit globals in promoted translation units
MRoiban Jul 16, 2026
507b045
Extract template loading into a translation unit
MRoiban Jul 16, 2026
1a08a66
Extract cover loading into a translation unit
MRoiban Jul 16, 2026
3b1441b
Share firmware ABI declarations across translation units
MRoiban Jul 16, 2026
3790a2c
Separate firmware resolution from cover caching
MRoiban Jul 16, 2026
9d5abd6
Extract cover caching into a translation unit
MRoiban Jul 16, 2026
2a795bd
Extract notebook backup I/O into a translation unit
MRoiban Jul 16, 2026
98e50bf
Extract verified notebook widget accessors
MRoiban Jul 16, 2026
fc72131
Extract active page operations into a translation unit
MRoiban Jul 16, 2026
ad1c6f0
Extract legacy backup visibility cleanup
MRoiban Jul 16, 2026
df5e8c5
Extract custom asset visibility policy
MRoiban Jul 16, 2026
ff60fbe
Extract notebook cover editing workflow
MRoiban Jul 16, 2026
2a942e8
Share notebook widget lookup helpers
MRoiban Jul 16, 2026
045448c
Extract active page action dispatch
MRoiban Jul 16, 2026
aee8da9
Delegate notebook cover hook behavior
MRoiban Jul 16, 2026
b9c2c00
Extract notebook menu contributions
MRoiban Jul 16, 2026
a14bf3a
Move visibility hook state into its service
MRoiban Jul 16, 2026
b6d8751
Extract eraser menu hook installation
MRoiban Jul 16, 2026
36ecc07
Delegate eraser menu hook behavior
MRoiban Jul 16, 2026
410408e
Share layer types across translation units
MRoiban Jul 16, 2026
00a72e3
Extract layer state persistence
MRoiban Jul 16, 2026
9721922
Extract active layer routing service
MRoiban Jul 16, 2026
b1ac8eb
Extract layer preview workflow
MRoiban Jul 16, 2026
bb53070
Extract layer eraser routing
MRoiban Jul 16, 2026
3f6300f
Extract native layer menu workflow
MRoiban Jul 16, 2026
94e36cf
Meet umbrella globals budget
MRoiban Jul 16, 2026
a6aa701
Ignore AppleDouble layer preview metadata
MRoiban Jul 16, 2026
e093736
Preserve eraser state across tool switches
MRoiban Jul 16, 2026
97b00b7
Finish plugin source modularization
MRoiban Jul 16, 2026
c64bbeb
Add release verification and NickelTC CI
MRoiban Jul 16, 2026
9d97845
Resolve preview objdump from PATH
MRoiban Jul 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 82 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
name: Source checks
name: Verification and NickelTC build

on:
push:
pull_request:
workflow_dispatch:

permissions:
contents: read

env:
NICKELTC_IMAGE: ghcr.io/pgaskin/nickeltc:1
ABI_FIXTURES_URL: https://github.com/MRoiban/KoboNotebookPlus/releases/download/v0.0.1-alpha/KoboNotebookPlus-fw4.38.23697-abi-fixtures.tar.gz
ABI_FIXTURES_SHA256: 2e5fe02b15a947d490c2be29d2bda9598a2413eb2369c281b2e1dd9846abb9db
BASELINE_URL: https://github.com/MRoiban/KoboNotebookPlus/releases/download/v0.0.1-alpha/KoboNotebookPlus-v0.0.1-alpha-condor-fw4.38.23697-plugin.zip
BASELINE_SHA256: 8707bf7c3c3d8c3a06af351f694701f196b1151ffb6974368aec8210aaa54f2b

jobs:
python:
source-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -19,5 +27,75 @@ jobs:
python-version: "3.12"
- name: Compile verification scripts
run: python -m py_compile scripts/*.py
- name: Run artifact-verifier tests
run: python scripts/test-verify-layer-artifacts.py
- name: Run host verification suite
run: make -C mods/custom-notebook-templates check

firmware-build-package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install ARM inspection tools
run: |
sudo apt-get update
sudo apt-get install --yes binutils-arm-linux-gnueabihf
mkdir -p "$RUNNER_TEMP/cross-bin"
ln -s "$(command -v arm-linux-gnueabihf-nm)" "$RUNNER_TEMP/cross-bin/nm"
ln -s "$(command -v arm-linux-gnueabihf-objdump)" "$RUNNER_TEMP/cross-bin/objdump"
echo "$RUNNER_TEMP/cross-bin" >> "$GITHUB_PATH"
- name: Fetch checksum-pinned firmware ABI fixtures and baseline
run: |
mkdir -p extracted/rootfs/usr/local/Kobo ci/baseline
curl --fail --location --retry 3 "$ABI_FIXTURES_URL" -o "$RUNNER_TEMP/abi-fixtures.tar.gz"
echo "$ABI_FIXTURES_SHA256 $RUNNER_TEMP/abi-fixtures.tar.gz" | sha256sum --check --strict
tar -xzf "$RUNNER_TEMP/abi-fixtures.tar.gz" -C extracted/rootfs/usr/local/Kobo
curl --fail --location --retry 3 "$BASELINE_URL" -o "$RUNNER_TEMP/baseline.zip"
echo "$BASELINE_SHA256 $RUNNER_TEMP/baseline.zip" | sha256sum --check --strict
unzip -q "$RUNNER_TEMP/baseline.zip" -d ci/baseline
mkdir -p mods/custom-notebook-templates/package
cp extracted/rootfs/usr/local/Kobo/libiinknote.so mods/custom-notebook-templates/package/libiinknote.so
echo "f80a7de7a1c482173a89b18f2bb8164fcfb53b8fab9b2a75bd23998813a528ea mods/custom-notebook-templates/package/libiinknote.so" | sha256sum --check --strict
- name: Verify firmware ABI pins
run: python scripts/verify-layer-abi.py
- name: Clean NickelTC build
run: docker run --rm -v "$PWD:/work" -w /work "$NICKELTC_IMAGE" make -C mods/custom-notebook-templates clean
- name: Compile with NickelTC
run: docker run --rm -v "$PWD:/work" -w /work "$NICKELTC_IMAGE" make -C mods/custom-notebook-templates -j2 VERSION="ci-${GITHUB_SHA::12}"
- name: Package with NickelTC
run: docker run --rm -v "$PWD:/work" -w /work "$NICKELTC_IMAGE" make -C mods/custom-notebook-templates koboroot VERSION="ci-${GITHUB_SHA::12}"
- name: Verify compiled preview ABI
run: >-
python scripts/verify-layer-preview-abi.py
mods/custom-notebook-templates/libcustomnotebooktemplates.so
--objdump arm-linux-gnueabihf-objdump
- name: Verify loader-visible binary parity
run: >-
python scripts/verify-binary-parity.py
ci/baseline/libcustomnotebooktemplates.so
mods/custom-notebook-templates/libcustomnotebooktemplates.so
--tool-prefix arm-linux-gnueabihf-
- name: Validate installer contents and binary identity
run: >-
python scripts/verify-release-package.py
mods/custom-notebook-templates/KoboRoot.tgz
mods/custom-notebook-templates/libcustomnotebooktemplates.so
- name: Record build checksums
run: |
sha256sum \
mods/custom-notebook-templates/KoboRoot.tgz \
mods/custom-notebook-templates/libcustomnotebooktemplates.so \
mods/custom-notebook-templates/package/libiinknote.so \
| tee SHA256SUMS
- uses: actions/upload-artifact@v4
with:
name: KoboNotebookPlus-${{ github.sha }}
path: |
mods/custom-notebook-templates/KoboRoot.tgz
mods/custom-notebook-templates/libcustomnotebooktemplates.so
SHA256SUMS
if-no-files-found: error
retention-days: 14
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,40 @@

All notable changes to KoboNotebookPlus will be documented in this file.

## [0.0.2-alpha] - 2026-07-17

### Fixed

- Preserve the configured eraser width whenever Nickel switches between pen,
object eraser, brush eraser, or the stylus hardware eraser.
- Reapply the eraser policy after firmware tool activation, so the highlighted
object/brush mode and the engine's actual erasing behavior stay synchronized.
- Coalesce a guarded zero-delay replay after tool activation to cover cached
erasers which Nickel restyles or replaces later in the same event cycle.
- Verify that eraser state replay leaves the selected notebook layer unchanged.

### Changed

- Replace the 8,000-line umbrella translation unit and all `.cc.inc` fragments
with explicit firmware-resolution, runtime, hook, entry-point, visibility,
menu, page, eraser, and layer translation units.
- Centralize firmware addresses in an immutable pin inventory and keep only two
audited process globals (`gPluginState` and NickelHook's `info`).
- Add `make check` for artifact, binary-parity, globals-budget, and release-
package verifier tests.
- Add GitHub CI which verifies firmware ABI pins, performs a clean NickelTC
build, checks preview ABI and loader-visible parity, validates the exact
two-file installer, and records SHA-256 checksums.

### Verification

- Tested on Kobo Elipsa 2E (`condor`) firmware `4.38.23697`, including pen and
hardware eraser switching, both eraser policies and widths, layer selection,
isolated erasing, previews, covers, templates, page tools, and library asset
filtering.
- The release installer remains firmware-specific and contains only the ARM
plugin plus the checksum-pinned stock `libiinknote.so`.

## [0.0.1-alpha] - 2026-07-16

### Added
Expand Down
73 changes: 58 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,48 @@ Elipsa 2E. Instead of overlaying a separate app, it hooks the native notebook
UI and the MyScript ink engine directly, so layers, previews, and erasing all
behave like built-in features.

<!-- TODO: add 1–2 photos here — the Layers popup on-device and a custom
template/cover. A single picture will sell this better than the list
below. -->
## On-device previews

Click any preview to view it at full size.

<table>
<tr>
<td align="center">
<a href="docs/images/previews/layers.jpg">
<img src="docs/images/previews/layers.jpg" width="420" alt="Native Layers popup on a Kobo Elipsa 2E">
</a><br>
<strong>Native layers</strong>
</td>
<td align="center">
<a href="docs/images/previews/eraser-controls.jpg">
<img src="docs/images/previews/eraser-controls.jpg" width="420" alt="Brush and object eraser controls with five eraser sizes">
</a><br>
<strong>Eraser controls</strong>
</td>
</tr>
<tr>
<td align="center">
<a href="docs/images/previews/cover-picker.jpg">
<img src="docs/images/previews/cover-picker.jpg" width="420" alt="Custom notebook cover picker on a Kobo Elipsa 2E">
</a><br>
<strong>Custom covers</strong>
</td>
<td align="center">
<a href="docs/images/previews/notebook-menu.jpg">
<img src="docs/images/previews/notebook-menu.jpg" width="420" alt="Notebook menu with layers and page operations">
</a><br>
<strong>Page and notebook tools</strong>
</td>
</tr>
<tr>
<td align="center" colspan="2">
<a href="docs/images/previews/notebook-library.jpg">
<img src="docs/images/previews/notebook-library.jpg" width="420" alt="Kobo notebook library showing a custom checkerboard cover">
</a><br>
<strong>Custom covers in the notebook library</strong>
</td>
</tr>
</table>

> [!CAUTION]
> This plugin calls private Kobo and MyScript C++ APIs **by firmware
Expand All @@ -37,9 +76,10 @@ behave like built-in features.

## Status

Experimental, source-only — **no stable binary release yet**. The current
build has passed its host-side ABI, relocation, packaging, and ARM
disassembly checks; final on-device runtime verification is still pending.
Experimental alpha. The current release has passed host-side ABI, relocation,
packaging, ARM disassembly, and on-device feature verification. It is still
firmware-specific software built on private APIs, not a stable or portable
Kobo extension.

## Compatibility

Expand All @@ -55,13 +95,14 @@ used by the current build is pinned by the verification scripts in
`scripts/`. Supporting a different firmware requires a fresh binary audit —
changing only the version check is unsafe.

## Why source-only
## Release package and source

This repository intentionally contains no Kobo firmware, MyScript libraries,
Binary Ninja databases, device backups, notebooks, generated plugins, or
`KoboRoot.tgz`. The install archive embeds a stock `libiinknote.so`, which
you must extract from firmware you obtained yourself; it must not be
redistributed here.
The source repository contains no firmware images, Binary Ninja databases,
device backups, notebooks, or generated build products. Firmware-pinned alpha
releases provide a checksum file and a complete `KoboRoot.tgz` containing only
the ARM plugin and the exact stock `libiinknote.so` required by the supported
firmware. Source builders can instead supply their own matching firmware
libraries using the process below.

## Building

Expand Down Expand Up @@ -115,9 +156,11 @@ docker run --rm -v "$PWD:/work" -w /work \

## Installing

Before installing, inspect the built `KoboRoot.tgz` and confirm it contains
only the compiled ARM plugin and the exact stock library — nothing else.
Never install a package built for a different firmware.
Download `KoboRoot.tgz` and its adjacent `.sha256` file from the matching
[GitHub release](https://github.com/MRoiban/KoboNotebookPlus/releases). Verify
the checksum before installing. Never install a package built for a different
device or firmware. Source builders should also inspect the archive and confirm
it contains only the compiled ARM plugin and the exact stock library.

1. Back up `/mnt/onboard/.kobo/KoboReader.sqlite`, `Kobo eReader.conf`, and
your notebooks.
Expand Down
Binary file added docs/images/previews/cover-picker.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/previews/eraser-controls.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/previews/layers.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/previews/notebook-library.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/previews/notebook-menu.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 18 additions & 1 deletion mods/custom-notebook-templates/Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
NICKELHOOK ?= ../../third_party/NickelHook/

ifneq ($(filter check,$(MAKECMDGOALS)),check)
include $(NICKELHOOK)/NickelHook.mk
endif

override LIBRARY := libcustomnotebooktemplates.so
override SOURCES += src/customnotebooktemplates.cc
override SOURCES += src/firmware_resolver.cc src/fs_util.cc src/settings.cc \
src/templates.cc src/covers.cc src/cover_cache.cc src/cover_editor.cc src/page_io.cc \
src/notebook_widget.cc src/notebook_hook_services.cc src/notebook_menu.cc src/pages.cc \
src/page_actions.cc src/visibility.cc src/eraser_menu.cc src/layers_state.cc \
src/layers_service.cc src/layers_preview.cc src/layers_eraser.cc src/layers_menu.cc \
src/plugin_runtime.cc src/plugin_entry.cc src/hooks.cc src/visibility_hooks.cc
override MOCS += src/covermenureceiver.h
override PKGCONF += Qt5Widgets
override CXXFLAGS += -Wall -Wextra -Werror -Wno-missing-field-initializers \
Expand All @@ -14,4 +21,14 @@ override CXXFLAGS += -Wall -Wextra -Werror -Wno-missing-field-initializers \
# and manifest file untouched.
override KOBOROOT += package/libiinknote.so:/usr/local/Kobo/libiinknote.so

.PHONY: check
check:
python3 ../../scripts/test-verify-layer-artifacts.py
python3 ../../scripts/test-verify-binary-parity.py
python3 ../../scripts/test-verify-globals-budget.py
python3 ../../scripts/test-verify-release-package.py
python3 ../../scripts/verify-globals-budget.py

ifneq ($(filter check,$(MAKECMDGOALS)),check)
include $(NICKELHOOK)/NickelHook.mk
endif
Loading
Loading