Kein Absturz mehr ohne nutzbare Grafikkarte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🖋️ ink-notes
Eine latenzoptimierte, hardwarebeschleunigte Inking- und Notizbuch-Anwendung für Linux, entwickelt in Rust mit wgpu und winit.
ink-notes kombiniert minimale Eingabelatenz beim Zeichnen mit Stift, Touchpad oder Maus mit einem flüssigen stufenlosen Viewport, automatischer Geometrie- und Formerkennung, Objekttransformationen, Multi-Dokumenten-Verwaltung und Vektor-PDF-Export.
✨ Features im Überblick
⚡ Latenzoptimierte Inking-Pipeline
- Hardware-Rendering: Direct-to-Screen-Rendering mit
wgpu(Vulkan / X11 / Wayland) unter Nutzung vonPresentMode::Mailboxfür minimale Eingabeverzögerung. - Zwei-Stufen-Modell:
- Live-Phase: Eingehende Stylus-Samples werden in Echtzeit durch einen 1€-Filter (One-Euro-Filter) entprellt und inkrementell als GPU-Meshes dargestellt.
- Commit-Phase (Post-Stroke Finalisierung): Akausale Ecken-Erkennung (Corner Preservation bei Knicken > 65°), gewichtetes 3-Punkt-Druckfenster, Ramer-Douglas-Peucker (RDP) Dezimierung und saubere runde Endkappen.
- Centripetal Catmull-Rom Splines: Weiche C1-stetige Kurveninterpolation ohne Überschwinger oder Schleifen.
- Werkzeuge:
- Stift (Pen): Feine, variable Strichstärken (0.3 mm bis 1.2 mm) mit Notiz-Farben (Schwarz, Blau, Rot, Grün, Grau).
- Textmarker (Highlighter): Breite, halbtransparente Striche (4.0 mm bis 8.0 mm) mit Multiplikations-Blending.
- Radierer (Eraser): Segment- und abstandsbasierte Löschung einzelner Striche auf dem DIN-A4-Canvas.
🔍 Stufenloser Zoom & Pan (Kamera)
- Viewport-Transformation: 2D-Projektionsmatrix im WGSL-Vertex-Shader.
- Cursor-zentrierter Zoom: Stufenloses Zoomen von 25 % bis 500 % zentriert auf die Cursor-Position via Mausrad, Trackpad-Pinch oder Tastenkürzel (
Strg +/Strg -/Strg 0). - Pan: Verschieben des Canvas per mittlerer Maustaste,
Leertaste + Dragoder Trackpad-Pan. - Inverse Koordinatentransformation: Präzise Umrechnung von Bildschirm- in DIN-A4-Dokumentkoordinaten (
screen_to_page).
📐 Geometrie- & Objekterkennung (Shape Recognition)
- Erkannte Primitiven:
- Geraden (Line): Sehnen- zu Pfadlängenverhältnis > 0.92.
- Rechtecke (Rectangle): 4 orthogonale Ecken via Douglas-Peucker-Polygonzug (RDP).
- Kreise & Ellipsen (Circle/Ellipse): Geschlossene Pfade mit minimaler Varianz via Least-Squares Circle Fit.
- Koordinatensysteme (Axes): Zwei orthogonale Achsen (80°–100° gesnappt auf exakt 90°) mit Richtungspfeilen.
- Dreiecke, Rhomben / Rauten & Oktaeder: Regelmäßige Polygone mit Eckpunkt-Analyse.
- Interaktionsmodelle:
- Draw-and-Hold: Verweilt der Stift am Ende eines Strichs für > 400 ms regungslos (< 3 px), snappt die Skizze sofort automatisch in eine perfekte Vektorform ein.
- Formen-Werkzeug (Shape Mode): Direkte Erkennung und Ersetzung beim Loslassen.
🖐️ Objektauswahl & Transformation (Lasso / Select)
- Lasso & Klick-Auswahl: Schnelles Markieren einzelner Striche oder Gruppen per Rahmen (
V). - Echtzeit-Verschieben: Freies Verschieben ausgewählter Objekte über die Seite.
- Stufenlose Skalierung: Größenänderung über interaktive Eck-Griffe.
- Drehung: Stufenlose Rotation um das Objektzentrum über den oberen Dreh-Griff oder exakte 90°-Schritte mit Taste
R. - Multicolor-Umfärbung: Ausgewählte Objekte direkt über die Farbpalette umfärben.
- Clipboard & Editieren: Kopieren (
Strg + C), Einfügen (Strg + V) und Löschen (Entf/Backspace). - Undo / Redo: Vollständige Transaktionshistorie (
Strg + Z/Strg + Y) für alle Aktionen inklusive Transformationen.
📚 Multi-Dokumenten- & Notizbuch-Verwaltung
- Dateisystem-Architektur: Jedes Dokument wird in einem separaten Verzeichnis unter
~/.local/share/ink-notes/notebooks/<id>/mitmanifest.jsonund Einzelseiten (page_<id>.json) gespeichert. - Dokumenten-Manager: Toolbar-Modal zum Anlegen, Wechseln, Umbenennen und Löschen von Notizbüchern.
- Verlustfreier Wechsel: Beim Dokumentenwechsel sichert ein synchroner Disk-Flush alle anstehenden Änderungen garantiert ab.
📄 Seiten & Hintergründe
- Virtuelles DIN-A4-Raster: 2100 × 2970 Koordinateneinheiten (10 Einheiten = 1 mm).
- Hintergrundmuster: Blanko, Kariert (5 mm), Liniert (7.5 mm), Punktraster (5 mm).
- Seitenverwaltung: Vor- und Zurückblättern, neue Seiten anfügen, Seitensprung-Menü.
💾 Autosave & Persistenz
- Asynchroner Autosave-Controller: Sichert Änderungen im Hintergrund nach 1.5 s Ruhezeit (Debounce), ohne das Zeichnen zu blockieren.
- Atomares Schreiben: Temp-Dateien mit atomarem Rename (
manifest.json.tmp->manifest.json) schützen vor Datenkorruption bei Abstürzen. - Dediziertes Speichern (
Strg + S): Sofortiger, synchroner Disk-Flush mit visueller Toast-Rückmeldung. - Sicherer Shutdown:
Strg + Qoder Schließen des Fensters führt vor Programmende einen vollständigen synchronen Flush aus.
🖨️ Vektor-PDF-Export
- Exportiert das Dokument via
printpdfnach~/Documents/<Titel>.pdf(mit automatischer Kollisionsvermeidung_1,_2). - Striche und geometrische Formen werden als skalierbare Vektoren ausgegeben. Kürzel:
Strg + E.
📦 Release, Packaging & Updater
- Natives .deb-Paket: Optimiert für Zorin OS 18 (Ubuntu 24.04 LTS Basis) mit vollständiger XDG Desktop-Integration (
.desktop, Hicolor-Icons SVG & PNG, AppStream Metainfo, Debian Copyright & Changelog). - Portables .AppImage: Eigenständiges x86_64 Bundle.
- In-App Updater (
src/updater.rs): Automatischer Prüflauf gegen Gitea Releases, SemVer-Filterung, Minisign-Signaturprüfung, SHA-256-Prüfung und atomarer In-Place Austausch.
1. Release-Pakete bauen
# Baut .deb, .AppImage und berechnet SHA256SUMS.txt in target/
./scripts/build-release.sh
Ergebnis in target/:
ink-notes_0.1.0_amd64.debink-notes_0.1.0_x86_64.AppImageSHA256SUMS.txtSHA256SUMS.txt.minisig(nur mit gesetztemMINISIGN_SECRET_KEY, siehe unten)
Release-Signatur einrichten (einmalig)
In-App-Updates werden nur mit gültiger Minisign-Signatur installiert. Signiert wird mit rsign2, einer Minisign-kompatiblen Implementierung (cargo install rsign2 --locked). Einmalig ein Schlüsselpaar erzeugen (ohne Passwort, da es nur als CI-Secret verwendet wird):
rsign generate -W -p ink-notes.pub -s ink-notes.key
Wichtig: Schlüssel und Signierwerkzeug müssen zusammenpassen.
rsign generate -Wverschlüsselt den Schlüssel mit einem leeren Passwort; dasminisign-CLI (z. B. Ubuntu-Paket 0.11) kann ihn ohne Terminal nicht laden.build-release.shund die CI signieren deshalb mitrsign.
- Die zweite Zeile aus
ink-notes.pub(beginnt mitRW) insrc/updater.rsalsUPDATE_SIGNING_PUBLIC_KEYeintragen und committen. - Den kompletten Inhalt von
ink-notes.keyin Gitea unter Repository → Einstellungen → Actions → Secrets alsMINISIGN_SECRET_KEYanlegen. ink-notes.keysicher offline aufbewahren und vom Rechner löschen. Geht der Schlüssel verloren, können bestehende Installationen keine Updates mehr prüfen.
Lokal signiert build-release.sh nur, wenn MINISIGN_SECRET_KEY gesetzt und rsign installiert ist. In der CI ist die Signatur Pflicht (INK_NOTES_REQUIRE_SIGNATURE=1), und das Skript prüft, dass der geheime Schlüssel zum eingebauten öffentlichen Schlüssel passt.
2. Installation auf Zorin OS 18 / Ubuntu 24.04
# Natives Debian-Paket installieren
sudo dpkg -i target/ink-notes_0.1.0_amd64.deb
# Oder portables AppImage starten
chmod +x target/ink-notes_0.1.0_x86_64.AppImage
./target/ink-notes_0.1.0_x86_64.AppImage
Hinweise zu AppImage, glibc & FUSE:
- glibc-Mindestversion:
ink-notesbenötigtglibc >= 2.39(Standard unter Ubuntu 24.04 LTS und Zorin OS 18). - Host-Grafikstack: Das AppImage bündelt die Anwendungslogik, nutzt für maximale Performance und Hardwarebeschleunigung jedoch den Treiber- und Grafikstack des Host-Systems (Vulkan ICD-Treiber, Wayland bzw. X11).
- FUSE 2 / FUSE 3 Kompatibilität: Ubuntu 24.04-basierte Distributionen nutzen standardmäßig FUSE 3 (
fuse3). Sollte das AppImage beim Start einen Fehler bezüglichlibfuse.so.2ausgeben, kann entweder das Kompatibilitätspaket installiert werden:oder das AppImage direkt ohne FUSE entpackt und ausgeführt werden:sudo apt install libfuse2t64./ink-notes_0.1.0_x86_64.AppImage --appimage-extract-and-run
3. Release auf Gitea veröffentlichen
Das Skript arbeitet vollkommen idempotent und aktualisiert bei wiederholter Ausführung Release-Notizen und Assets:
export GITEA_TOKEN="<DEIN_GITEA_TOKEN>"
./scripts/publish-gitea-release.sh
🛡️ Updater-Sicherheit
Der In-App-Updater (src/updater.rs) folgt strengen Sicherheitsrichtlinien:
- Minisign-Signatur (Pflicht):
- Jedes Release enthält
SHA256SUMS.txt.minisig, eine Minisign-Signatur der Prüfsummendatei. - Der Updater lädt
SHA256SUMS.txtund die Signatur vor dem Paket und prüft sie gegen den in die App eingebauten öffentlichen Schlüssel (UPDATE_SIGNING_PUBLIC_KEYinsrc/updater.rs, Crateminisign-verify). - Fehlt die Signatur, ist sie ungültig oder ist kein Schlüssel eingebaut, wird das Update abgelehnt. Ein kompromittierter Release-Server kann so keine manipulierten Pakete ausliefern.
- Jedes Release enthält
- SHA-256-Integritätsprüfung:
- Der SHA-256-Hash des heruntergeladenen Pakets wird mit dem Eintrag in der signierten
SHA256SUMS.txtverglichen (sha2). - Weicht die Prüfsumme ab oder fehlt ein Eintrag, wird die Datei sofort gelöscht und der Installationsvorgang mit Fehlermeldung abgebrochen.
- Der SHA-256-Hash des heruntergeladenen Pakets wird mit dem Eintrag in der signierten
- Redirect-Schutz & HTTPS:
- Jede Anfrage erzwingt HTTPS – auch Download-Links aus der Release-API und jedes Weiterleitungsziel.
- Bei HTTP-Redirects werden sensible Autorisierungs-Header (
Authorization: token ...) verworfen, sobald die Weiterleitung auf eine andere Origin oder ein externes CDN verweist.
- Isolierter Download-Pfad & Path-Traversal-Schutz:
- Downloads werden in einem isolierten Benutzerverzeichnis abgelegt (
$XDG_CACHE_HOME/ink-notes/updatesbzw.~/.cache/ink-notes/updates, Berechtigung0700). - Asset-Namen werden sanitisiert: Pfadtrenner (
/,\), Pfadüberquerungen (..), Steuerzeichen und leere Dateinamen werden strikt abgewiesen.
- Downloads werden in einem isolierten Benutzerverzeichnis abgelegt (
- Atomarer AppImage-Tausch:
- Kein direktes Überschreiben laufender Programmdateien per
fs::copy(verhindert den Linux-Kernel-Fehler "Text file busy"). - Die neue Datei wird temporär im selben Verzeichnis angelegt (
<name>.new.<pid>), mit Ausführungsrechten (0755) versehen, das bestehende AppImage als<name>.bakgesichert (dessen Fehler nicht ignoriert werden) und anschließend per atomaremfs::renameausgetauscht.
- Kein direktes Überschreiben laufender Programmdateien per
⚙️ CI-Runner einrichten (Gitea Actions / act_runner)
Der Veröffentlichungs-Workflow liegt unter .gitea/workflows/release.yaml.
Runner-Label konfigurieren
Der Workflow nutzt standardmäßig das Runner-Label:
runs-on: ubuntu-latest
In der Konfigurationsdatei Ihres Gitea act_runner (config.yaml) muss dieses Label auf ein passendes Container-Image oder den Host gemappt sein:
runner:
labels:
- "ubuntu-latest:docker://catthehacker/ubuntu:act-latest"
# oder für native Host-Ausführung:
# - "ubuntu-latest:host"
Diagnose & Fehlerbehebung
Bleibt ein Release-Job auf "Waiting to run" hängen:
- Runner online? Prüfen Sie in Gitea unter Einstellungen -> Aktionen -> Runner, ob der
act_runneraktiv und mit einem grünen Punkt als Online markiert ist. - Label-Übereinstimmung: Stimmen die im Runner registrierten Labels exakt mit dem
runs-on-Feld in.gitea/workflows/release.yamlüberein? - Rechte: Der Workflow prüft
if [ "$(id -u)" -ne 0 ]und setztsudonur ein, wenn der Runner nicht alsrootläuft. Das verhindert Fehler sowohl in unprivilegierten Host-Umgebungen als auch in Root-Containern.
🚀 Schnellstart (Entwicklung)
Voraussetzungen
- Rust & Cargo: Version 1.75 oder neuer (
rustup update stable) - System-Bibliotheken (Linux):
- Vulkan-Treiber (z. B.
mesa-vulkan-driversoder proprietäre Treiber) - X11 / Wayland Bibliotheken (
libx11-dev,libxcursor-dev,libxrandr-dev,libxi-dev,libxkbcommon-dev)
- Vulkan-Treiber (z. B.
Bauen & Ausführen
# Repository klonen
git clone https://gitea.pansi.eu/harald/ink-notes.git
cd ink-notes
# Kompilieren und starten via Starter-Skript
./run.sh
Alternativ direkt mit Cargo:
cargo run --release
⌨️ Tastatur- & Maus-Shortcuts
| Aktion | Tastenkürzel / Mausgeste |
|---|---|
| Zeichnen | Linke Maustaste / Stiftspitze |
| Canvas verschieben (Pan) | Mittlere Maustaste oder Leertaste + Drag |
| Zoom zentriert auf Cursor | Mausrad / Touchpad-Pinch |
| Zoom zurücksetzen (100 %) | Strg + 0 |
| Zoom In / Out | Strg + + / Strg + - |
| Auswahlwerkzeug (Lasso) | V |
| Auswahl um 90° drehen | R |
| Kopieren & Einfügen | Strg + C / Strg + V |
| Auswahl löschen | Entf oder Backspace |
| Undo / Redo | Strg + Z / Strg + Y (oder Strg + Shift + Z) |
| Nächste Seite | Bild ab (PageDown) oder N |
| Vorherige Seite | Bild auf (PageUp) |
| Erste / Letzte Seite | Pos 1 (Home) / Ende (End) |
| Jetzt speichern (Disk Flush) | Strg + S |
| Vektor-PDF-Export | Strg + E |
| Dialoge / Menüs schließen | Esc |
| Beenden (mit Disk-Flush) | Strg + Q (oder Fenster schließen) |
🏗️ Projekt- und Modul-Architektur
src/
├── main.rs # Event-Loop (winit), Input-Routing, App-Orchestrierung
├── lib.rs # Bibliotheks-Exporte für Tests und Komponenten
├── canvas.rs # wgpu Render-Pipeline, GPU-Buffer, Zeichenzustand
├── viewport.rs # Kamera-Pan, Zoom, 2D-Transformationsmatrizen
├── shader.wgsl # WGSL Vertex- & Fragment-Shader für Inking-Meshes
├── filter.rs # One-Euro-Filter gegen Eingabe-Jitter
├── spline.rs # Catmull-Rom, Corner Preservation & Dreiecksstreifen
├── geometry.rs # RDP-Algorithmus, Least-Squares Circle Fit, Shape Recognition
├── selection.rs # Lasso-Auswahl, Bounding-Box, Griffe, Rotation & Skalierung
├── background.rs # Tessellierung für Kariert, Liniert, Punktraster
├── document.rs # Notizbuch-Datenmodell, Seiten, DIN-A4-Koordinatensystem
├── history.rs # Undo/Redo-Transaktionsstapel (Add, Erase, Transform)
├── storage.rs # Atomares Laden/Speichern von Manifest & Seiten, Multi-Notebooks
├── autosave.rs # Debounced Hintergrund-Autosave mit MPSC-Kanal
├── updater.rs # In-App Gitea Updater (SemVer, HTTPS, SHA256-Integrität, Self-Update)
├── export.rs # Skalierbarer Vektor-PDF-Export via printpdf
└── ui.rs # Floating Glassmorphism-Toolbar, Dokumenten- & Update-Manager (egui)
Ausführliche Details zur Architektur und mathematischen Modellen findest du unter docs/ARCHITECTURE.md.
🧪 Tests ausführen
Die gesamte Inking-Pipeline, Geometrieerkennung, Speicherlogik und der Updater sind mit umfassenden Unit-Tests abgesichert:
cargo test
Alle Unit-Tests prüfen Berechnungen, Hit-Testing, Undo/Redo, Persistenz, Updater-Sicherheit und Versionsvergleiche konsistent ab.
📄 Lizenz
Dieses Projekt ist unter der MIT-Lizenz lizenziert (Copyright © 2026 Harald Pansi).