haraldandClaude Opus 5.5 b6580f197d fix(storage): Inhalt der zuletzt geöffneten Seite nach Neustart nicht überschreiben
Beim Start wurde nur die erste Seite geladen. War zuletzt eine spätere Seite
offen, war sie nach dem Neustart ein leerer, ungeladener Platzhalter: Das
Blatt erschien leer, und Zeichnen/Radieren/Hintergrund wechseln speicherte
den Platzhalter und überschrieb den bisherigen Inhalt der Seite.

- load_notebook lädt zusätzlich die aktuelle Seite
- Autosave schreibt nie ungeladene Platzhalter (Schutz ging beim Merge des
  Storage-Branches verloren)
- Regressionstests für beide Fälle

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 07:03:08 +02:00

🖋️ 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 von PresentMode::Mailbox für minimale Eingabeverzögerung.
  • Zwei-Stufen-Modell:
    1. Live-Phase: Eingehende Stylus-Samples werden in Echtzeit durch einen 1€-Filter (One-Euro-Filter) entprellt und inkrementell als GPU-Meshes dargestellt.
    2. 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 + Drag oder 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>/ mit manifest.json und 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 + Q oder Schließen des Fensters führt vor Programmende einen vollständigen synchronen Flush aus.

🖨️ Vektor-PDF-Export

  • Exportiert das Dokument via printpdf nach ~/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.deb
  • ink-notes_0.1.0_x86_64.AppImage
  • SHA256SUMS.txt
  • SHA256SUMS.txt.minisig (nur mit gesetztem MINISIGN_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 -W verschlüsselt den Schlüssel mit einem leeren Passwort; das minisign-CLI (z. B. Ubuntu-Paket 0.11) kann ihn ohne Terminal nicht laden. build-release.sh und die CI signieren deshalb mit rsign.

  1. Die zweite Zeile aus ink-notes.pub (beginnt mit RW) in src/updater.rs als UPDATE_SIGNING_PUBLIC_KEY eintragen und committen.
  2. Den kompletten Inhalt von ink-notes.key in Gitea unter Repository → Einstellungen → Actions → Secrets als MINISIGN_SECRET_KEY anlegen.
  3. ink-notes.key sicher 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-notes benötigt glibc >= 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üglich libfuse.so.2 ausgeben, kann entweder das Kompatibilitätspaket installiert werden:
    sudo apt install libfuse2t64
    
    oder das AppImage direkt ohne FUSE entpackt und ausgeführt werden:
    ./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:

  1. Minisign-Signatur (Pflicht):
    • Jedes Release enthält SHA256SUMS.txt.minisig, eine Minisign-Signatur der Prüfsummendatei.
    • Der Updater lädt SHA256SUMS.txt und die Signatur vor dem Paket und prüft sie gegen den in die App eingebauten öffentlichen Schlüssel (UPDATE_SIGNING_PUBLIC_KEY in src/updater.rs, Crate minisign-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.
  2. SHA-256-Integritätsprüfung:
    • Der SHA-256-Hash des heruntergeladenen Pakets wird mit dem Eintrag in der signierten SHA256SUMS.txt verglichen (sha2).
    • Weicht die Prüfsumme ab oder fehlt ein Eintrag, wird die Datei sofort gelöscht und der Installationsvorgang mit Fehlermeldung abgebrochen.
  3. 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.
  4. Isolierter Download-Pfad & Path-Traversal-Schutz:
    • Downloads werden in einem isolierten Benutzerverzeichnis abgelegt ($XDG_CACHE_HOME/ink-notes/updates bzw. ~/.cache/ink-notes/updates, Berechtigung 0700).
    • Asset-Namen werden sanitisiert: Pfadtrenner (/, \), Pfadüberquerungen (..), Steuerzeichen und leere Dateinamen werden strikt abgewiesen.
  5. 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>.bak gesichert (dessen Fehler nicht ignoriert werden) und anschließend per atomarem fs::rename ausgetauscht.

⚙️ 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:

  1. Runner online? Prüfen Sie in Gitea unter Einstellungen -> Aktionen -> Runner, ob der act_runner aktiv und mit einem grünen Punkt als Online markiert ist.
  2. Label-Übereinstimmung: Stimmen die im Runner registrierten Labels exakt mit dem runs-on-Feld in .gitea/workflows/release.yaml überein?
  3. Rechte: Der Workflow prüft if [ "$(id -u)" -ne 0 ] und setzt sudo nur ein, wenn der Runner nicht als root lä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-drivers oder proprietäre Treiber)
    • X11 / Wayland Bibliotheken (libx11-dev, libxcursor-dev, libxrandr-dev, libxi-dev, libxkbcommon-dev)

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).

S
Description
No description provided
Readme MIT
8.1 MiB
2026-10-19 17:19:13 +02:00
Languages
Rust 96.4%
Shell 1.9%
Go Template 0.8%
WGSL 0.5%
Python 0.4%