n-link/desktop/README.md
Josip Jureta a0d955d3e7
fix(cx2): checksum NNSE packets over their declared length (#2)
Long CX II operations — listing a folder with many entries, downloading a
file of any size — failed with a reported `Busy`.

readPacket checksummed `transferred`, the byte count libusb returned, rather
than `completeLength`, the length the packet declares. The calculator can
hand back more bytes than the packet occupies (a 64-byte packet arriving as
65 was what showed up in tracing), and the stray byte failed the checksum.
The packet was dropped and so never acked, the calculator retransmitted the
same sequence number indefinitely, and the receive loop gave up after 40
reads. `transferred` was also wrong after the multi-chunk continuation loop,
where it holds only the final chunk's size — the reason large stream
transfers were unreliable.

Also add NNSE retransmission to packet_send_cx2. A lost packet or lost ack
was previously fatal, since the packet was written exactly once. Retries now
carry the "not the first try" bit (reqAck bit 3) that TI's own stack sets,
keeping the same seqno so the calculator can drop a duplicate. This path did
not trigger in testing — every packet acked on the first attempt once the
checksum was fixed — so its timeouts are deliberately generous to avoid
retransmitting during slow flash writes.

While here, zero `misc` and `unknown` instead of sending malloc garbage
inside a checksummed header, and handle allocation failure.

Verified against CX II firmware (0451:e022): `ls /` returns all 51 entries
across 8 runs, a 193 KB download is byte-identical across two runs, and
mkdir/rmdir round-trips.

Note the error was never actually Busy. The libnspire crate (0.2.3) matches
its positive NSPIRE_ERR_* constants against libnspire's negative return
codes, so every error falls through to the libusb table and is mislabelled;
-NSPIRE_ERR_INVALPKT and LIBUSB_ERROR_BUSY are both -6. That mismapping is
upstream in the crate and is not addressed here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 16:35:23 -04:00

3.1 KiB

N-Link

Free, cross-platform, CX-II compatible computer linking program for the TI-Nspire.

This package is a Tauri 2 + Vue 3 + Vite application. It was ported from Tauri 1.0.0-beta.8 / Vue CLI, which linked webkit2gtk-4.0 and could not build or run on current distributions (Ubuntu 26.04 ships only webkit2gtk-4.1).

Currently Linux-only: bundle targets are deb and appimage.

Prerequisites

  • Node.js 22+ (npm ships with it — this project uses npm workspaces, not yarn)
  • Rust 1.97+
  • System libraries (Ubuntu/Debian):
sudo apt-get install -y \
  libwebkit2gtk-4.1-dev \
  libjavascriptcoregtk-4.1-dev \
  libsoup-3.0-dev \
  librsvg2-dev \
  libxdo-dev \
  libayatana-appindicator3-dev \
  libusb-1.0-0-dev \
  build-essential curl wget file

Verify they resolve before building:

for p in webkit2gtk-4.1 javascriptcoregtk-4.1 libsoup-3.0 gtk+-3.0 libusb-1.0; do
  printf "%-24s " "$p"; pkg-config --modversion "$p" || echo MISSING
done

Setup

Run from the repository root (this is a workspace member):

npm install

--legacy-peer-deps is no longer needed — n-link-core is on Vue 3 as of the frontend port.

Note that the root package.json workspaces list contains only desktop and n-link-core. web/ is deliberately excluded: it is still on Nuxt 2 and pulls tailwindcss@1.9.6, which npm hoists to the root and which then shadows the tailwindcss@3 that desktop and @tailwindcss/forms require. Restore the entry when web is migrated.

Development

cd desktop
npm run tauri dev

Production build

cd desktop
npm run tauri build

Produces src-tauri/target/release/bundle/deb/*.deb and .../appimage/*.AppImage.

Headless CLI

The same binary works without a GUI:

./src-tauri/target/release/n-link --help
./src-tauri/target/release/n-link ls /

Notes and gotchas

  • cargo commands need dist/ to exist. Tauri's generate_context! macro validates the frontendDist path at compile time, so a bare cargo check / cargo build in src-tauri/ fails on a fresh clone until the frontend has been built once: npm run build --workspace desktop. npm run tauri build does this for you.
  • Do not run the binary from a snap-confined shell (for example, the integrated terminal of VS Code installed as a snap). Snap injects an LD_LIBRARY_PATH whose libpthread shadows the system one, producing undefined symbol: __libc_pthread_init. Use a normal terminal.
  • AppImage needs FUSE. Without it, run the AppImage with --appimage-extract and launch squashfs-root/AppRun instead.
  • vendor/libnspire-sys at the repo root is a locally patched crate wired in via [patch.crates-io]. It raises the CX II NNSE handshake retry limit (10 → 30), fixes the readPacket checksum length bug that made CX II transfers fail with a reported Busy, and adds NNSE retransmission. The build will not reproduce upstream behaviour without it. See nlink-cli/README.md for the details.