Sign in to any Nextcloud server (also private ones in a subfolder, with
their own port or a self-signed certificate) through the browser with
Login Flow v2; the app gets an app password of its own, which
"Disconnect" revokes again.
A background thread syncs the storage folder with the folder ink-notes
on the server every 30 seconds and shortly after saving, three-way
against the state after the last pass: one-sided changes are copied,
manifests and library.json are merged, a page changed on both sides is
kept twice (the server's version as conflict copy, which disk_watch
turns into a page), empty pages give way, deletions go into the trash
on both sides and lose against changes. Changes to the open notebook
wait while it has unsaved changes. Folder ETags spare listing unchanged
notebooks; the pass stops instead of deleting when the storage folder
or the server folder is missing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NativeActivity draws the whole window, also below the system bars, so the
top toolbar button lay under the status bar and taps there went to
Android (no other notebook could be opened). InkActivity now reports the
system bar and cutout insets; the app shrinks egui's screen rect to the
free area and adds the side insets to the page centering. The page itself
still scrolls below the bars.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New module math_assist.rs: when the last two strokes form an = and the pen
rests for 500 ms, the strokes left of it (in a band around the =, up to a
wide gap) are recognized and computed. The suggestion appears next to
the = and shows what was read, e.g. "2⁴ = 16"; tapping it with the pen
or clicking inserts the result as a text box in the ink color and as high
as the handwriting, as one undo step. The chip is not interactive for
egui, so fingers and palms pass through. Any new stroke drops it. In a
chain like x = 2 · 3 = the part after the previous = is computed.
Off by default: ☰ → Input → "Compute formulas after =", saved in
ui.json. math::display writes the recognized expression neatly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New module math_ink.rs turns the strokes of an expression into text for
math::solve. Consecutive overlapping strokes are grouped into symbols,
each symbol is classified with the $P point-cloud recognizer (plus the
turning angle and a small stroke-count penalty) against built-in
templates for digits, + ( ) / x y a b, and the structure comes from the
positions: stacked lines are =, stacked dots :, lines with symbols above
and below are fraction bars, low dots between digits decimal commas and
raised symbols exponents. Own templates can be added for stage 4.
Tested with synthetic handwriting (slanted, rotated, warped); not used by
the UI yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New module math.rs: solve() reads a recognised expression such as 2^4,
2,5 · 3 or (x + 3)² and returns its result as text (16, 7,5,
x² + 6x + 9). Own parser with exact fractions and polynomial expansion
instead of evalexpr, as implicit multiplication and expanding need it
anyway. Not used by the UI yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The release job now waits for the android job, takes its APK, lists it in
SHA256SUMS.txt and uploads it as a release asset. The android job is no
longer optional. README describes installing the APK.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- scripts/setup-android-sdk.sh installs fixed versions of the Android
command-line tools (checksum verified), platform 34, build-tools
34.0.0 and NDK 28.2
- packaging/android/Containerfile + scripts/build-apk-container.sh build
the APK in a container (podman or docker) with JDK 17 and Rust 1.99
- New CI job "Android APK (experimental)": builds the APK and keeps it
as a workflow artifact, signed with the key from the secrets
ANDROID_KEYSTORE_B64/ANDROID_KEYSTORE_PASS/ANDROID_KEY_ALIAS if set.
Not part of the release yet; its failure doesn't block the release.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
InkActivity extends NativeActivity:
- An invisible view with its own input connection receives the soft
keyboard's text (commitText, deleteSurroundingText), so characters
without a key event (umlauts by long press, ...) arrive as well. The
field is a visible-password field, so keyboards commit each character
instead of composing words.
- Import and "insert image" open the system file dialog
(ACTION_OPEN_DOCUMENT); the chosen file is copied to the cache folder
and handed to Rust by path.
src/android_bridge.rs calls the activity through JNI and receives its
native calls; package-android.sh compiles the class with javac and d8.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Pen vs. finger (#44): the patched winit hands Android pointers with
tool type stylus/eraser to the app separately (take_pen_samples, with
hover and button state) instead of as touch events. src/pen.rs turns
them, like octotablet's events on the desktop, into one PenInput
stream, so pen buttons, the eraser end and palm rejection work the
same everywhere. Hover exit right before touch-down does not count as
leaving; a pen without hover leaves 150 ms after lifting.
- Soft keyboard: shown via AndroidApp::show_soft_input when a text field
gets focus, hidden when it loses it. The patched winit now fills
KeyEvent::text, so typed characters reach egui.
- Exports (PDF, PNG, SVG) are written to the app folder and copied to
Download/ink-notes through the MediaStore (Android 10+, via JNI).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
File times are only as fine as the kernel clock tick. A manifest rewritten
right after a check with the same length (one page replaced by another)
looked unchanged, so the change was never picked up. Files up to 64 KiB
(manifest, open marker, small pages) now also carry a content hash.
Showed up as a flaky test_page_still_being_synced_is_tried_again in CI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
egui-winit reports `repaint: true` for every RedrawRequested, and the app
followed that with another request_redraw – so it redrew at full frame
rate all the time, keeping the app and the compositor (gnome-shell) busy.
Follow-up frames egui needs already come via `repaint_delay`.
The toast no longer redraws every frame either: one wake-up when its
fade-out starts is enough. While the updater checks or downloads, the
loop looks for its results every 100 ms instead.
Idle CPU under Xvfb/lavapipe: 187 % before, 0 % after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The drawing surface is created once the native window exists (Resumed)
and released when the app goes to the background (Suspended); before
that everything is saved, as Android may stop the app at any time
- Android's back button acts like Escape instead of closing the app
- Panics are written to logcat; no update check on Android yet
- scripts/package-android.sh builds target/ink-notes_<version>_arm64.apk
without Gradle (NDK, aapt2, zipalign, apksigner); signing key from
ANDROID_KEYSTORE/ANDROID_KEYSTORE_PASS/ANDROID_KEY_ALIAS, otherwise
the debug key
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
printpdf 0.7 pinned lopdf 0.31; newer printpdf versions are a complete
rewrite. The export now uses src/pdf_writer.rs, a small writer on top of
lopdf 0.46 with the subset of the printpdf API the export used: pages with
layers (optional content groups), paths, embedded TrueType fonts (Type0,
Identity-H, ToUnicode), images and bookmarks. Bookmark titles are written
as UTF-16BE directly, so the post-processing step for them is gone.
pdf_merge uses lopdf 0.46 as well.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
octotablet's default emulates a pen tool from the mouse on Windows Ink, so
every mouse move counted as pen hover and every click as pen down, while the
regular mouse events were ignored as duplicates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shared groundwork:
- paths.rs: folders for data, settings and downloads per system (Linux XDG
unchanged, Windows %APPDATA%/%LOCALAPPDATA%, Android app folder)
- Linux-only parts behind cfg: Wayland on-screen keyboard (osk/wayland.rs),
display backend and app id, the vendored winit touch extensions
- run_with(event_loop) so that other entry points can start the app
Windows (#48):
- cross-compiled with MinGW into a portable ink-notes_<version>_x86_64.exe
(system DLLs only), no console window in release builds
- file picker: user folders and drives; held notebook locks are detected
(ERROR_LOCK_VIOLATION)
- updater: replaces the running .exe via rename, active at the next start
- octotablet panicking without the Tablet PC components no longer crashes
the app
- release: .exe built in CI and part of the signed checksums
- all library tests pass under wine
Android (#42):
- android_main entry point, winit with android-native-activity,
android_logger; octotablet and arboard left out there
- cargo check and the cdylib build for aarch64-linux-android pass; the window
surface does not follow the activity lifecycle yet
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Storage location setting: the notebooks can be moved into a folder kept in
sync by Nextcloud, Google Drive, Syncthing etc. (subfolder `ink-notes`);
existing notebooks are copied there if it is still empty, the originals stay.
- Changes from other devices are taken over while everything local is saved:
changed pages are reloaded, new/removed/reordered pages and titles come in
through the manifest. A page file still being synced is retried later
instead of being set aside as damaged.
- Conflict copies made by sync clients become pages of their own.
- `.opened_by.json` marks the device that has a notebook open; opening a
notebook open elsewhere shows a warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The import now also accepts Saber files: .sbn2 (BSON) with its asset
files next to it, .sba archives (zip with main.sbn2 and assets) and the
old JSON .sbn, including the layout before format version 8 with
strokes outside the pages. Taken over: strokes with pen pressure and
perfect_freehand's thinning, highlighters, circle and rectangle shapes,
typed text (Quill delta as one text field), images (SVG rasterized),
background images and PDF pages as PDF backgrounds, paper patterns.
BSON and zip are read by small built-in readers, so no new
dependencies. The SVG rasterizer moved to src/import.rs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The import (menu entry and drag & drop) now also accepts .rnote files
from Rnote 0.5.10 on, including the transformation and document config
layouts that changed in 0.13 and 0.15. Taken over: brush strokes with
their pressure curve, shapes as outlines, texts, bitmap images and SVG
images, which are rasterized with resvg (Rnote stores imported PDF
pages as SVG). Fixed-size and continuous documents keep their pages;
infinite ones become an endless notebook.
The parts shared with the Xournal++ import moved to src/import.rs.
resvg and its dependencies were added to the Flatpak cargo sources.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The import menu entry (formerly "Import PDF") and drag & drop now also
accept .xopp and .xoj files. Each Xournal page becomes a page of a new
notebook: strokes with pen pressure, highlighters, texts, embedded
images, lined/squared/dotted paper, image backgrounds and PDF
backgrounds (looked up at the stored path and next to the file). All
layers are flattened in order; LaTeX formulas are skipped and counted
in the status message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each stroke mesh has about 16 points per pen sample, and every one of them
went into the PDF as a polygon point: a full handwritten page (1650 strokes)
took 2.9 s to export, and PNG export and copying a selection as an image
took as long, because both go through the PDF. The outline is now simplified
with a closed Douglas-Peucker that measures against segments, at most 0.01 mm
from the drawn outline. The same page now takes 0.44 s (PNG 0.55 s), the PDF
shrinks from 10.7 MB to 1.4 MB, and the rendered PNG differs only in edge
anti-aliasing (max 21/255). INK_NOTES_EXACT_OUTLINES=1 exports every point
as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pia's log shows the lift arriving 200-300 ms after the last move on every
swipe, so the release looked like a resting finger and nothing glided. The
vendored winit now keeps the Wayland timestamp of each touch event
(take_touch_time), and the fling measures with those times instead of when
the app got around to handling the events. The diagnostics log both gaps and
any event handling that blocks the app for more than 40 ms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/main.rs is now only the desktop entry point; the app itself lives in
src/app.rs, so other entry points such as android_main can start it. The code
is unchanged apart from crate:: paths; the source scans in the i18n and UI
tests and the docs point to app.rs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clamping the continuous or endless view reset the fling while the finger was
still down, so the release measured 0 px/s. Only an active glide stops there
now. The fling diagnostics also log moves, path length and the time since
the last move.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- #38: the eraser size button opens a menu like the pens: four presets and a slider
(2–30 mm diameter)
- #41: shape corners stay pointed down to about 14° (miter limit 8 instead of 2)
- #40: page picker tiles are as high as the tallest page needs, so slides get flat tiles;
the frame follows the page instead of the whole tile
- #28: the fling diagnosis also logs finger starts, mouse buttons and wheel/touchpad events
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- #41: snapped triangles, rectangles and polygons keep the full stroke width on every
side; corners are mitered, sharp ones bevelled (the old corner offset thinned the edges)
- #38: four eraser sizes, cycled by a button below the eraser modes and remembered in
ui.json
- #37: a selection made with the pen's select button can be moved with the bare pen
- #39: fingers resting on the screen when the pen comes near, or touching it while the pen
is near (and 0.7 s after), are ignored until lifted; the vendored winit ignores touches
on the title bar meanwhile
- #40: the page picker shows imported PDF pages, rendered small in the background
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The view stops 10 mm left of and above the start frame, or above and left
of content that lies further out in notebooks from 0.29/0.30, so a new
endless notebook starts at the top left like a sheet of paper.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
winit 0.29 draws the title bar itself on Wayland but only handles the
mouse there. A vendored copy of winit 0.29.15 (vendor/winit, used via
[patch.crates-io]) passes touches on the decorations to the frame like a
left click, so close, minimize and maximize work with a finger and
dragging the title bar moves the window.
CI no longer sets a global -D warnings, which would also apply to the
vendored crate; clippy still runs with -D warnings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Toolbar: 2 px gaps and 3 px dividers instead of 3 px and 7 px, and the
size estimate now matches the measured layout exactly. On a 900 px
screen the buttons are back to 30 px despite the new view-only button.
- Text tool: a new field starts three letters wide and grows with the
longest line up to the wrap width at the page edge.
- INK_NOTES_DEBUG_FLING now confirms it is active and logs every finger
lift, including touches ignored because the pen is near.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A 👁 button below the notebooks hides the tools. Pen, finger and mouse
then only scroll and zoom (the pen glides on like a finger), editing
shortcuts are ignored, Page Down no longer appends pages, and selection,
text box and ruler are closed when entering the mode.
The toolbar size estimate now includes the measured frame overhead and
can shrink buttons to 26 px, so the extra button still fits at 768 px.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CONTRIBUTING (en/de) and README now state that new code comments and commit messages are
written in English.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Die Vorlage der Release-Notes ist vollständig übersetzt. CONTRIBUTING (en/de) und README
vermerken, dass neue Code-Kommentare englisch geschrieben werden.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>