Files
chatter/doc/Chatter_technical_reference.md

343 lines
18 KiB
Markdown
Raw Normal View History

# Chatter — Technical Reference (master document)
**Audience:** a programmer who needs to understand, build, modify, or maintain
Chatter — including how the reMarkable standard software works and how Chatter
fits alongside it.
This is the **index + complete architecture**. Several topics have their own
deep-dive documents; this file summarizes each and links to it, then fills in
everything not covered elsewhere. Read this first; follow the links for detail.
## Document map
| Document | What it covers |
|---|---|
| [`Chatter_application_proposal.md`](Chatter_application_proposal.md) | The why: the assistive-speech use case, user-experience goals. |
| [`Chatter_implementation.md`](Chatter_implementation.md) | The phased plan and its running changelog; device facts, toolchain, decisions. |
| [`Chatter_phase1_findings.md`](Chatter_phase1_findings.md) | On-device investigation results (input devices, display, doc store). |
| [`Chatter_stylus_research.md`](Chatter_stylus_research.md) | Pen rendering, the Calligraphy model, width calibration, tooling. |
| [`Chatter_user_guide.md`](Chatter_user_guide.md) | The end-user-facing instructions. |
| [`developer_mode_screen.md`](developer_mode_screen.md) | Enabling developer mode on the device. |
| `tablet_access.md` | **SENSITIVE** (root password + IPs) — gitignored, not in the repo. |
---
## 1. The device
**reMarkable Paper Pro Move — model RM03A, codename "Chiappa".**
- **SoC:** NXP i.MX93 (Arm Cortex-A55, aarch64), 2 GB RAM, 64 GB storage.
- **OS:** "Codex" Linux 5.7.121 (Yocto *scarthgap*), image `3.27.1.0`. SSH server
is **dropbear** (not OpenSSH).
- **Display:** E Ink **Gallery 3 — color** (ACeP, "acep2"), 7.3", **954×1696
logical / 960×1696 framebuffer, 264 PPI** (≈10.4 px/mm). Driven by **DRM/KMS
only** (`imx-drm`); there is **no `/dev/fb*`**.
- **Input:** pen = `/dev/input/event2` (Elan marker: `ABS_X` 06760, `ABS_Y`
011960, `ABS_PRESSURE` 04096, `ABS_TILT_X/Y` ±9000, `BTN_TOOL_PEN`/
`BTN_TOOL_RUBBER` for flip-to-erase). Finger = `/dev/input/event3` (10-point
capacitive, `ABS_MT_*`, grid 1248×2208). Power = `event0`, hall/folio = `event1`.
Pen and finger are **separate evdev devices** — Chatter tells them apart natively.
Full investigation: [`Chatter_phase1_findings.md`](Chatter_phase1_findings.md).
### 1.1 Filesystem / persistence model (critical)
- **`/` (rootfs, `/dev/mmcblk0p3`)** — ext4, mounted **read-only**, but
**persistent**. Remount rw (`mount -o remount,rw /`) to write; survives reboots,
**lost on an OS update** (A/B partition swap).
- **`/etc`, `/run`, `/var/volatile`** — **VOLATILE overlays** (upperdir on tmpfs).
Anything written here is **lost on reboot**. (This is why a systemd unit dropped
in `/etc/systemd/system` vanished after a reboot.)
- **`/home`** — encrypted, **fully persistent**, ~45 GB free. Everything Chatter
installs lives here (`/home/root/chatter/`). SSH keys persist because they are
under `/home/root/.ssh`.
**Consequence:** the launcher's systemd unit is installed onto the **rootfs**
(`/usr/lib/systemd/system`) so it survives reboots; the binary and scripts live
under `/home`. **An OS update wipes rootfs changes — disable OS auto-updates on a
delivered device, and keep `scripts/install-launcher.sh` to reinstall.**
---
## 2. The standard reMarkable software stack
Understanding the stock stack is necessary because Chatter reuses its display
plumbing and hands off to it.
- **`xochitl`** — the stock note app. Qt **6.8.2** / Qt Quick, proprietary. Owns
the display while running. Started/stopped as the `xochitl` systemd service.
- **The `epaper` QPA platform plugin** (`libqsgepaper.so`, in
`/usr/lib/plugins/`). A Qt platform + **software** scene-graph renderer for the
e-paper. A Qt app runs on it with `-platform epaper` (+ `QT_QUICK_BACKEND=epaper`).
Key facts learned by reverse engineering:
- It is a **software** renderer: custom `QSGGeometryNode`s are silently dropped;
only textures/rects/glyphs render. (This is why Chatter cannot draw ink via
the scene graph — see §3.)
- **`EPFramebuffer`** is the panel singleton (actually `EPFramebufferAcep2` on
this color device), guarded by an **flock** at `/tmp/epframebuffer.lock`
only one process may hold the panel. Success marker in the journal:
`SWTCON initialized \o/`. A stranded holder → `Failed to lock epframebuffer` /
`Failed to initialize SWTCON` and a blank panel. **Always stop the previous
holder first.**
- Relevant `EPFramebuffer` methods (mangled symbols bound in `src/epfb.h`):
- `instance()` → the singleton.
- `setBuffers(std::tuple<QImage,QImage>, QImage*)` — sets the front/back
buffers; **cross-DSO and interposable** (see §3).
- `swapBuffers(QRect, EPContentType, EPScreenMode, QFlags<UpdateFlag>)`
pushes a region to the panel. A single explicit call renders **solid**
(no dashed two-pass). Screen modes (from `EPScreenModeItem::Mode`):
`Pen=0, Mono=1, Animation=2, UI=3, Content=4, Sleep=5`. Chatter uses
`Pen` (0) for fast ink and `Content` (4, "full update, STD") for full
refreshes.
- `ghostControl(GhostControlMode)` — the panel's anti-ghosting API. Modes 0/3
do an immediate full-screen de-ghost via the *region* `swapBuffers`; mode 1
schedules one. **Chatter does NOT call it** — its region-swap path uses
internal `EPContentMap`/`EPScreenModeMap` members the stock app maintains and
we do not, so calling it corrupts state and hangs after a few calls. Chatter
de-ghosts manually instead (§6.3).
- The panel uses **ACeP color waveforms** (`acep2_lut`, `get_waveform_data`,
software TCON "SWTCON"). Color **ghosting** is real and only cleared by a
full-update waveform driven to the **dark** extreme (§6.3).
- **Pen styles** live in `~/.config/remarkable/xochitl.conf`, key
**`LastWritingTool`** (a `@Variant` `QVariantMap`, readable via `QSettings`):
`LastPen` (tool id, e.g. 21 = Calligraphy, 16 = a fineliner), `LastPenSize`
(category 1/2/3 = thin/thicker/thickest), `LastPenColorCode` (`0xAARRGGBB`).
**xochitl writes this to disk only when you leave a document** (return to the
document list) — not on toolbar taps, and not reliably on an abrupt stop.
---
## 3. Chatter architecture (the core idea)
**Problem:** the make-or-break requirement is that ink look like the stock pen —
solid, crisp, low-latency. The `epaper` software scene graph **cannot** do this:
geometry nodes don't render, and `QQuickPaintedItem` textures always come out as
the e-ink **dashed two-pass** refresh, regardless of screen mode or antialiasing.
**Solution — a direct-framebuffer ink pipeline that bypasses the Qt scene:**
1. **`FbCapture`** (`src/FbCapture.{h,cpp}`) interposes
`EPFramebuffer::setBuffers` **in-process**. The executable is linked
`-Wl,--export-dynamic`, so its definition of that symbol wins the cross-DSO
call from the plugin; we capture the real framebuffer's pixel memory and wrap
it as a `QImage` (`FbCapture::framebuffer()`) with **no copy** (`constBits`).
(Intra-DSO calls like `swapBuffers` are *not* LD_PRELOAD-interposable due to
direct binding — but in-process `--export-dynamic` on `setBuffers` works.)
2. **`InkEngine`** (`src/InkEngine.{h,cpp}`) draws strokes with `QPainter`
**straight into that framebuffer image**, then calls
`EPFramebuffer::swapBuffers` (via `src/epfb.h`) on just the dirty rectangle.
One explicit swap → **solid, single-pass** ink.
3. **Qt Quick renders only the static UI** (the two buttons). The scene never
recomposites over the ink because nothing in it animates.
This is what gives stock-quality strokes. The reverse-engineering trail and the
dead-ends (geometry nodes, screen modes, antialiasing) are in
[`Chatter_stylus_research.md`](Chatter_stylus_research.md) and the
`Chatter_implementation.md` changelog (v0.7).
### 3.1 Process / run model
- Chatter is a single Qt 6 executable run as a **transient systemd service**
(`systemd-run --unit=chatter …`), with `QT_QUICK_BACKEND=epaper`,
`LD_LIBRARY_PATH=/usr/lib/plugins/scenegraph`, `-platform epaper`.
- It **requires xochitl to be stopped** (single DRM master + the panel flock).
- Managed via `systemctl {stop,status} chatter` and `journalctl -u chatter`.
---
## 4. Source components
```
src/
main.cpp Entry point: reads pen style, wires PenDevice → InkEngine,
exposes `ink`/`appControl` to QML, loads the QML UI.
FbCapture.{h,cpp} Interposes EPFramebuffer::setBuffers; exposes the live
framebuffer QImage + FbCapture::ready().
InkEngine.{h,cpp} THE core. Virtual canvas, ink drawing, finger-erase, buttons
(paint/hit-test/actions), two-finger scroll, de-ghosting.
PenDevice.{h,cpp} evdev reader for the pen (event2); maps to screen coords;
emits strokeStart / strokeMove(pos,pressure,tiltX,tiltY,eraser)
/ strokeEnd.
AppControl.{h,cpp} returnToStandard(): transient unit stops chatter, starts xochitl.
epfb.h asm-label bindings to the private EPFramebuffer symbols
(instance, swapBuffers, ghostControl).
EPScreenModeItem.{h,cpp} Legacy/unused: binding to the private screen-mode item
from the scene-graph era. Kept for reference.
InkCanvas.*, Experiment.* Legacy from the Qt-Quick-canvas spike; NOT built.
qml/Main.qml White Window; the Back/Clear buttons (TopButton); a
MultiPointTouchArea for finger erase (1) and scroll (2).
tools/
chatter_launcher.c The return-to-Chatter daemon (4-finger watcher on event3).
grabtest.c Probe whether an input device is EVIOCGRAB-exclusive.
fbdump.cpp LD_PRELOAD framebuffer snapshot (BMP; device has no PNG plugin).
swapshim.cpp, setbufshim.cpp Feasibility shims used during RE.
scripts/
build.sh Source the SDK env, cmake build.
deploy-and-run.sh Stop xochitl+chatter, scp, relaunch as a transient unit.
to-chatter.sh Switch standard → Chatter (run by the launcher).
install-launcher.sh Persistently install the launcher unit on the rootfs.
chatter-launcher.service The systemd unit (installed to rootfs).
restore-xochitl.sh Return to the standard GUI.
```
---
## 5. Input & gesture model
Two independent input streams, never confused:
- **Pen (`event2`)** — read by `PenDevice` and delivered to `InkEngine`. The
`epaper` QPA does **not** deliver the pen to Qt, so the pen never triggers QML.
- **Finger (`event3`)** — delivered by the `epaper` QPA to Qt as touch, handled in
QML (`MultiPointTouchArea`, and the buttons' `MouseArea`s).
Gesture map:
| Input | Action | Where handled |
|---|---|---|
| Stylus draw | Ink (flip = erase via `BTN_TOOL_RUBBER`) | PenDevice → InkEngine |
| Stylus tap on a button | Button action | InkEngine hit-tests (`m_buttons`) |
| **1 finger** drag | **Erase wipe** (~12 mm; a tap erases nothing) | QML MultiPointTouchArea → `InkEngine::erase*` |
| **2 fingers** drag | **Vertical scroll** | QML → `InkEngine::panBy/panEnd` |
| Finger tap on a button | Button action (gray feedback) | QML MouseArea → `InkEngine::flashButton/activateButton` |
| **4 fingers** hold (~700 ms) | **Return to Chatter** (only while xochitl is front) | `tools/chatter_launcher.c` |
The MultiPointTouchArea **latches** the gesture type until all fingers lift, so a
two-finger scroll never degrades into an erase when one finger is raised. Erase
requires movement past a ~1.5 mm threshold (tap-safe).
Buttons (`Back`, `Clear`) are a **single source of truth** in `InkEngine`: QML
registers their geometry (`registerButton`) so the engine can exclude ink, redraw
them after a blit, and hit-test stylus taps. Press feedback (`flashButton`) and
actions (`activateButton`) are drawn directly to the framebuffer (instant, no
scene flashing); both finger and stylus route through them.
---
## 6. The virtual canvas, scrolling, and de-ghosting
### 6.1 Growable raster canvas
`InkEngine` holds a `QImage` **canvas** larger than the screen (starts 2× tall,
**grows downward** as you write near the bottom). The screen is a **viewport**
into it at vertical offset `m_panY`. Drawing maps screen→canvas (`+m_panY`); a
dirty canvas rect is blitted back to the framebuffer (`blitRegion`) and the
buttons are repainted on top. (Storage model chosen: raster, to preserve the
exact ink quality; trade-off is no crisp zoom-in. Horizontal/zoom are future work.)
### 6.2 Scrolling
Two-finger drag → `panBy(dy)` (natural: content follows fingers), clamped to the
canvas, fast-blitted per step. `panEnd()` debounces a de-ghost (§6.3).
### 6.3 De-ghosting (color ACeP ghosting)
Fast `Pen`-waveform swaps leave **color residue** ("faint red duplicate") that
accumulates while scrolling and is **not** cleared by a white redraw — it is
panel retention, cleared only by a full-update waveform driven to **black**
(white/gray do not clear it; this was tested). Chatter's `fullRefresh()`:
fills the screen **black** + full-update swap (`screenMode=Content`), waits
~220 ms, then full-updates the real content. `ghostControl()` would be the
"proper" API but corrupts state (§2), so this manual flash is used.
To keep the flash from being intrusive:
- It runs **only after scrolling**, **debounced** ~1 s after the last scroll
(not on every finger-lift).
- **Clear has two methods:** if there was **no scrolling** since the last clear
(the common fill-one-screen case) it uses the **gentle fast clear**; if there
**was** scrolling, it does the **black de-ghost** clear. (`m_scrolled` flag.)
- Tunables via env: `CHATTER_FULL_SM` (full-update screen mode, default 4),
`CHATTER_FLASH_GRAY` (flash level 0=black..255; black is what actually clears).
A black flash is intrinsic to clearing color ghosting (the stock UI flashes on
its full refreshes too); we minimized *when* it happens rather than eliminating it.
---
## 7. Pen-style matching, width calibration, calligraphy
- Chatter reads `xochitl.conf` `LastWritingTool` at startup (`readPenStyle` in
`main.cpp`) and applies tool type, size, and color — **no style menu in
Chatter**. Because each switch-to-Chatter restarts the process, it re-reads the
current pen. **The flush sequence matters** (§2): set the pen, *use it*, **leave
the document**, then switch to Chatter.
- **Width** is calibrated to measured widths on the 264-PPI panel:
size 3 ≈ 2 mm, size 2 ≈ 1 mm, thinnest ≈ 2 px; pressure ≈ doubles width.
- **Calligraphy** (tool 21) is approximated with a dynamic
direction/pressure/speed/tilt width model. Full detail, measurements, and the
formula: [`Chatter_stylus_research.md`](Chatter_stylus_research.md).
---
## 8. Toggle & the return launcher
- **Chatter → standard:** `Back``InkEngine::backRequested`
`AppControl::returnToStandard()` → a transient unit stops chatter and starts
xochitl (sequenced so the panel lock is released first).
- **standard → Chatter:** `tools/chatter_launcher.c` runs always as a rootfs
systemd service. It reads `event3` **without grabbing it** (verified possible
via `tools/grabtest.c` — xochitl does not hold an exclusive grab), counts
multitouch slots, and on a **4-finger hold ≥700 ms** while xochitl is the front
app runs `to-chatter.sh`. It reopens the device on any read interruption (sleep/
wake) so it never dies.
- **Install persistently:** `scripts/install-launcher.sh` (remounts the rootfs rw,
places the unit + its `multi-user.target.wants` symlink under
`/usr/lib/systemd/system`). See the persistence model in §1.1.
---
## 9. Build & deploy
**Get the source:** clone from the Gitea server (needs an account with read
access to `ack/chatter` and your SSH key registered, or use the HTTPS URL):
```
git clone git@git.andykopra.com:ack/chatter.git
cd chatter
```
**Toolchain:** official reMarkable Chiappa SDK `3.27.0.97` (Qt 6.8.2 sysroot,
x86_64 host). Install, then `source environment-setup-cortexa55-remarkable-linux`.
```
scripts/build.sh # source SDK env + cmake build -> build/chatter
scripts/deploy-and-run.sh # stop xochitl+chatter, scp, relaunch as transient unit
scripts/restore-xochitl.sh # back to the standard GUI
scripts/install-launcher.sh # one-time (and after each OS update): persist the launcher
```
`CMakeLists.txt`: Qt6 Quick app (`qt_add_executable` + `qt_add_qml_module`), links
`libqsgepaper.so`, and crucially `target_link_options(... -Wl,--export-dynamic)`
so the `setBuffers` interposition wins.
**Device housekeeping:** only one process may hold the panel — always stop the
previous holder. Deploy under `/home/root/chatter`, never the rootfs (except the
launcher unit). The device sleeps and DHCP may reassign its IP on wake; if you
script a reconnect, rescan for the tablet's MAC address (shown on the device's
GPLv3-compliance screen, alongside its IPs).
---
## 10. Status & open items
**Working on hardware:** solid stock-quality ink matching the selected pen;
finger-wipe erase; whole-page Clear (two methods); stylus eraser; bidirectional
toggle (Back + 4-finger launcher, persistent); vertical scroll on a growable
canvas with debounced de-ghosting; instant button feedback (finger + stylus).
**Open / future:**
- **Save** (Phase 5) — write a timestamped transcript into a "Chatter" folder in
xochitl's on-disk document format; importance to the user still unconfirmed.
See `Chatter_implementation.md` §6 / §6a.
- **Horizontal scroll & zoom-out overview** — deferred extensions of the canvas.
- **Calligraphy fidelity** — "close enough" approximation; exact nib unknown.
- **Erase / gesture thresholds** — to be tuned with the end user (Matt's field test).
- **OS auto-update** would wipe the rootfs launcher unit (and could break paths) —
disable it on a delivered device.
---
*This document is the technical entry point. When the design changes, update this
file and the relevant component doc; keep `Chatter_implementation.md`'s changelog
as the chronological record.*