Use a reMarkable 2 (Paper Pro experimental) as a pressure-sensitive drawing tablet on Windows via SSH + Windows Ink
Find a file
dreeko cb8d1d180f fix(cli): present a friendly error instead of a stack trace on connect failure
The auto-detect probe ran outside any try/catch, so transport-level
failures (no route to host, connection refused, auth failure, etc.)
splattered the full SSH.NET stack to the user's terminal. Wrap both the
probe and the existing pipeline.RunAsync handler in a shared
ReportFatal/ClassifyFatal helper that walks inner exceptions, matches
on SocketError and SshException subtypes, and prints a one-line
Error/Hint pair. Stack traces are now gated behind --debug.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 22:51:44 +08:00
.github/workflows ci: pass -f to publish/build now that CLI multi-targets 2026-05-06 18:50:41 +08:00
src fix(cli): present a friendly error instead of a stack trace on connect failure 2026-05-17 22:51:44 +08:00
tests chore(cleanup) 2026-05-11 11:08:51 +08:00
tools/EventDiagnostics chore(cleanup) 2026-05-11 11:08:51 +08:00
.gitignore chore: add RESOLUTION_PLAN.md to .gitignore 2026-05-11 12:10:43 +08:00
Directory.Build.props build: allow restoring the solution on non-Windows hosts 2026-05-17 22:51:34 +08:00
README.md docs: cover Paper Pro support in README; draft v0.4.0 release notes 2026-05-11 10:42:35 +08:00
remarkable-input-tablet.sln Add Linux CLI support via uinput 2026-05-06 17:26:58 +08:00

remarkable-input-tablet

Use a reMarkable 2 (stable) or reMarkable Paper Pro (experimental — see Paper Pro status) as a pressure-sensitive drawing tablet on Windows and Linux. No drivers, no root modifications, no third-party services.

The tablet connects over SSH (USB or Wi-Fi). The pen's raw evdev events are streamed to the PC and injected as native pointer input, giving you full pressure sensitivity, tilt, hover detection, and eraser support in any compatible application. Optional multi-touch gestures (pinch / pan / rotate) inject as real touch contacts to apps that consume them.

Requirements

Tablet — reMarkable 2

  • Firmware tested: Wacom I2C Digitizer, version 1231
  • Root password: Settings → Help → Copyrights and licenses → scroll to bottom

Tablet — reMarkable Paper Pro

  • Developer Mode must be enabled first: Settings → General → Paper Tablet → Software → Advanced → Developer Mode. The root password then appears at Settings → Help → About → Copyrights and Licenses. See reMarkable's Developer mode article.
  • Experimental — most device constants are seeded from community data (Evidlo/remarkable_mouse rmpro branch) and have not been independently verified against hardware. See Paper Pro status for the current verification gaps.

Host — Windows

  • Windows 10 1809 or later (Windows Ink pointer injection API)
  • Connection: USB cable (SSH at 10.11.99.1) or Wi-Fi (same SSH, different IP)

Host — Linux

  • Any x86-64 Linux with kernel 4.5+ (uinput module)
  • Connection: USB cable or Wi-Fi (same as Windows)
  • Permissions: membership in the input group (one-time setup, see below)

Quick start

Windows — GUI app

Download RemtabletApp-*.zip from Releases, extract, run RemarkableTablet.App.exe.

The app lives in the system tray. Right-click → Connect... to open Settings, enter your device IP and root password, and click Connect.

The Settings dialog also exposes a Touch Gestures checkbox to enable pinch / pan / rotate from the rM2 touchscreen (see the Touch gestures section), and a Pressure dropdown to pick between Linear, Soft (boosts light strokes), and Hard (suppresses light strokes) response curves.

Windows — CLI

Download remtablet-*-win-x64.zip from Releases, extract, and run:

remtablet.exe --password <root-password>

Linux — CLI

Download remtablet-*-linux-x64.tar.gz from Releases, extract, and run:

# One-time: grant /dev/uinput access (log out and back in after)
sudo usermod -aG input $USER

# Run
./remtablet --password <root-password>

If your display resolution is not 1920×1080, pass it explicitly:

./remtablet --password <root-password> --width 2560 --height 1440

Press Ctrl-C to stop.

CLI options

Flag Default Description
--password <pw> Root password (required unless --key is set)
--key <path> Path to SSH private key file
--address <ip> 10.11.99.1 Device IP address
--orientation <value> portrait portrait, landscape, portraitflipped, landscapeflipped
--output <value> ink ink (full pressure+tilt) or mouse (cursor only, Windows only)
--width <px> auto (Windows) / 1920 (Linux) Screen width in pixels
--height <px> auto (Windows) / 1080 (Linux) Screen height in pixels
--debug off Print pipeline stage info on startup
--gestures <value> off touch (inject multi-touch contacts for pinch / pan / rotate) or off. The rM2 firmware suppresses touch while the pen is in proximity, so two-finger gestures only register when the pen is set aside.
--pressure <value> linear Pressure response curve. linear (1:1), soft (boosts light strokes — pen feels lighter), or hard (suppresses light strokes — pen feels stiffer).
--device <value> auto auto (probe via uname -m), rm2, or rmpp. Auto-detect runs a short SSH command before the streaming pipeline starts; force a specific profile only if detection fails.

Orientation

Hold the tablet with the pen slot at the bottom for portrait (default). Orientation controls how the tablet's native coordinate space maps to the screen:

Value Physical position
portrait Pen slot at bottom (default drawing position)
landscape Pen slot on right
portraitflipped Pen slot at top
landscapeflipped Pen slot on left

Touch gestures

Pass --gestures touch to enable pinch / pan / rotate gestures from the rM2's capacitive touchscreen. The tool opens a second SSH stream against /dev/input/event2 and injects multi-touch contacts to the host:

  • Windows: synthetic touch contacts via CreateSyntheticPointerDevice(PT_TOUCH)
    • InjectSyntheticPointerInput. Apps that handle Windows touch (Krita, Photoshop, Affinity, browsers) run their own gesture recognition on the injected contacts.
  • Linux: a second uinput device (reMarkable 2 Touch) using the MT-B slot protocol with INPUT_PROP_DIRECT. Apps reading the input subsystem see real multi-touch contacts.

Important hardware behavior: the rM2 firmware suppresses touch reporting while the pen is in proximity (verified via evtest). This means simultaneous draw + pinch is not possible at the hardware level — the workflow is "lift pen → pinch / pan / rotate → resume drawing." This is a property of the device, not the tool.

App compatibility

Windows (Windows Ink output)

  • Krita — Settings → Configure Krita → Tablet → set input to Windows 8+ Pointer Input
  • Photoshop 2018+ — works out of the box
  • Affinity Photo / Designer — works out of the box
  • Clip Studio Paint — works out of the box
  • Paint Tool SAI v2 — requires "Tablet" input mode in preferences

Use Mouse mode only as a fallback for applications that don't support Windows Ink.

Linux (uinput output)

The virtual device appears as a standard pen tablet to any app that reads from the Linux input subsystem:

  • Krita — works out of the box with pressure and tilt
  • GIMP — enable extended input devices: Edit → Input Devices
  • Inkscape — Input Devices dialog, set the virtual tablet to "Screen" mode
  • MyPaint — works out of the box
  • Blender — works with tablet pressure in sculpt and paint modes

The device uses INPUT_PROP_DIRECT so absolute coordinates map 1:1 to screen pixels. Pressure is reported on the 01024 scale.

Touch gestures (--gestures touch)

Pinch / pan / rotate compatibility depends on whether the host application consumes Windows touch (or Linux multi-touch) gestures. Confirmed working where verified; rows marked "untested" are expected to work but haven't been hands-on validated.

App Pinch zoom Pan Rotate Notes
Windows
Krita Real touch + Windows Ink coexist cleanly.
Photoshop untested untested untested Expected to work — Photoshop has full Windows touch support.
Affinity Photo / Designer untested untested untested Expected to work.
Clip Studio Paint untested untested untested
Microsoft Edge / Chrome untested untested n/a Browser pinch-zoom of pages.
Windows Photos untested untested n/a Native Windows app — should work.
Linux
Krita untested untested untested
GIMP untested untested n/a
Inkscape untested untested n/a
Blender untested untested n/a

If you confirm or find a failure, the table above is the place to record it.

Linux uinput setup

The /dev/uinput device node requires write access. The cleanest approach is a udev rule (no sudo required after setup):

# Option A: add yourself to the input group (requires log out/in)
sudo usermod -aG input $USER

# Option B: udev rule for uinput specifically
echo 'KERNEL=="uinput", GROUP="input", MODE="0660"' \
  | sudo tee /etc/udev/rules.d/70-uinput.rules
sudo udevadm control --reload && sudo udevadm trigger

Settings persistence

The GUI app (Windows) saves settings to:

%APPDATA%\remarkable-input-tablet\settings.json

Passwords are never stored. You will be prompted each session.

Reconnection

The pipeline reconnects automatically if the SSH stream drops (USB unplugged, device sleeps, Wi-Fi hiccup). Before each reconnect attempt a synthetic pen-up and an "all touch contacts released" event are injected so drawing applications don't get a stuck pen or stuck touch contacts. Retry delays follow exponential backoff: 1 s, 2 s, 4 s, 8 s, 16 s, 30 s (capped).

Building from source

Requires .NET 10 SDK.

git clone <repo>
cd remarkable-input-tablet

Windows

dotnet build
dotnet test

Linux

# Build and test Linux-compatible projects.
# -f net10.0 skips the Windows TFM (CLI is multi-targeted).
dotnet build src/RemarkableTablet.Cli -f net10.0
dotnet test tests/RemarkableTablet.Core.Tests

Publish CLI — Windows (NativeAOT, ~12 MB single file)

dotnet publish src/RemarkableTablet.Cli -c Release -r win-x64 -f net10.0-windows `
  -p:PublishAot=true -p:InvariantGlobalization=true -o out/cli

Publish CLI — Linux (NativeAOT, ~12 MB single file)

# Prerequisite: clang and zlib headers
sudo apt-get install -y clang zlib1g-dev   # Debian/Ubuntu

dotnet publish src/RemarkableTablet.Cli -c Release -r linux-x64 -f net10.0 \
  -p:PublishAot=true -p:InvariantGlobalization=true -o out/cli

Publish GUI app — Windows (self-contained single file, ~70 MB)

dotnet publish src/RemarkableTablet.App -c Release -r win-x64 `
  -p:SelfContained=true -p:PublishSingleFile=true -o out/app

Trimming is intentionally not enabled: the tray icon uses System.Windows.Forms.NotifyIcon, and WinForms is not trim-compatible (NETSDK1175). The CLI ships smaller because it uses NativeAOT instead.

Architecture

reMarkable 2                         Host PC
─────────────────────────────        ─────────────────────────────────────────────
/dev/input/event1 (pen)
        │
   cat (SSH stdout) ──┐
                      │
/dev/input/event2 (touch, when --gestures touch)
        │             │
   cat (SSH stdout) ──┤
                      │
   ─── SSH (TCP) ────►┴► SshTransport ── one SshClient, one SshDeviceStream per evdev device
                            │
                            ├──► EvdevParser → Channel<EvdevEvent> ──► TabletStateMachine
                            │                                              │  Channel<PenFrame>
                            │                                         CoordinateMapper
                            │                                              │  MappedFrame
                            │                                          IOutputMode
                            │                                           ├─ WindowsInkOutput      (Windows Ink injection)
                            │                                           ├─ MouseOutput           (cursor only, Windows)
                            │                                           └─ UinputOutput          (Linux pen tablet)
                            │
                            └──► EvdevParser → Channel<EvdevEvent> ──► TouchStateMachine
                                                                           │  Channel<TouchFrame>
                                                                       TouchCoordinateMapper
                                                                           │  MappedTouchFrame
                                                                       ITouchOutput
                                                                        ├─ WindowsTouchInjectionOutput  (synthetic touch, Windows)
                                                                        └─ UinputTouchOutput            (Linux MT-B touchscreen)

Projects:

Project Platform Role
RemarkableTablet.Core Any Platform-agnostic pipeline: evdev parser, state machines (pen + touch), coordinate mappers, gesture engine, multi-stream SSH transport
RemarkableTablet.Windows Windows Win32 P/Invoke layer: Windows Ink pen injection, mouse output, synthetic touch injection (PT_TOUCH)
RemarkableTablet.Linux Linux uinput P/Invoke layer: virtual pen tablet + virtual MT-B multi-touch device
RemarkableTablet.Cli Windows + Linux remtablet — headless CLI, NativeAOT
RemarkableTablet.App Windows RemarkableTablet.App.exe — system tray GUI, WPF + WinForms
tools/EventDiagnostics Windows Live evdev event stream logger — streams events to console for debugging

Paper Pro status

The reMarkable Paper Pro is on a 64-bit aarch64 platform (NXP i.MX 8MM) versus the rM2's 32-bit armv7l, which changes the kernel struct input_event size from 16 to 24 bytes. The shared event parser is parameterised by EvdevLayout (see src/RemarkableTablet.Core/Devices/) and a regression test feeds a 24-byte stream to guard against the desync signature documented in Evidlo/remarkable_mouse Issue #92.

Auto-detection via uname -m routes the right profile automatically. You can also pass --device rmpp explicitly.

What is verified:

  • Build + unit tests (Windows + Linux, Core + Windows test suites).
  • 64-bit event-struct decoding (synthetic frames).
  • Device-name to profile mapping.

What is not yet verified on real hardware (search the source for TODO(rmpp-phase0)):

  • Pen tilt and hover-distance axis ranges (placeholders match rM2 conventions).
  • Touchscreen axis ranges (placeholders match the 1620 × 2160 display).
  • Whether the pen suppresses touch in proximity (assumed true; rM2 behavior).
  • That uname -m returns aarch64 on the production firmware build (community sources strongly imply yes).

If you have a Paper Pro and want to help, run tools/EventDiagnostics against /dev/input/event2 and /dev/input/event3 and open an issue with the captured axis ranges. Until those replace the placeholders, treat Paper Pro support as experimental.

Hardware details

Pen digitizer (/dev/input/event1)

Confirmed via evtest on firmware version 1231 (Wacom I2C Digitizer). Axis convention re-verified 2026-05-07 against the touchscreen — earlier docs had ABS_X / ABS_Y rotated 180°.

Axis Range Notes
ABS_X 0 20966 Long axis: 0 = top of device, max = USB/bottom (portrait)
ABS_Y 0 15725 Short axis: 0 = right, max = left (portrait)
Pressure 0 4095 12-bit, mapped to 01024 via shaping curve (Windows Ink scale)
Distance 0 255 Hover height above surface
Tilt X/Y 9000 9000 Firmware units, mapped to ±90°

Touchscreen (/dev/input/event2)

Capacitive multi-touch panel, driver pt_mt. Confirmed via evtest 2026-05-07.

Axis Range Notes
ABS_MT_POSITION_X 0 1403 Display-aligned, INPUT_PROP_DIRECT
ABS_MT_POSITION_Y 0 1871 Display-aligned, INPUT_PROP_DIRECT
ABS_MT_PRESSURE 0 255 Per-contact pressure
ABS_MT_SLOT 0 31 Hardware-reported; tool caps tracking at 5
ABS_MT_TRACKING_ID 0 65535 Monotonically incrementing per-contact ID
Sample rate ~85 Hz Measured under continuous motion

Pen-priority hardware behavior: the rM2 firmware suppresses the touchscreen entirely while the pen is in proximity. This is verified — the PenToolGate was originally planned as a host-side workaround but turned out to be unnecessary. Workflow: lift the pen, gesture, then resume drawing.

Note on tilt: tilt rotation matches the position transform that aligns with the touchscreen. The Windows Ink sign convention (positive tilt-X = pen leans toward +X screen axis) is not independently verified — if brush highlights point the wrong direction, the four cases in CoordinateMapper.RotateTilt are the place to flip signs.

Hardware details — Paper Pro

Experimental. Values below are from the Evidlo/remarkable_mouse rmpro branch (Issue #92), not independently verified. See Paper Pro status for what still needs hardware confirmation. The placement of pen / touch device nodes matches the community reports: event0 = power button, event1 = pen attach/detach, event2 = pen, event3 = touch.

Pen digitizer (/dev/input/event2)

Axis Range Notes
ABS_X 0 11180 Resolution 2832 ticks/mm
ABS_Y 0 15340 Resolution 2064 ticks/mm
Pressure 0 4096 12-bit, mapped to 01024 via shaping curve
Tilt X/Y ±9000 Placeholder; not in the community data
Distance 0 255 Placeholder; not in the community data

The active "Marker Plus" stylus is battery-powered and inductively charged, not Wacom EMR. Old rM2/LAMY EMR pens do not work on the Paper Pro.

Touchscreen (/dev/input/event3)

Axis Range Notes
ABS_MT_POSITION_X 0 1619 Placeholder; assumes display-aligned 1620 × 2160
ABS_MT_POSITION_Y 0 2159 Placeholder; assumes display-aligned
ABS_MT_PRESSURE 0 255 Per-contact pressure (assumed; not verified)

Event struct

struct input_event on aarch64 Linux is 24 bytes: 8-byte tv_sec, 8-byte tv_usec, then __u16 type, __u16 code, __s32 value. Field offsets are 16 / 18 / 20 versus rM2's 8 / 10 / 12. The shared EvdevParser reads these from a per-profile EvdevLayout.

License

MIT