Skip to content

Add reMarkable Paper Pro support (experimental)#2

Merged
dreeko merged 10 commits into
masterfrom
rmpp-support
May 11, 2026
Merged

Add reMarkable Paper Pro support (experimental)#2
dreeko merged 10 commits into
masterfrom
rmpp-support

Conversation

@dreeko

@dreeko dreeko commented May 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • Introduces a DeviceProfile abstraction so per-model hardware facts (event-struct layout, device paths, axis ranges) live in one place; ReMarkable2Profile and ReMarkablePaperProProfile plug in.
  • Adds --device auto|rm2|rmpp (CLI) and a Device dropdown (GUI). auto probes uname -m over SSH and picks the right profile; armv7l → rM2, aarch64 → rMPP.
  • Fixes a latent Wayland correctness gap on rM2: input_absinfo.resolution is now populated on the virtual uinput pen device (rM2 = 100, rMPP = 2832/2064), so libinput-based compositors recognise it as a tablet rather than a generic absolute device.

Paper Pro support is experimental — most rMPP constants are seeded from Evidlo/remarkable_mouse's rmpro branch and have not been verified on hardware. Search the source for TODO(rmpp-phase0) for the gaps (pen tilt + distance ranges, touch axis ranges, pen-suppression assumption). Full plan + release-notes draft in docs/IMPLEMENTATION_PLAN_RMPP.md and docs/RELEASE_NOTES_v0.4.0.md.

Test plan

  • dotnet build clean — 0 warnings, 0 errors, all projects.
  • dotnet test green — Core 72/72 (includes new 64-bit parser regression + 19 DeviceDetector tests), Windows 10/10.
  • ReMarkable2Constants references gone from compiled code.
  • Manual rM2 smoke: pen draws in Krita with pressure curve choice unchanged.
  • Manual rM2 Linux smoke: verify the virtual uinput device shows up as a tablet under Wayland (udevadm info --query=property --name=<device> shows ID_INPUT_TABLET=1).
  • Phase 0 hardware verification on a Paper Pro — required before tagging v0.4.0:
    • Enable Developer Mode, confirm SSH at 10.11.99.1.
    • evtest /dev/input/event2 (pen) and event3 (touch); save captures.
    • Confirm tilt + distance ranges and replace TODO(rmpp-phase0) placeholders.
    • Confirm touch axis ranges and orientation.
    • Verify whether pen suppresses touch in proximity (sets PenSuppressesTouch).

🤖 Generated with Claude Code

dreeko and others added 10 commits May 11, 2026 10:26
Pure refactor — extract reMarkable 2 hardware constants into a
DeviceProfile record. Behavior is unchanged on rM2; all existing tests
pass without value adjustments.

The pipeline now reads device-specific facts (event-struct layout,
device-node paths, pen/touch axis ranges, max tracked contacts) from
a profile instead of a static class. Host-target scale (Windows Ink
pressure 0-1024, tilt +/-90) moves to InjectionScale because it is
not device-specific.

EvdevParser is parameterised by EvdevLayout: 32-bit ARM (rM2) uses
the 16-byte input_event with HHi at offsets 8/10/12; 64-bit ARM uses
24-byte input_event with HHi at offsets 16/18/20. Adds regression
tests for the 24-byte path so the rMPP desync signature reported in
Evidlo/remarkable_mouse Issue #92 cannot land here.

Phase 1 of docs/IMPLEMENTATION_PLAN_RMPP.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Constants seeded from Evidlo/remarkable_mouse's rmpro branch (Issue #92).
Event layout is Bits64 (24-byte input_event) so the shared EvdevParser
decodes the device's aarch64 stream correctly — the regression tests
added in the previous commit guard against accidental Bits32 fallback.

Items not in the public community data (tilt, distance, touch axis
ranges, pen-suppression behavior) carry // TODO(rmpp-phase0) markers
and must be confirmed via evtest on real hardware before v0.4.0 ships.

Phase 2 of docs/IMPLEMENTATION_PLAN_RMPP.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The input_absinfo struct already had a resolution field declared in
interop, but UinputOutput.SetAxis never populated it — the value was
silently 0 on every axis. libinput uses this field to recognise the
virtual device as a tablet on Wayland; with resolution=0 the
compositor categorises us as a generic absolute-axis device, which
falls back to mouse semantics rather than pen ones.

The fix surfaces resolution as a parameter on SetAxis, takes
ABS_X/Y values from the active DeviceProfile, and sets rM2 to
100 ticks/mm (the FreeCap23 value). rMPP uses Evidlo's 2832/2064
which it already declares in its profile. ABS_PRESSURE and ABS_TILT
limits also move to InjectionScale rather than literals so the
host-target scale lives in one place.

Phase 3 of docs/IMPLEMENTATION_PLAN_RMPP.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DeviceDetector probes the connected reMarkable's CPU architecture via
`uname -m`: armv7l maps to the rM2 profile, aarch64/arm64 maps to the
Paper Pro profile. SshTransport now exposes RunCommandAsync for the
one-shot probe; the streaming pipeline opens its own session
afterwards as before.

CLI gains --device auto|rm2|rmpp (default auto). GUI gains a Device
dropdown at the top of the Connection section, persisted in
AppSettings.Device. The App's StartPipeline is now a thin wrapper
over StartPipelineAsync so detection can happen off-thread without
blocking the UI.

Unit tests cover the architecture-to-profile mapping and the
short-name lookup. Live SSH detection requires real hardware and is
exercised in Phase 0 bring-up rather than in CI.

Phase 4 of docs/IMPLEMENTATION_PLAN_RMPP.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- README intro and Requirements call out Paper Pro alongside rM2 and
  the Developer Mode unlock step.
- New "Paper Pro status" section enumerates what is verified vs.
  what still needs hardware (the TODO(rmpp-phase0) markers).
- New "Hardware details — Paper Pro" mirrors the existing rM2 section
  with Evidlo's draft axis ranges flagged as placeholders.
- `--device auto|rm2|rmpp` added to the CLI options table.
- New docs/RELEASE_NOTES_v0.4.0.md drafts the GitHub release narrative;
  the project does not maintain a CHANGELOG file.
- Feasibility and implementation-plan docs gain a Status header
  noting Phases 1-5 are landed and Phase 0 is the remaining gate.

Phase 5 of docs/IMPLEMENTATION_PLAN_RMPP.md. No code changes in this
commit; tag v0.4.0 deferred until Phase 0 hardware verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small fixes addressing real-world feedback that the auto-detect
path could hang for 30+ seconds and then transition silently to
"Disconnected" with no indication of what went wrong.

1. SshTransport sets ConnectionInfo.Timeout to 15s. SSH.NET's default
   socket-connect timeout is ~30s, which left the user staring at
   "Connecting…" through a full half-minute when the rM's USB-Ethernet
   had a transient hiccup on cold-start. 15s tolerates normal handshake
   variance while still feeling responsive on failure.

2. SshTransport.ConnectAsync is now a no-op when the SshClient is
   already connected. This lets the auto-detect probe pass its
   connected transport into TabletPipeline; the pipeline's first
   ConnectAsync no longer tears down and re-establishes the session,
   eliminating the duplicate handshake. Reconnect-after-EOF still
   takes the full cleanup-and-rebuild route.

3. App.xaml.cs raises a new ConnectionErrorOccurred event when
   StartPipelineAsync catches an exception before reaching the
   streaming loop. SettingsWindow subscribes and writes the message
   into the status text in red, so probe timeouts and auth failures
   are no longer invisible. The Disconnected state change is fired
   first so the error message lands on top of it.

Reported after manual smoke test on rM2: auto-detect timed out (now
visible in UI), manual rm2 worked (path unchanged). The Test
Connection button surfaces its own errors via the existing
SetStatus("Failed: ..." ) path and benefits from the new 15s timeout
since it builds its own SshClient with the same default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
StartPipelineAsync's catch already forwarded probe / mapper / output
construction failures to ConnectionErrorOccurred so SettingsWindow
could display them. Errors raised inside the pipeline's own retry
loop (pipeline.Error event — typically socket-level failures during
reconnect, e.g. ECONNREFUSED when the rM is asleep or the IP is
wrong) went only to the app log; the UI silently transitioned to
"Disconnected." with no hint of what failed.

Forward those errors through ConnectionErrorOccurred too, on the
dispatcher thread because the pipeline raises Error from the
thread-pool. Diagnosed from a smoke test over Wi-Fi where the user
hit a permanent ECONNREFUSED loop with no useful UI feedback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The docs/ directory is in .gitignore but these five files were
tracked before the rule was added. Remove them from the index;
the working-tree copies are untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dreeko dreeko merged commit 61e4453 into master May 11, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant