Initial commit: Chatter — assistive-writing app for reMarkable Paper Pro Move

Direct-framebuffer ink pipeline (stock-quality strokes), finger-wipe erase,
growable scrolling canvas with color-ghost cleanup, bidirectional toggle with a
persistent 4-finger return launcher, instant button feedback. Includes prebuilt
aarch64 binaries (dist/), build/deploy/install scripts, a user guide, and a
complete technical reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 16:34:28 +02:00
commit 5f21d9099c
43 changed files with 2878 additions and 0 deletions

89
src/main.cpp Normal file
View File

@@ -0,0 +1,89 @@
// Chatter — Qt Quick app with a direct-framebuffer ink pipeline.
// Pen strokes are drawn straight into the e-paper framebuffer (captured by
// FbCapture) and refreshed with an explicit solid swap (InkEngine). Qt Quick is
// used only for the static UI (Back / Clear). This is what gives stock-quality
// solid, low-latency ink — the Qt scene graph can't.
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QScreen>
#include <QSettings>
#include <QVariantMap>
#include <QColor>
#include <QRegion>
#include <QRect>
#include "PenDevice.h"
#include "AppControl.h"
#include "InkEngine.h"
#include "FbCapture.h" // links the setBuffers interposer into the executable
struct PenStyle {
int type = 2;
qreal size = 2.0;
QColor color = Qt::black;
};
// Read the pen the user last selected in the standard GUI (xochitl.conf,
// [General]/LastWritingTool — flushed when xochitl stops, i.e. on Back).
static PenStyle readPenStyle()
{
PenStyle ps;
QSettings xs(QStringLiteral("/home/root/.config/remarkable/xochitl.conf"),
QSettings::IniFormat);
const QVariantMap t = xs.value(QStringLiteral("LastWritingTool")).toMap();
if (t.isEmpty()) {
qWarning("readPenStyle: LastWritingTool unreadable; using defaults");
return ps;
}
ps.type = t.value("LastPen").toInt();
ps.size = t.value("LastPenSize").toDouble();
const uint code = t.value("LastPenColorCode").toUInt();
if (code != 0)
ps.color = QColor::fromRgba(QRgb(code));
qInfo("readPenStyle: pen=%d size=%.2f color=%s", ps.type, ps.size,
qPrintable(ps.color.name(QColor::HexArgb)));
return ps;
}
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
const PenStyle penStyle = readPenStyle();
const QSize screen = app.primaryScreen()->geometry().size();
PenDevice pen(screen.width(), screen.height());
AppControl appControl;
InkEngine ink;
ink.setInkColor(penStyle.color);
// reMarkable exposes only the size CATEGORY (LastPenSize 1/2/3 =
// thin/thicker/thickest). Calibrate to measured widths on this 264-PPI panel:
// thicker ~1mm, thickest ~2mm, thinnest ~2px. px/mm = 264/25.4 ~= 10.4.
constexpr double pxPerMm = 264.0 / 25.4;
const double maxMm = qMax(0.2, (penStyle.size - 1.0) * 1.0); // sz3->2mm, sz2->1mm
ink.setWidthRange(2.0, maxMm * pxPerMm);
ink.setPenType(penStyle.type);
// Back button (finger or stylus) → return to the standard GUI.
QObject::connect(&ink, &InkEngine::backRequested, &appControl, &AppControl::returnToStandard);
// Pen drives the ink engine directly (no QML in the ink loop).
QObject::connect(&pen, &PenDevice::strokeStart, &ink, &InkEngine::strokeStart);
QObject::connect(&pen, &PenDevice::strokeMove, &ink, &InkEngine::strokeMove);
QObject::connect(&pen, &PenDevice::strokeEnd, &ink, &InkEngine::strokeEnd);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("appControl", &appControl);
engine.rootContext()->setContextProperty("ink", &ink);
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreationFailed,
&app, []() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.loadFromModule("Chatter", "Main");
return app.exec();
}